From 6c882e6a27d32fae8c94d6418a33b6a5ee8ab25c Mon Sep 17 00:00:00 2001 From: Bhagirathsinh Vaghela Date: Sun, 10 May 2026 13:49:52 +0200 Subject: [PATCH 1/5] feat(cache): add cache token audit logging and TUI sidebar behind OPENCODE_CACHE_AUDIT Co-authored-by: Bhagirathsinh Vaghela --- packages/core/src/flag/flag.ts | 1 + .../tui/feature-plugins/sidebar/context.tsx | 46 +++++++++++++++---- packages/opencode/src/session/processor.ts | 8 ++++ 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/packages/core/src/flag/flag.ts b/packages/core/src/flag/flag.ts index f76d1aaf9d2..fd2e0199e08 100644 --- a/packages/core/src/flag/flag.ts +++ b/packages/core/src/flag/flag.ts @@ -86,6 +86,7 @@ export const Flag = { OPENCODE_EXPERIMENTAL_WORKSPACES: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_WORKSPACES"), OPENCODE_EXPERIMENTAL_EVENT_SYSTEM: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"), OPENCODE_EXPERIMENTAL_SESSION_SWITCHING: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_SESSION_SWITCHING"), + OPENCODE_EXPERIMENTAL_CACHE_AUDIT: truthy("OPENCODE_EXPERIMENTAL_CACHE_AUDIT"), // Evaluated at access time (not module load) because tests, the CLI, and // external tooling set these env vars at runtime. diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx index 405e8c1458a..dc76001c0e9 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx @@ -1,7 +1,8 @@ import type { AssistantMessage } from "@opencode-ai/sdk/v2" import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui" +import { Flag } from "@opencode-ai/core/flag/flag" import type { InternalTuiPlugin } from "../../plugin/internal" -import { createMemo } from "solid-js" +import { Show, createMemo } from "solid-js" const id = "internal:sidebar-context" @@ -22,27 +23,54 @@ function View(props: { api: TuiPluginApi; session_id: string }) { return { tokens: 0, percent: null, + cacheInput: 0, + cacheNew: 0, + cacheRead: 0, + cacheWrite: 0, + cacheHitPercent: null, + cacheOutput: 0, } } const tokens = last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write const model = props.api.state.provider.find((item) => item.id === last.providerID)?.models[last.modelID] + const cacheInput = last.tokens.input + last.tokens.cache.read + last.tokens.cache.write + const cacheHitPercent = cacheInput > 0 ? ((last.tokens.cache.read / cacheInput) * 100).toFixed(1) : null return { tokens, percent: model?.limit.context ? Math.round((tokens / model.limit.context) * 100) : null, + cacheInput, + cacheNew: last.tokens.input, + cacheRead: last.tokens.cache.read, + cacheWrite: last.tokens.cache.write, + cacheHitPercent, + cacheOutput: last.tokens.output, } }) return ( - - - Context - - {state().tokens.toLocaleString()} tokens - {state().percent ?? 0}% used - {money.format(cost())} spent - + <> + + + Context + + {state().tokens.toLocaleString()} tokens + {state().percent ?? 0}% used + {money.format(cost())} spent + + + + Cache Audit + {state().cacheInput.toLocaleString()} input tokens + {state().cacheNew.toLocaleString()} new + {state().cacheRead.toLocaleString()} cache read + {state().cacheWrite.toLocaleString()} cache write + {state().cacheHitPercent}% hit rate + {state().cacheOutput.toLocaleString()} output tokens + + + ) } diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index 9765175e9e1..0d598c8a6bc 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -25,6 +25,7 @@ import { SyncEvent } from "@/sync" import { SessionEvent } from "@/v2/session-event" import { ModelV2 } from "@opencode-ai/core/model" import { ProviderV2 } from "@opencode-ai/core/provider" +import { Flag } from "@opencode-ai/core/flag/flag" import * as DateTime from "effect/DateTime" import { RuntimeFlags } from "@/effect/runtime-flags" @@ -510,6 +511,13 @@ export const layer: Layer.Layer< usage: value.usage, metadata: value.providerMetadata, }) + if (Flag.OPENCODE_EXPERIMENTAL_CACHE_AUDIT) { + const totalInputTokens = usage.tokens.input + usage.tokens.cache.read + usage.tokens.cache.write + const cacheHitPercent = totalInputTokens > 0 ? ((usage.tokens.cache.read / totalInputTokens) * 100).toFixed(1) : "0.0" + log.info( + `[CACHE] ${ctx.model.id} input=${totalInputTokens} (cache_read=${usage.tokens.cache.read} cache_write=${usage.tokens.cache.write} new=${usage.tokens.input}) hit=${cacheHitPercent}% output=${usage.tokens.output} total=${usage.tokens.total ?? 0}`, + ) + } if (!ctx.assistantMessage.summary) { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. if (flags.experimentalEventSystem) { From 4d16b2b7ce13cec4d916e9898ec7bcf1372be311 Mon Sep 17 00:00:00 2001 From: "Martin C. Richards" Date: Sun, 10 May 2026 17:54:22 +0200 Subject: [PATCH 2/5] refactor(cache): rename audit variables, add rawInputTokens, use slog --- .../tui/feature-plugins/sidebar/context.tsx | 22 +++++++++---------- packages/opencode/src/session/processor.ts | 4 ++-- packages/opencode/src/session/session.ts | 2 ++ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx index dc76001c0e9..681b05f8b0b 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx @@ -23,29 +23,29 @@ function View(props: { api: TuiPluginApi; session_id: string }) { return { tokens: 0, percent: null, - cacheInput: 0, - cacheNew: 0, + totalInputTokens: 0, + newInputTokens: 0, cacheRead: 0, cacheWrite: 0, cacheHitPercent: null, - cacheOutput: 0, + outputTokens: 0, } } const tokens = last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write const model = props.api.state.provider.find((item) => item.id === last.providerID)?.models[last.modelID] - const cacheInput = last.tokens.input + last.tokens.cache.read + last.tokens.cache.write - const cacheHitPercent = cacheInput > 0 ? ((last.tokens.cache.read / cacheInput) * 100).toFixed(1) : null + const totalInputTokens = last.tokens.input + last.tokens.cache.read + last.tokens.cache.write + const cacheHitPercent = totalInputTokens > 0 ? ((last.tokens.cache.read / totalInputTokens) * 100).toFixed(1) : null return { tokens, percent: model?.limit.context ? Math.round((tokens / model.limit.context) * 100) : null, - cacheInput, - cacheNew: last.tokens.input, + totalInputTokens, + newInputTokens: last.tokens.input, cacheRead: last.tokens.cache.read, cacheWrite: last.tokens.cache.write, cacheHitPercent, - cacheOutput: last.tokens.output, + outputTokens: last.tokens.output, } }) @@ -62,12 +62,12 @@ function View(props: { api: TuiPluginApi; session_id: string }) { Cache Audit - {state().cacheInput.toLocaleString()} input tokens - {state().cacheNew.toLocaleString()} new + {state().totalInputTokens.toLocaleString()} input tokens + {state().newInputTokens.toLocaleString()} new {state().cacheRead.toLocaleString()} cache read {state().cacheWrite.toLocaleString()} cache write {state().cacheHitPercent}% hit rate - {state().cacheOutput.toLocaleString()} output tokens + {state().outputTokens.toLocaleString()} output tokens diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index 0d598c8a6bc..f59bde6ef95 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -512,9 +512,9 @@ export const layer: Layer.Layer< metadata: value.providerMetadata, }) if (Flag.OPENCODE_EXPERIMENTAL_CACHE_AUDIT) { - const totalInputTokens = usage.tokens.input + usage.tokens.cache.read + usage.tokens.cache.write + const totalInputTokens = usage.rawInputTokens const cacheHitPercent = totalInputTokens > 0 ? ((usage.tokens.cache.read / totalInputTokens) * 100).toFixed(1) : "0.0" - log.info( + slog.info( `[CACHE] ${ctx.model.id} input=${totalInputTokens} (cache_read=${usage.tokens.cache.read} cache_write=${usage.tokens.cache.write} new=${usage.tokens.input}) hit=${cacheHitPercent}% output=${usage.tokens.output} total=${usage.tokens.total ?? 0}`, ) } diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index 85486480aa4..dbf255964cb 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -419,6 +419,7 @@ export const getUsage = (input: { model: Provider.Model; usage: LanguageModelUsa } const contextTokens = inputTokens + const rawInputTokens = inputTokens const costInfo = input.model.cost?.tiers ?.filter((item) => item.tier.type === "context" && contextTokens > item.tier.size) @@ -439,6 +440,7 @@ export const getUsage = (input: { model: Provider.Model; usage: LanguageModelUsa .toNumber(), ), tokens, + rawInputTokens, } } From 77b066b1fe24d98dc1554ea6d96901550be3c863 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 10 May 2026 21:20:13 -0400 Subject: [PATCH 3/5] Use SyncEvent service at event call sites (#26782) --- packages/opencode/src/session/prompt.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index bc58fbdf356..254b66a4f7b 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -201,7 +201,7 @@ export const layer = Layer.effect( const summary = yield* SessionSummary.Service const sys = yield* SystemPrompt.Service const llm = yield* LLM.Service - const references = yield* Reference.Service + const sync = yield* SyncEvent.Service const flags = yield* RuntimeFlags.Service const runner = Effect.fn("SessionPrompt.runner")(function* () { @@ -1579,7 +1579,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the text: nextPrompt.text.join("\n"), files: nextPrompt.files, agents: nextPrompt.agents, - references: nextPrompt.references, + }, }) } From 595d43f3c2c38c6ef2fba596b0a4279d5253f7a2 Mon Sep 17 00:00:00 2001 From: Bhagirathsinh Vaghela Date: Sun, 10 May 2026 13:49:52 +0200 Subject: [PATCH 4/5] feat(cache): add cache token audit logging and TUI sidebar behind OPENCODE_CACHE_AUDIT Co-authored-by: Bhagirathsinh Vaghela --- .../tui/feature-plugins/sidebar/context.tsx | 22 +++++++++---------- packages/opencode/src/session/processor.ts | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx index 681b05f8b0b..dc76001c0e9 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx @@ -23,29 +23,29 @@ function View(props: { api: TuiPluginApi; session_id: string }) { return { tokens: 0, percent: null, - totalInputTokens: 0, - newInputTokens: 0, + cacheInput: 0, + cacheNew: 0, cacheRead: 0, cacheWrite: 0, cacheHitPercent: null, - outputTokens: 0, + cacheOutput: 0, } } const tokens = last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write const model = props.api.state.provider.find((item) => item.id === last.providerID)?.models[last.modelID] - const totalInputTokens = last.tokens.input + last.tokens.cache.read + last.tokens.cache.write - const cacheHitPercent = totalInputTokens > 0 ? ((last.tokens.cache.read / totalInputTokens) * 100).toFixed(1) : null + const cacheInput = last.tokens.input + last.tokens.cache.read + last.tokens.cache.write + const cacheHitPercent = cacheInput > 0 ? ((last.tokens.cache.read / cacheInput) * 100).toFixed(1) : null return { tokens, percent: model?.limit.context ? Math.round((tokens / model.limit.context) * 100) : null, - totalInputTokens, - newInputTokens: last.tokens.input, + cacheInput, + cacheNew: last.tokens.input, cacheRead: last.tokens.cache.read, cacheWrite: last.tokens.cache.write, cacheHitPercent, - outputTokens: last.tokens.output, + cacheOutput: last.tokens.output, } }) @@ -62,12 +62,12 @@ function View(props: { api: TuiPluginApi; session_id: string }) { Cache Audit - {state().totalInputTokens.toLocaleString()} input tokens - {state().newInputTokens.toLocaleString()} new + {state().cacheInput.toLocaleString()} input tokens + {state().cacheNew.toLocaleString()} new {state().cacheRead.toLocaleString()} cache read {state().cacheWrite.toLocaleString()} cache write {state().cacheHitPercent}% hit rate - {state().outputTokens.toLocaleString()} output tokens + {state().cacheOutput.toLocaleString()} output tokens diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index f59bde6ef95..1bbf860172e 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -512,7 +512,7 @@ export const layer: Layer.Layer< metadata: value.providerMetadata, }) if (Flag.OPENCODE_EXPERIMENTAL_CACHE_AUDIT) { - const totalInputTokens = usage.rawInputTokens + const totalInputTokens = usage.tokens.input + usage.tokens.cache.read + usage.tokens.cache.write const cacheHitPercent = totalInputTokens > 0 ? ((usage.tokens.cache.read / totalInputTokens) * 100).toFixed(1) : "0.0" slog.info( `[CACHE] ${ctx.model.id} input=${totalInputTokens} (cache_read=${usage.tokens.cache.read} cache_write=${usage.tokens.cache.write} new=${usage.tokens.input}) hit=${cacheHitPercent}% output=${usage.tokens.output} total=${usage.tokens.total ?? 0}`, From f6b1ccd65a2480f036098c415af6746062752e92 Mon Sep 17 00:00:00 2001 From: "Martin C. Richards" Date: Tue, 12 May 2026 08:12:58 +0200 Subject: [PATCH 5/5] fix(session): restore missing Reference.Service after rebase A conflict during the rebase incorrectly removed the const references = yield* Reference.Service line from the prompt.ts layer generator, causing 7 typecheck errors. Adding it back restores the Reference.Service dependency needed by resolvePromptParts. --- packages/opencode/src/session/prompt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 254b66a4f7b..e4c612b7384 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -201,7 +201,7 @@ export const layer = Layer.effect( const summary = yield* SessionSummary.Service const sys = yield* SystemPrompt.Service const llm = yield* LLM.Service - + const references = yield* Reference.Service const sync = yield* SyncEvent.Service const flags = yield* RuntimeFlags.Service const runner = Effect.fn("SessionPrompt.runner")(function* () {