fix(opencode): show reported context usage for OpenRouter threads#22761

draft7
main
fix/issue-8388
opened8d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 5d ago

Problem

OpenCode 1.x reports step tokens but never supplied the composer context snapshot. With the legacy context indicator enabled, OpenRouter threads showed no meter after completed turns. Fixes #8388.

Change

The adapter now publishes the last owned step as context usage at turn completion, while preserving cumulative turn billing. Model limits load in the background; a pending or unavailable catalog yields an unknown ceiling without delaying completion. No renderer or contract changes.

Scope and approval

Fixes #8388 GitCafe: https://git.cafe/pingdotgg/t3code/issues/8388 Triaged bug via the automated issue flow.

Visuals are corroboration at 1280×800, light, Chrome 154/Linux, DPR 2. Different fresh threads and popover states are labelled; no pixel-equivalence claim. No render path changed, so the server-only matrix requires runtime transcripts and scoped tests rather than frontend diff sheets.

Validation: 32 adapter tests pass, including last-step selection and completion with an unresolved catalog; both new cases fail with the base adapter. 22 server replay tests pass. Scoped typechecks and changed-file lint pass. Committed fixture runs against the real OpenCode CLI. Final proof audit: sufficient; fresh final-head transcript matches the persisted turn and durable usage event.

Verification

Reproduced on 7ffc3bb7e8 with the real web dev server and OpenCode 1.18.18 pointed at a credential-free local OpenRouter-shaped SSE endpoint. Two OpenCode turns had cumulative usage but no context snapshot. Switching the same thread to Codex showed the meter. On adapter head e85525de29, OpenCode persisted used 12032 / max 128000 and displayed 9.4%. A fresh app turn at final head 70a0684ec4, using the committed reproducer, confirms the same result.

Evidence:

Independent final reviews (Codex gpt-6.1-sol and Claude Opus 5.5) both returned MERGE on the final head; 26 CI checks pass. Nonblocking review note: manual compaction can leave the meter stale until the next normal turn; compaction telemetry is outside this issue’s message-usage fix. The PR remains draft for dispatcher handoff.

Unverified: real OpenRouter account/network; reporter's Windows desktop/Electron; Android and iOS. Historical turns are not backfilled. Neighboring #22637 concerns overestimation without reported usage and remains separate.

Models/harnesses: Codex gpt-6.1-sol and Claude Opus 5.5/Fable 5.1 through T3 Code delegated tasks.

Evidence

Superseded: the evidence links above (fixture matrix, final evidence index, before/after runtime JSON, screenshots, proof audit) predate a later code change. The runtime evidence below was captured on the current head, 0f40a81e0c, and replaces them.

Before is the merge-base 8acbefefc5; after is the head 0f40a81e0c. Each side ran the real T3 server with isolated state and the real OpenCode 1.18.18 CLI, which uses the v1 adapter this PR changes. OpenCode called a local OpenRouter-shaped stub, so every usage payload is SYNTHETIC. Both sides ran the same scripted WebSocket sequence. Each value below agrees across the stored provider-turn.updated event, the provider-turn projection row, and the frames the client receives over WebSocket.

RowBeforeAfter
R1: OpenRouter model, one stepno tokenUsage; billing 12000 in / 1000 cached / 32 outtokenUsage used 12032, max 128000; billing unchanged
R2: OpenRouter, two steps (tool call, then text)no tokenUsage; billing 21500 / 1500 / 72context = last step only (used 12032, max 128000); billing still the cumulative 21500 / 1500 / 72
R3a: provider returns an error before any steprun failed, no tokenUsage, billing unavailablesame, no crash
R3b: step finishes but reports no usageno tokenUsage; billing all zerostokenUsage present and all zeros (meter shows 0%); run completes, no crash
R4: non-OpenRouter model (acme/stub-a)no tokenUsage; billing 12000 / 1000 / 32tokenUsage used 12032, max 64000; billing unchanged

The tokenUsage change is not limited to OpenRouter: every OpenCode 1.x model now reports context usage (R4). Billing (turnTokenUsage) is byte-identical before and after on every row. Context usage is published only on the turn's final update. Events from a turn still in progress carry none on either side.

  • Matrix: matrix.md
  • Transcripts: before · after · summary
  • Drivers: capture.mjs sha256 67a263975d72db0294bdc1fb551a22c7f791a7fa1fc3fec98523d9dd57347046 · stub.ts sha256 abc10a87f70d66efeea2487975458dfbff7f471fd109ab6a98db52bf7f4a7f71 · extract.sh sha256 32937194aeada94ed1fe0d2d17b0e771f2e046cb323560b332c0decec12c5ded
  • Proof audit: audit2.md (SUFFICIENT, with an independent rerun of R2 on the head)

Not evidenced at runtime:

  • maxTokens = null while the model catalog is pending or failed. The adapter test's pending variant covers this.
  • OpenCode 2.x: its adapter is untouched by this PR.
  • UI screenshots: none were captured this round. The client WebSocket frames carry the values the meter reads.
Verification log
  • 2026-10-22, fs-12, evidence-only round 2 on head 0f40a81e0c82bee35ff625b7b965524c45e88e14, base 8acbefefc51b4d281dcd04108c82baa1ab902b0e.
  • Proof audit pass 1, read-only: derived the matrix from the diff. It corrected two expectations. R4 is not "unchanged": the context meter now appears for every OpenCode 1.x model. R3 splits into zero steps (no tokenUsage) and a step without usage (all-zero tokenUsage).
  • Capture: before/after servers with isolated base-dirs, the same driver, and a synthetic stub. All five rows ran on both sides and the after side matched the expected values.
  • Proof audit pass 2: driver sha256 checked against the uploads and the local copies, and every row checked against the code at each SHA. R2 was rerun independently on the head from a fresh base-dir with identical values. Verdict: SUFFICIENT.
  • Models/harness: Claude Opus 5.5 through T3 Code delegated tasks (Codex unavailable).

Checks

22 successful · 5 skipped
All checks passed
1c42fe1

Added 1 commit

  • test(opencode): takeover tests for OpenRouter context usage (#8388)8e668a7
Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
CodexLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiB✅
CodexLive turn WebSocket decoded21.6 KiB21.6 KiB0 B (0.0%)24.4 KiB✅
CodexLive turn messages220 (0.0%)8✅
ClaudeTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
ClaudeLive turn WebSocket wire1.3 KiB1.3 KiB0 B (0.0%)2.0 KiB✅
ClaudeLive turn WebSocket decoded21.9 KiB21.9 KiB0 B (0.0%)24.4 KiB✅
ClaudeLive turn messages220 (0.0%)8✅

Baseline: da8cc59 · PR result: b9d268a · Source CI: failure

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Added 6 commits

  • fix(opencode): OpenRouter turns report their context usage to the composer212b7b6
  • fix(opencode): finish turns without waiting for context limitse85525d
  • test(opencode): add a credential-free OpenRouter usage fixture for #83888f3b87d
  • test(opencode): make usage fixture setup portable70a0684
  • Merge branch 'main' into fix/issue-83880f40a81
  • chore: sync OpenCode usage fix with main1c42fe1