You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
compaction.reserved is silently ignored for any model whose limit lacks an input field. This was reported in #13980, which was closed by the stale bot without being fixed — the current code still ignores reserved on this code path. Filing this to revive it with a fuller analysis and to tie together several related reports.
Environment
opencode: 1.17.14 (source pinned to commit 203885d24; overflow.ts identical on current HEAD)
Use a built-in model that has no limit.input (e.g. zhipuai/glm-5.2). Observe that compaction does not trigger at input - reserved; it triggers at context - maxOutputTokens = 1000000 - 32000 = 968000. The reserved value has zero effect.
Root cause
packages/opencode/src/session/overflow.ts — usable() has two mutually exclusive branches, and reserved only participates in one:
Models without limit.input (which is common — most models.dev entries only declare context + output) fall into branch B, where reserved is computed but never used. maxOutputTokens = min(limit.output, OUTPUT_TOKEN_MAX=32000) (from provider/transform.ts), so for glm-5.2 the threshold is 1000000 - 32000 = 968000 regardless of reserved.
The config schema (packages/core/src/v1/config/provider.ts) does support limit.input on custom providers, but there is no way to override limit.input for a built-in provider's model — a custom provider with the same name as a built-in does not replace the built-in entry. So users of built-in providers cannot work around this in config.
Silent failure: the user sets reserved, gets no error, but the setting never takes effect. This is a UX trap — there's no signal that the config is being ignored for their model.
This makes reserved a consistent "trigger N tokens earlier" knob regardless of whether limit.input is declared. Default reserved is min(COMPACTION_BUFFER=20000, maxOutputTokens), so the default threshold for branch-B models shifts earlier by ~20K — safer (triggers before the hard limit) and consistent with branch A's behavior.
Open semantic question for maintainers: should reserved be subtracted from limit.input (branch A) or from context - maxOutputTokens (branch B)? These differ when limit.input != context - maxOutputTokens. The two branches should agree on the reference point.
The models.dev registry also omits limit.input for these models (systematic across the zhipuai provider). I'll file that separately against models.dev. But the opencode-side fix is needed regardless, since reserved should not be silently dropped for any model.
Summary
compaction.reservedis silently ignored for any model whoselimitlacks aninputfield. This was reported in #13980, which was closed by the stale bot without being fixed — the current code still ignoresreservedon this code path. Filing this to revive it with a fuller analysis and to tie together several related reports.Environment
203885d24;overflow.tsidentical on current HEAD)zhipuai/glm-5.2(limit = { context: 1000000, output: 131072 }, noinput)Reproduction
Config:
Use a built-in model that has no
limit.input(e.g.zhipuai/glm-5.2). Observe that compaction does not trigger atinput - reserved; it triggers atcontext - maxOutputTokens = 1000000 - 32000 = 968000. Thereservedvalue has zero effect.Root cause
packages/opencode/src/session/overflow.ts—usable()has two mutually exclusive branches, andreservedonly participates in one:Models without
limit.input(which is common — most models.dev entries only declarecontext+output) fall into branch B, wherereservedis computed but never used.maxOutputTokens = min(limit.output, OUTPUT_TOKEN_MAX=32000)(fromprovider/transform.ts), so for glm-5.2 the threshold is1000000 - 32000 = 968000regardless ofreserved.The config schema (
packages/core/src/v1/config/provider.ts) does supportlimit.inputon custom providers, but there is no way to overridelimit.inputfor a built-in provider's model — a custom provider with the same name as a built-in does not replace the built-in entry. So users of built-in providers cannot work around this in config.Expected vs actual
limit.input - reserved(reserved takes effect)context - maxOutputTokens = 968000(reserved silently ignored)Impact
Silent failure: the user sets
reserved, gets no error, but the setting never takes effect. This is a UX trap — there's no signal that the config is being ignored for their model.Proposed fix
Make
reservedapply in branch B as well:This makes
reserveda consistent "trigger N tokens earlier" knob regardless of whetherlimit.inputis declared. Defaultreservedismin(COMPACTION_BUFFER=20000, maxOutputTokens), so the default threshold for branch-B models shifts earlier by ~20K — safer (triggers before the hard limit) and consistent with branch A's behavior.Open semantic question for maintainers: should
reservedbe subtracted fromlimit.input(branch A) or fromcontext - maxOutputTokens(branch B)? These differ whenlimit.input != context - maxOutputTokens. The two branches should agree on the reference point.Related issues
reservedcapped at 20K viamin(COMPACTION_BUFFER, …), below real max output. Same class of "reserved not behaving as expected" bug.limit.inputbranch only; the no-limit.inputbranch (this issue) was missed.reservedbeing ignored. Cross-referenced as same-model.limit.inputvslimit.contextsemantics; relevant to the open question above.Complementary upstream
The models.dev registry also omits
limit.inputfor these models (systematic across thezhipuaiprovider). I'll file that separately against models.dev. But the opencode-side fix is needed regardless, sincereservedshould not be silently dropped for any model.