summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/components/session/session-context-breakdown.ts
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-02-12 09:49:14 -0600
committerGitHub <[email protected]>2026-02-12 09:49:14 -0600
commitff4414bb152acfddb5c0eb073c38bedc1df4ae14 (patch)
tree78381c67d21ef6f089647f6b19e7aa2976840dbc /packages/app/src/components/session/session-context-breakdown.ts
parent56ad2db02055955f926fda0e4a89055b22ead6f9 (diff)
downloadopencode-ff4414bb152acfddb5c0eb073c38bedc1df4ae14.tar.gz
opencode-ff4414bb152acfddb5c0eb073c38bedc1df4ae14.zip
chore: refactor packages/app files (#13236)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Frank <[email protected]>
Diffstat (limited to 'packages/app/src/components/session/session-context-breakdown.ts')
-rw-r--r--packages/app/src/components/session/session-context-breakdown.ts132
1 files changed, 132 insertions, 0 deletions
diff --git a/packages/app/src/components/session/session-context-breakdown.ts b/packages/app/src/components/session/session-context-breakdown.ts
new file mode 100644
index 000000000..e263b2957
--- /dev/null
+++ b/packages/app/src/components/session/session-context-breakdown.ts
@@ -0,0 +1,132 @@
+import type { Message, Part } from "@opencode-ai/sdk/v2/client"
+
+export type SessionContextBreakdownKey = "system" | "user" | "assistant" | "tool" | "other"
+
+export type SessionContextBreakdownSegment = {
+ key: SessionContextBreakdownKey
+ tokens: number
+ width: number
+ percent: number
+}
+
+const estimateTokens = (chars: number) => Math.ceil(chars / 4)
+const toPercent = (tokens: number, input: number) => (tokens / input) * 100
+const toPercentLabel = (tokens: number, input: number) => Math.round(toPercent(tokens, input) * 10) / 10
+
+const charsFromUserPart = (part: Part) => {
+ if (part.type === "text") return part.text.length
+ if (part.type === "file") return part.source?.text.value.length ?? 0
+ if (part.type === "agent") return part.source?.value.length ?? 0
+ return 0
+}
+
+const charsFromAssistantPart = (part: Part) => {
+ if (part.type === "text") return { assistant: part.text.length, tool: 0 }
+ if (part.type === "reasoning") return { assistant: part.text.length, tool: 0 }
+ if (part.type !== "tool") return { assistant: 0, tool: 0 }
+
+ const input = Object.keys(part.state.input).length * 16
+ if (part.state.status === "pending") return { assistant: 0, tool: input + part.state.raw.length }
+ if (part.state.status === "completed") return { assistant: 0, tool: input + part.state.output.length }
+ if (part.state.status === "error") return { assistant: 0, tool: input + part.state.error.length }
+ return { assistant: 0, tool: input }
+}
+
+const build = (
+ tokens: { system: number; user: number; assistant: number; tool: number; other: number },
+ input: number,
+) => {
+ return [
+ {
+ key: "system",
+ tokens: tokens.system,
+ },
+ {
+ key: "user",
+ tokens: tokens.user,
+ },
+ {
+ key: "assistant",
+ tokens: tokens.assistant,
+ },
+ {
+ key: "tool",
+ tokens: tokens.tool,
+ },
+ {
+ key: "other",
+ tokens: tokens.other,
+ },
+ ]
+ .filter((x) => x.tokens > 0)
+ .map((x) => ({
+ key: x.key,
+ tokens: x.tokens,
+ width: toPercent(x.tokens, input),
+ percent: toPercentLabel(x.tokens, input),
+ })) as SessionContextBreakdownSegment[]
+}
+
+export function estimateSessionContextBreakdown(args: {
+ messages: Message[]
+ parts: Record<string, Part[] | undefined>
+ input: number
+ systemPrompt?: string
+}) {
+ if (!args.input) return []
+
+ const counts = args.messages.reduce(
+ (acc, msg) => {
+ const parts = args.parts[msg.id] ?? []
+ if (msg.role === "user") {
+ const user = parts.reduce((sum, part) => sum + charsFromUserPart(part), 0)
+ return { ...acc, user: acc.user + user }
+ }
+
+ if (msg.role !== "assistant") return acc
+ const assistant = parts.reduce(
+ (sum, part) => {
+ const next = charsFromAssistantPart(part)
+ return {
+ assistant: sum.assistant + next.assistant,
+ tool: sum.tool + next.tool,
+ }
+ },
+ { assistant: 0, tool: 0 },
+ )
+ return {
+ ...acc,
+ assistant: acc.assistant + assistant.assistant,
+ tool: acc.tool + assistant.tool,
+ }
+ },
+ {
+ system: args.systemPrompt?.length ?? 0,
+ user: 0,
+ assistant: 0,
+ tool: 0,
+ },
+ )
+
+ const tokens = {
+ system: estimateTokens(counts.system),
+ user: estimateTokens(counts.user),
+ assistant: estimateTokens(counts.assistant),
+ tool: estimateTokens(counts.tool),
+ }
+ const estimated = tokens.system + tokens.user + tokens.assistant + tokens.tool
+
+ if (estimated <= args.input) {
+ return build({ ...tokens, other: args.input - estimated }, args.input)
+ }
+
+ const scale = args.input / estimated
+ const scaled = {
+ system: Math.floor(tokens.system * scale),
+ user: Math.floor(tokens.user * scale),
+ assistant: Math.floor(tokens.assistant * scale),
+ tool: Math.floor(tokens.tool * scale),
+ }
+ const total = scaled.system + scaled.user + scaled.assistant + scaled.tool
+ return build({ ...scaled, other: Math.max(0, args.input - total) }, args.input)
+}