feat(usage): report OpenCode spend and tokens from its local store - #12254
jaikhuranna wants to merge 1 commit into
Conversation
Usage covered Claude, Codex, and Grok transcripts but skipped OpenCode entirely: `UsageProviderKind` had no `opencode` entry and nothing read OpenCode's history. An OpenCode user saw no spend or tokens beside the other providers. This adds `opencode` to the usage contract (v6, additive - merge compatibility stays at v4) and reads assistant-message tokens and cost from OpenCode's own SQLite store at `$XDG_DATA_HOME/opencode/opencode.db`. OpenCode's token counts are disjoint, so reasoning folds into output per the shared invariant, and a zero reported cost falls through to the rate table. The store is re-read wholesale under the existing `(size, mtime)` scan cache - there is no transcript byte stream to resume from. Worked by GLM (glm-flash-latest) via OpenCode.
Evidence: OpenCode usage on a real storeBoth screenshots come from the same machine and the same Before — the stable install's Usage page: $0.00, no activity. The user's history is entirely OpenCode sessions, which the usage contract ignored. After — this branch: OpenCode appears with spend, tokens, cache savings, and a per-model breakdown, read from the same store (90-day window). 31 sessions · $30.27 · 254M tokens (231M cached input), cache savings $7.32. Models without rates (muse-spark-*, nvidia/nemotron) render as Unpriced and are excluded from cost totals. |
Mobile verificationThe phone app (T3 Code Dev, this branch, Android emulator) paired to a server running this branch. The Usage widget renders the OpenCode series from the v6 contract buckets with no provider-specific mobile code — it is driven entirely by the shared OpenCode — $29.79 · 100.0% of cost · 245M tokens (90-day window), confirming the contract addition covers the mobile surface as-is. |



What changed
The Usage report covered Claude, Codex, and Grok Build but not OpenCode, so OpenCode-only users saw no spend or tokens. This adds OpenCode as a usage source following the same playbook the grok addition (v5) used:
UsageProviderKindgains"opencode".USAGE_CONTRACT_VERSIONbumps to 6;USAGE_MERGE_COMPATIBLE_SINCEstays at 4 because the change is additive.opencodeUsageStore.tsreads assistant rows from OpenCode's own SQLite store ($XDG_DATA_HOME/opencode/opencode.db, openedreadOnly) through a pureparseOpenCodeMessageinusageTranscripts.ts. The store rides the existing(size, mtime)scan cache wholesale — there is no transcript byte stream to resume from.inputexcludes cache reads,outputexcludes reasoning), so reasoning folds intooutputTokensper the contract's subset invariant; a zero reported cost falls through to the rate table, matching Claude Code subscription turns.Deliberately deferred to keep this thin: the per-thread context-window meter, cursor/antigravity sources, and reading
session_message(v2 projection, empty in the wild;dedupeKeyis the message id, so dual-reading later is additive-safe).521 insertions across 14 files (~250 production, ~270 tests), verified with the touched suites: parser, store round-trip, one integrated service scan, scan-cache round-trip, and chart bands.
Worked by GLM (glm-flash-latest) via OpenCode.