Skip to content

feat(server): show OpenCode session context window usage - #7072

Closed
Terrigible wants to merge 13 commits into
pingdotgg:mainfrom
Terrigible:feat/opencode-context-window-usage
Closed

Terrigible wants to merge 13 commits into
pingdotgg:mainfrom
Terrigible:feat/opencode-context-window-usage

Conversation

@Terrigible

@Terrigible Terrigible commented Aug 15, 2026 •

Copy link
Copy Markdown

What Changed

  • OpenCode sessions now show a context usage meter in the thread, just like Codex and Claude sessions already do.

Why

  • OpenCode sessions show no context usage in the thread timeline, unlike Codex and Claude, even though OpenCode reports cumulative token counters and an auto-compaction config.

Caveats

  • No "Total processed" figure for OpenCode. OpenCode only exposes cumulative token counts per assistant message, not a reliable session-wide total, so the tooltip's "Total processed" row stays hidden for OpenCode sessions. The meter shows only current-window usage, whereas Codex and Claude sessions also show the session total.

UI Changes

N/A — server-side only; the meter is already supported.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (N/A)
  • I included a video for animation/interaction changes (N/A)

Implementation Details

  • Build a ThreadTokenUsageSnapshot from OpenCode's per-message token breakdown (input, output, reasoning, cache read/write).
  • OpenCode honors compaction.auto from the project's opencode.json; the flag defaults to true when the config can't be read.
  • Read the model context window and auto-compaction flag from the config endpoints, scoped to the session directory (an externally-launched server reports the project's config, not its own cwd).
  • Re-probe per session; each session's first token-bearing message refetches config, nothing is cached across sessions.
  • Clamp usedTokens to the context window so the meter never reads past 100% when OpenCode's cumulative counts momentarily overshoot around auto-compaction.
  • Sanitize counters — anything that isn't a finite non-negative number becomes 0.
  • Dedup re-broadcast message.updated events so repeated snapshots don't persist duplicate context-window.updated activities.
  • Bound and best-effort metadata probes (OPENCODE_CONTEXT_METADATA_PROBE_TIMEOUT_MS); a wedged fetch degrades the meter to token counts without a percentage rather than hanging session start.

Created by deepseek-v4-flash via opencode.


Note

Medium Risk
Changes OpenCode event handling and concurrent config probing on the hot session path; failures are bounded but incorrect metadata could misstate compaction or context limits until probes succeed.

Overview
OpenCode threads can now drive the same context usage meter as Codex and Claude by turning cumulative assistant message.updated token counters into thread.token-usage.updated snapshots.

OpenCodeAdapter adds helpers to sanitize token fields, sum usage, build a ThreadTokenUsageSnapshot (optional maxTokens from the model catalog, compactsAutomatically from compaction.auto), and dedupe identical re-broadcasts. Metadata is loaded once per session via bounded 2s probes to config.providers and config.get (session directory–scoped); failures degrade to counts without a percentage cap rather than blocking the event pump.

Usage emission runs on a per-session FIFO background worker so slow or hung config probes do not stall sequential session events. Zero-token broadcasts skip probes; interrupted turns still publish usage on the correct turn id. OpenCodeAdapter.test gains a config mock and broad coverage for clamping, malformed counters, probe timeouts, pump ordering, dedup, emit failures, and session stop while probing.

Reviewed by Cursor Bugbot for commit 6939051. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add OpenCode session context-window usage reporting via background worker

  • Adds token-usage snapshot construction in OpenCodeAdapter that sanitizes malformed counters, sums input/output/reasoning/cache tokens, and includes the model context limit when available
  • Introduces runContextWindowUsageWorker, a per-session FIFO background worker that loads model metadata and emits thread.token-usage.updated events without blocking the sequential event pump
  • loadContextUsageMetadata probes config.providers and config.get in parallel with a 2-second timeout; failures and timeouts leave defaults and are not retried
  • emitContextWindowUsage skips zero-token payloads, suppresses duplicate snapshots, and only marks a snapshot as emitted after the event succeeds
  • Risk: emitContextWindowUsage now forks a background worker per session on first positive-token payload; session teardown must propagate interruption to stop it, as verified by the stop-during-usage-probe test in OpenCodeAdapter.test.ts

Macroscope summarized 6939051.

@coderabbitai

coderabbitai Bot commented Aug 15, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: 8dc09198-c739-4d62-9750-479650fdb4d2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Warning

Your free Security trial is over. An organization admin can upgrade to Advanced for continuous pull request security review or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 15, 2026
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Aug 15, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This adds OpenCode context-window usage as a new user-facing capability and changes the production event path with per-session metadata probes and background FIFO workers. The asynchronous behavior, fallback metadata defaults, and session-lifecycle handling warrant human review.

Notes:

  • No code objects were reviewed. Approvability was decided on eligibility alone.

You can add or adjust custom eligibility rules. Learn more.

@Terrigible

Copy link
Copy Markdown
Author

Addressed the review findings from Cursor Bugbot and Macroscope on the metadata probe timeout.

Root cause: the shared Effect.timeoutOption wrapped the whole Effect.all of the config.providers + config.get probes, and the session was marked modelContextWindowCacheLoaded before the probes ran. If config.get was slow, the combined timeout dropped a completed config.providers catalog — and since the cache was already marked loaded, maxTokens never recovered for the rest of the session (meter stuck on raw counts without a percentage).

Fix: bound each probe with its own Effect.timeoutOption instead of one shared timeout, so a wedged config.get no longer discards the completed model catalog. Added a regression test that hangs config.get past the probe timeout and asserts maxTokens still lands from the catalog (verified it fails on the previous code).

Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts Outdated
@Terrigible Terrigible changed the title feat(server): report OpenCode session context window usage feat(server): show OpenCode session context window usage Aug 15, 2026
@Terrigible
Terrigible force-pushed the feat/opencode-context-window-usage branch from 9a6daad to 5055ca4 Compare August 27, 2026 02:08
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts Outdated
@Terrigible

Copy link
Copy Markdown
Author

Addressed the Bugbot finding about zero-token updates stalling the event pump (commit 06a727ec9).

Root cause: emitContextWindowUsage awaited loadContextUsageMetadata before checking whether anything would be emitted, and OpenCode broadcasts freshly-created assistant messages with an all-zeros tokens object — so those broadcasts queued behind the config probes even though buildOpenCodeContextWindowUsage returns undefined for any zero-total snapshot and nothing could ever be emitted. Since the event pump is sequential, part deltas and turn completion waited with it.

Fix: bail out of emitContextWindowUsage when the token total is 0 before touching the probes (openCodeTokenTotal uses the same sanitizer as the snapshot builder, so it can't diverge). Non-zero counts keep the existing one-shot probe behavior. Added a regression test that hangs both config endpoints and feeds a zero-token broadcast followed by another event — without the fix the pump stalls and the test times out; with it, both events flow and configProbeDirectories confirms no round-trip was paid.

Rebase of feat/opencode-context-window-usage onto upstream/main (2daff8c).
Squashed 14 commits: context window reporting, token sanitization, probe
timeouts, directory-scoped config probes, per-probe timeout handling,
auto-compaction flag, and zero-token broadcast pump fix.

Original commits:
06a727ec9 fix(server): don't stall OpenCode event pump on zero-token broadcasts
5055ca407 fix(server): read auto-compaction setting when catalog probe times out
9a4646a96 fix(server): keep OpenCode model catalog when a config probe times out
5f086b48b docs(server): trim OpenCode context usage comments
0855e3f5c fix(server): narrow OpenCode token counter guard for TS 6.0
f8192359f fix(server): scope OpenCode context usage probes to the session directory
cac12a5f9 fix(server): tolerate malformed OpenCode token breakdowns
e1481e234 fix(server): sanitize OpenCode token counts before reporting context usage
f11a03df1 fix(server): tighten OpenCode context usage metadata probe timeout
157eda4bb fix(server): harden OpenCode context usage reporting
3d7f385b1 test(server): drop redundant OpenCode auto-compaction default test
9b5745485 feat(server): read auto-compaction from OpenCode config
ec429fb3a fix(server): drop total processed tokens from OpenCode context usage
791219301 feat(server): report OpenCode session context window usage
@Terrigible
Terrigible force-pushed the feat/opencode-context-window-usage branch from 06a727e to 09a9319 Compare August 30, 2026 16:18
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts
- require integer context windows before emitting maxTokens
  (fractional limits violated PositiveInt and could persist)
- keep emitting token usage even when display output is suppressed
  after an interrupt, otherwise the meter underreports until the
  next completed message
@Terrigible

Copy link
Copy Markdown
Author

Addressed the two new review findings on 09a9319 (now 537322a):

Macroscope — fractional maxTokens (Medium, OpenCodeAdapter.ts:248): modelContextWindow like 200000.5 was accepted and persisted as maxTokens, violating ThreadTokenUsageSnapshot.maxTokens (PositiveInt). Fixed by requiring Number.isInteger both when caching the provider limit (loadContextUsageMetadata) and when building the snapshot (buildOpenCodeContextWindowUsage); non-integers are now treated as unknown (no maxTokens, percentage degraded).

Cursor Bugbot — interrupt skips usage (Low, L2214/L2152): suppressInterruptedParentOutput sits before the switch and was dropping the assistant message.updated that carries the cumulative token counts for an aborted turn, so the meter underreported until the next completed message. Fixed by exempting token usage from the suppression — when that guard fires, we still call emitContextWindowUsage for an assistant message.updated with tokens (zero-total early-return and dedup still apply via the existing helper). Verified 92 tests pass and fractional limits no longer emit.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts Outdated
The suppress-path emit used activeTurnId, which is empty after an
interrupt (turnless, breaking revert) and is already the next turn
once awaitingBusyAfterInterruption is set (misattributing aborted
counts to the new turn). Use interruptedTurnId / pendingIdle turn
instead, and avoid stamping on the new turn when no aborted id is
known.
@Terrigible

Copy link
Copy Markdown
Author

Addressed Bugbot Medium on 537322a — Interrupt usage stamped with wrong turn (now 99de889).

Previous fix exempted token usage from suppressInterruptedParentOutput but stamped with turnId (activeTurnId). After an interrupt activeTurnId is empty → turnless snapshot (revert can't restore), and once a follow-up sets awaitingBusyAfterInterruption the captured turnId is already the next turn → aborted counts misattributed.

Fix: in the suppress branch, resolve the usage turn as interruptedTurnId ?? pendingIdleReconciliation.turnId, falling back to turnId only when not awaiting the next busy. When awaiting busy and no aborted id is known, skip the emit entirely to avoid stamping on the new turn; otherwise emit on the aborted turn. Turnless fallback kept only for the non-awaiting case where no better id exists. 92 tests pass.

- trim verbose block comments to 1-2 liners
- unify token counter helper (sanitizeCounter)
- collapse nested Option/Exit checks into single guard
- shorten session context field docs
- keep all behavior and tests passing
…xt-window-usage

# Conflicts:
#	apps/server/src/provider/Layers/OpenCodeAdapter.ts
# Conflicts:
#	packages/client-runtime/src/rpc/session.test.ts
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts Outdated
The first token-bearing message.updated awaited the config.providers/config.get probes inline, stalling the sequential event pump for up to the probe timeout and delaying text deltas, approvals, and idle/completion events queued behind it. Emit the usage snapshot from cached metadata instead, running the probe in a session-scoped background fiber.
@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Sep 4, 2026
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts Outdated

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts
Queue token-bearing inputs for a per-session FIFO worker instead of forking one background fiber per message. A slow metadata probe could otherwise let the first message's stale counters overwrite newer usage once the probe completed, bypassing dedup because the late limit differed.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts Outdated
Handle probe/emit failures per worker iteration instead of letting one bad snapshot end the runContextWindowUsageWorker loop. A dead worker left modelContextWindowQueue set with nothing reading it, silencing the meter for the rest of the session. Only interruption still ends the loop on session teardown.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d3a0df1. Configure here.

Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts
Record lastEmittedContextWindowUsage after the emit succeeds instead of before. A failed emit no longer marks the snapshot published, so an identical re-broadcast retries instead of deduping against a snapshot the meter never showed. Also propagate any interruption (pure or mixed) out of the usage worker so session teardown never waits on it, and cover stopping while the probe is wedged.
The upstream merge commits ran an older formatter over every staged file, leaving whitespace-only deltas in 31 files this branch never otherwise touched. Restore them to main's content so the PR shows only the OpenCode context-usage changes.
The re-merge resolution had reflowed Array.from onto multiple lines; main already carries the substantive sourceEvents lines, so restore its exact content.
@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Sep 5, 2026
# Conflicts:
#	apps/server/src/provider/Layers/OpenCodeAdapter.test.ts
#	apps/server/src/provider/Layers/OpenCodeAdapter.ts
@shivamhwp

Copy link
Copy Markdown
Collaborator

Note: GPT-6 on behalf of shivam (@shivamhwp).

The new metadata tests in OpenCodeAdapter.test.ts still advance the clock repeatedly in 100 ms steps to schedule the event pump. Replace the ordering case with an explicit catalog-response gate: queue both usage messages and a following session.updated, release the catalog when that following event arrives, then collect the two usage events. This makes event delivery and FIFO ordering the synchronization points. For cases specifically checking the probe timeout, wait for probe entry before advancing the clock once.

Use an openrouter catalog with slash-containing model keys such as z-ai/glm-5.3-flash and poolside/laguna-s-2.1:free in that case; it also covers #8388's model lookup path.

The added appendOpenCodeAssistantTextDelta test import is unused. Remove that import and the corresponding export change.

@juliusmarminge

Copy link
Copy Markdown
Member

Thanks for the PR. We're not taking changes to the orchestration and provider layers right now: that part of the server is being rewritten for V2, and merging into the current code would either conflict with or be thrown away by that work.

Closing for now. If this is still an issue once V2 lands, please reopen (or open a fresh PR against the new code) and we'll take a proper look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants