fix: reconcile modelContextLimit on model switch (#312) - #314
Merged
Conversation
Within one LLM request the host fires experimental.chat.messages.transform BEFORE experimental.chat.system.transform (sst/opencode: session/prompt.ts triggers messages.transform, then llm/request.ts triggers system.transform inside handle.process). state.modelContextLimit is written only by the system hook, so the first request after a model switch computed every percentage threshold against the PREVIOUS model's window — e.g. a 50% emergencyThresholdPercent fired at ~26% real usage after switching 200K -> 1M (issue #312), and 1M -> 200K missed a real emergency. Fix: SessionStateRegistry keeps a `${providerID}/${modelID}` -> context limit catalog. The system hook records every observed model's limit into it (before the session-state guard, so it works even without state); the messages hook reconciles state.modelContextLimit from the catalog entry for the model named on the request's user message; plugin init seeds the catalog best-effort from GET /config/providers. Unknown models keep the previous fallback (stale limit for one request, corrected by the system hook later in the same request). Tests: 7 new regression tests in tests/model-switch-limits.test.ts; full suite 988 pass, 0 fail.
📦 Built Plugin ArtifactBranch: Option A — Install from npm PR tag (recommended)opencode plugin opencode-acp@pr-314 --globalEach push to this PR publishes a new version under the Option B — Install from GitHubopencode plugin "github:ranxianglei/opencode-acp#2026-08-16_model-switch-limit-catalog" --globalOption C — Download artifact
tar xzf opencode-acp-pr314.tgz
cp -r package/dist ~/.cache/opencode/packages/opencode-acp@latest/node_modules/opencode-acp/dist
This comment is automatically updated on each push. |
ranxianglei
pushed a commit
that referenced
this pull request
Aug 16, 2026
Two follow-ups from PR review:
1. index.ts — hydration was fire-and-forget with .catch(() => {}): a silent
degrade (host without /config/providers, failed fetch, empty catalog) was
invisible. Now logs: info with entry count on success, warn on zero
entries / failure. Still fire-and-forget, never blocks init.
2. Extract the model-limit catalog into lib/state/model-limits.ts
(createModelLimitCatalog). SessionStateRegistry delegates to it; the
test registry stub composes the SAME factory instead of hand-rolling a
copy that could drift. Public registry API unchanged.
Verified: tsc clean, 988/988 tests pass, prettier clean on touched files
(unrelated pre-existing format dirt in state.ts/registry-stub.ts left
as-is to keep the diff surgical).
ranxianglei
added a commit
that referenced
this pull request
Aug 17, 2026
Completes the #314 fix with #313's fail-safe as the fallback path. #314 (merged) reconciles state.modelContextLimit from the model-limit catalog when the request's user message names a known model. When the catalog MISSES (fresh instance + failed hydration + never-used model), the stale limit survived and every percentage threshold still ran against the previous model's window — the exact #312 false positive, narrowed to a rare corner but not eliminated. This PR records the identity pair (modelProviderID/modelID) alongside the limit whenever the system hook writes it, and on a catalog miss the messages hook compares the request's model against that identity: - match → keep the limit (no needless blindness when the catalog simply lacks an entry for the CURRENT model) - mismatch→ invalidate (limit = undefined); consumers already tolerate undefined (fresh sessions run with it until the first system.transform), and this same request's system.transform refreshes the pair anyway - legacy persisted states carry no identity → treated as stale (one blind turn per upgraded session, same as #313) The identity pair is persisted/restored together with the limit. Tests: legacy-state invalidation, identity-mismatch invalidation, same-identity keep (emergency still fires at 130%), system-hook identity recording, persistence round-trip. 991/991 pass, tsc clean. Co-authored-by: ranxianglei <ranxianglei@users.noreply.github.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem (issue #312)
切换模型导致上下文水平计算错误:配置了 50% 紧急压缩阈值,但从 200K 切到 1M 模型后,在 26% 实际用量时就触发了紧急告警。
Root cause(host 源码证实):一次 LLM 请求内 opencode 先触发
experimental.chat.messages.transform(session/prompt.ts:1255),后触发experimental.chat.system.transform(llm/request.ts:69,在handle.process内部)。而state.modelContextLimit只有 system hook 写入 —— 切换模型后的第一个请求,所有百分比阈值仍按旧模型窗口计算:Fix
SessionStateRegistry新增${providerID}/${modelID}→ context limit 目录:lib/state/state.tsrecordModelLimit/resolveModelLimit/hydrateModelLimitsFromClient(best-effort,从不抛出)lib/hooks.tsmodel.limit.context记入目录(在 session-state 守卫之前);messages hook 在getOrCreate后按请求 user message 上的模型名同步state.modelContextLimitindex.tsGET /config/providers预填目录未知模型保持原回退路径(旧值一个请求,system hook 同请求内刷新)—— 修复纯增量,无 schema/持久化/API 变更。热路径开销:每次 messages.transform 一次
Map.get。Verification
npx tsc --noEmit✅npm run build✅tests/model-switch-limits.test.ts7 项回归:/config/providers预填 / throwing client 容错node --import tsx --test tests/*.test.ts:988 pass, 0 failDevlog:
devlog/2026-08-16_model-switch-limit-catalog/(REQ.md + WORKLOG.md)Fixes #312