Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/src/flag/flag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -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 (
<box>
<text fg={theme().text}>
<b>Context</b>
</text>
<text fg={theme().textMuted}>{state().tokens.toLocaleString()} tokens</text>
<text fg={theme().textMuted}>{state().percent ?? 0}% used</text>
<text fg={theme().textMuted}>{money.format(cost())} spent</text>
</box>
<>
<box>
<text fg={theme().text}>
<b>Context</b>
</text>
<text fg={theme().textMuted}>{state().tokens.toLocaleString()} tokens</text>
<text fg={theme().textMuted}>{state().percent ?? 0}% used</text>
<text fg={theme().textMuted}>{money.format(cost())} spent</text>
</box>
<Show when={Flag.OPENCODE_EXPERIMENTAL_CACHE_AUDIT && state().cacheHitPercent != null}>
<box>
<text fg={theme().text}><b>Cache Audit</b></text>
<text fg={theme().textMuted}>{state().cacheInput.toLocaleString()} input tokens</text>
<text fg={theme().textMuted}> {state().cacheNew.toLocaleString()} new</text>
<text fg={theme().textMuted}> {state().cacheRead.toLocaleString()} cache read</text>
<text fg={theme().textMuted}> {state().cacheWrite.toLocaleString()} cache write</text>
<text fg={theme().textMuted}>{state().cacheHitPercent}% hit rate</text>
<text fg={theme().textMuted}>{state().cacheOutput.toLocaleString()} output tokens</text>
</box>
</Show>
</>
)
}

Expand Down
8 changes: 8 additions & 0 deletions packages/opencode/src/session/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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"
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}`,
)
}
if (!ctx.assistantMessage.summary) {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (flags.experimentalEventSystem) {
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,

},
})
}
Expand Down
2 changes: 2 additions & 0 deletions packages/opencode/src/session/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -439,6 +440,7 @@ export const getUsage = (input: { model: Provider.Model; usage: LanguageModelUsa
.toNumber(),
),
tokens,
rawInputTokens,
}
}

Expand Down
Loading