fix: context budget guard + no-window warning (#347) - #350
Conversation
A model with no declared context window (limit.context=0) disabled every percentage threshold in the plugin, so requests could grow past the backend's real window and be rejected with HTTP 400 - which opencode swallows as an empty exit-0 response (billion-context#317). - enforceContextBudget: deterministic prune-to-fit guard in messages.transform (truncate largest old tool outputs, then clear oldest) using the model window or an absolute maxContextLimit, reserving completionReserveTokens (default 32768) for completion - one-time WARN when the model reports no context window and the catalog has no entry, with actionable config guidance - new config: compress.completionReserveTokens (schema + docs) - 14 new unit tests
📦 Built Plugin ArtifactBranch: Option A — Install from npm PR tag (recommended)opencode plugin opencode-acp@pr-350 --globalEach push to this PR publishes a new version under the Option B — Install from GitHubopencode plugin "github:ranxianglei/opencode-acp#2026-08-28_context-budget-guard" --globalOption C — Download artifact
tar xzf opencode-acp-pr350.tgz
cp -r package/dist ~/.cache/opencode/packages/opencode-acp@latest/node_modules/opencode-acp/dist
This comment is automatically updated on each push. |
[bot] Picked up PR #347 (
Now running the mandatory dual-agent review (source per AGENTS.md §5.3, tests per §5.6) plus a few consistency checks (devlog commit hash vs branch tip, PR/CI state on GitHub). Full review verdict will follow as a second comment. |
…ard, docs Dual-agent review of the context budget guard found: - BLOCKER: compress.completionReserveTokens missing from VALID_CONFIG_KEYS → spurious "Unknown keys" toast for users setting the documented key - NaN reserve hazard: non-numeric config value made budget NaN, disabling every early-exit (guard would truncate all candidates) - estimateWireTokens undercount: anchored on last assistant by role while getCurrentTokenUsage anchors on last assistant WITH token data; also dropped the system prompt estimate when getCurrentTokenUsage fell back to content estimation - truncation could GROW content just over the 4000-char threshold (prefix/suffix overlap + marker line) - docs (schema, EN/CN, warning text) claimed an absolute compress.maxContextLimit enables the guard; it deliberately does not Tests: +9 (23 in enforce-budget.test.ts, 9 new in config-validation.test.ts); full suite 1052/1052.
[bot] Review verdict: REQUEST-CHANGES → fixed & pushed. Two independent agent reviews (source + tests) of the PR branch found 1 blocker and several real bugs. All fixed, pushed as two commits on BLOCKER (fixed): Other bugs found & fixed:
Left as documented known limitations (low impact, noted in WORKLOG): the one-time no-window warning can fire one turn early for a catalog-unknown model that declares its limit (transform ordering); first-user-message protection is index-0-only (matches Tests: +9 new (23 total in Verification (all on the pushed branch):
No version bump (content branch). PR is ready — merge is a human-only operation per project rules, so I'll leave that to you. |
What
Fixes #347 (billion-context#317): sessions whose input grew past the backend's real context window were rejected with HTTP 400, which opencode swallows as an empty exit-0 response — permanently stuck session, no error surface.
Root cause chain (full analysis in #317): a custom model with no
limitentry →modelContextLimitstays undefined → every percentage threshold (min/max/emergency, GC) is disabled → only advisory nudges remain → input grows unbounded until the backend 400s (production case: 230,527 input + 32,000 completion > 262,144 window).Changes
Deterministic prune-to-fit guard (
lib/messages/enforce-budget.ts, new): inmessages.transform, after the existing GC truncation, if the estimated request exceedsmodelContextLimit - completionReserveTokens, old compressible tool outputs are truncated (prefix+suffix, same marker astruncateLargeToolOutputsfor idempotency) and, if still over, cleared to the standard placeholder. Protections: first user message, last 3 messages,protectedTools, compress-tool outputs (summaries).The guard enforces ONLY the model-reported window. An absolute
compress.maxContextLimitis deliberately not a fallback — it is a soft nudge threshold, not the backend's real limit; pruning to it would destroy context the backend would accept (this regressede2e-blocks-nudgesduring development: the guard pruned the one large tool output below the recommendation floor →nothingToCompress→ no nudge suffix).Loud one-time WARN (
lib/hooks.ts): when the model reports no context window and the catalog has no entry, log actionable guidance once per session (set the modellimitin opencode.json — this both enables the guard and fixes the 32,000 max_tokens fallback — or set an absolutecompress.maxContextLimitfor proactive nudges).New config
compress.completionReserveTokens(default 32768, schema + EN/CN docs): reserved for the completion; covers opencode's 32,000max_tokensfallback whenlimit.outputis 0/unknown.Transient state
noContextLimitWarned(not persisted; default/reset increateSessionState/resetSessionState).Pre-flight
npm run typecheck— cleannpm test— 1043/1043 pass (14 new tests intests/enforce-budget.test.ts)npm run build— successtests/e2e-blocks-nudges.test.ts10/10Notes
lastCompaction: 0in the stuck sessions was a red herring (it only tracks opencode's native compaction, disabled bycompaction.auto: false) — see release: v1.14.21 — /acp command fixes and completion (permission gate, export, help) #317.maxTokensbody-key quirk) are tracked separately; body drafted in release: v1.14.21 — /acp command fixes and completion (permission gate, export, help) #317 for a human to file (PAT has no access to that repo).