From 24cba4070dbca9325a919b2fda76cc97645e6581 Mon Sep 17 00:00:00 2001 From: minhn4 Date: Mon, 7 Sep 2026 09:18:23 +0700 Subject: [PATCH 1/2] feat(model-limits): give GLM-5.3-Flash its served 262144 window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gateway serves zai-org/GLM-5.3-Flash with the same 262144-token window as MiniMax-M3 (a deployment resource cap, not the model's native window), but with no table entry it fell back to the safe 200K default — so every agent CoDev configures for it was told the wrong window. One table entry covers every command: install, config, model, and the launch-time key refresh all write through the four configure.ts writers, and each resolves its numbers from limitsFor. Also refreshes the AGENTS.md section that still described the pre-#259 model-limits shape (explicit per-model triggers, an atob-encoded M3_ID, DEFAULT_LIMITS at 200K/160K) and notes that the table has a twin in codev-code that has to move with it. Co-Authored-By: Claude Opus 5 (1M context) --- AGENTS.md | 4 +++- src/lib/model-limits.ts | 10 ++++++---- tests/lib/configure.test.ts | 4 ++++ tests/lib/model-limits.test.ts | 4 ++++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 55a0836..5481da5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -142,7 +142,9 @@ The load-bearing consequence: the provider id **is** CoDev's authorship marker f Every agent CoDev configures has to be *told* the window of the model it's talking to. The gateway serves custom models none of them recognize, and each guesses differently when unconfigured: Codex assumes a 272K fallback, OpenCode assumes context `0` (which disables compaction outright), Continue falls back to a generic default. `src/lib/model-limits.ts` is the single source of truth; the four writers in `configure.ts` translate it into each agent's own knob and hold no window constants of their own. The flat `GATEWAY_CONTEXT_WINDOW` / `GATEWAY_COMPACT_*` constants that used to live in `const.ts` are gone — they encoded the assumption that every gateway model shares one 196608-token window, which stopped being true once the gateway served both a 1M-token and a 200K-token model. -`ModelLimits` is `{ context, trigger, output? }`: the true window, the absolute token count where auto-compaction should fire, and an optional output cap. **`trigger` is explicit rather than a percentage** — the gap between window and trigger is a per-model judgement call, not a constant. `limitsFor(modelId)` resolves **remote → table → `DEFAULT_LIMITS`**, where remote is the gateway's own numbers cached in auth.json and `DEFAULT_LIMITS` (200K/160K) covers anything unrecognized. The 200K sibling of the 1M model is deliberately *absent* from the table: the default already describes it, and an entry that merely restates the default is one more thing to keep in sync. That 1M model's id lives in `model-limits.ts` as `M3_ID`, `atob`-encoded for the same reason `FALLBACK_MODEL` is in `const.ts` — the upstream vendor's name stays out of the shipped bundle, so keep it that way when adding table entries. +`ModelLimits` is `{ context, trigger, output? }`: the true window, the absolute token count where auto-compaction should fire, and an optional output cap. **The table carries windows only** — `limitsFromWindow` derives the trigger as `COMPACT_PCT` (90%) of the window, whatever the window's source, so adding a model means adding one number. `limitsFor(modelId)` resolves **remote → table → `DEFAULT_LIMITS`**, where remote is the gateway's own numbers cached in auth.json and `DEFAULT_LIMITS` (200K/180K) covers anything unrecognized. A model the deployment serves at that same 200K window needs no entry: the default already describes it, and an entry that merely restates the default is one more thing to keep in sync. Model ids in the table are plain strings (#259 dropped the `atob`-encoded `M3_ID`); `FALLBACK_MODEL` in `const.ts` is still encoded and stays that way. + +**A table entry has a twin in [codev-code](https://github.com/quickbeard/codev-code) (`packages/codev-gateway/src/model-limits.ts`).** CoDev Code's own gateway sign-in configures itself without going through the hub, so it carries its own copy of the same table. The two are deliberately separate — the hub must keep configuring agents on machines with an older CoDev Code — but a model added to one and not the other gets different windows depending on which flow wrote the config. Add it to both. Each agent takes a different shape, and the differences are the whole reason this module exists: diff --git a/src/lib/model-limits.ts b/src/lib/model-limits.ts index d88203d..2bca6ee 100644 --- a/src/lib/model-limits.ts +++ b/src/lib/model-limits.ts @@ -46,13 +46,15 @@ export const DEFAULT_LIMITS: ModelLimits = limitsFromWindow(DEFAULT_WINDOW); // Known gateway models' context windows. Keyed by the exact id `/v1/models` // reports, which is what lands in every agent config. // -// M3's window is capped at 262144 on the current deployment (the model -// supports more, the gateway's resources don't). The 200K sibling is -// deliberately absent: DEFAULT_WINDOW already describes it correctly, and an -// entry that merely restates the default is one more thing to keep in sync. +// M3's and GLM-5.3-Flash's windows are capped at 262144 on the current +// deployment (the models support more, the gateway's resources don't). M3's +// 200K sibling is deliberately absent: DEFAULT_WINDOW already describes it +// correctly, and an entry that merely restates the default is one more thing to +// keep in sync. const TABLE: Record = { "MiniMax/MiniMax-M3": 262144, "zai-org/GLM-4.7-cc": 200000, + "zai-org/GLM-5.3-Flash": 262144, }; // Windows reported by the gateway, cached in auth.json by the model-choice diff --git a/tests/lib/configure.test.ts b/tests/lib/configure.test.ts index 5e839bf..5d332c0 100644 --- a/tests/lib/configure.test.ts +++ b/tests/lib/configure.test.ts @@ -965,6 +965,10 @@ describe("configureCodex", () => { configureCodex({ apiKey: "sk-codex", model: "zai-org/GLM-4.7-cc" }); expect(readCodexToml().model_context_window).toBe(200000); expect(readCodexToml().model_auto_compact_token_limit).toBe(180000); + + configureCodex({ apiKey: "sk-codex", model: "zai-org/GLM-5.3-Flash" }); + expect(readCodexToml().model_context_window).toBe(262144); + expect(readCodexToml().model_auto_compact_token_limit).toBe(235930); }); test("does not touch ~/.claude.json (Codex-only install)", async () => { diff --git a/tests/lib/model-limits.test.ts b/tests/lib/model-limits.test.ts index 5fc576d..2142d25 100644 --- a/tests/lib/model-limits.test.ts +++ b/tests/lib/model-limits.test.ts @@ -55,6 +55,10 @@ describe("limitsFor", () => { context: 200000, trigger: 180000, }); + expect(limitsFor("zai-org/GLM-5.3-Flash")).toEqual({ + context: 262144, + trigger: 235930, + }); }); test("falls back to the 200K default for an unrecognized model", () => { From f1ed1486a6ba2f37dbf8919235b1e3634baa0841 Mon Sep 17 00:00:00 2001 From: minhn4 Date: Mon, 7 Sep 2026 09:37:48 +0700 Subject: [PATCH 2/2] feat(model-limits): drop the GLM-4.7-cc window entry Its 200000 restated DEFAULT_WINDOW exactly, so limitsFor returns the same {context: 200000, trigger: 180000} through the default tier and all four writers emit identical config. The table now carries only the models whose window differs from the default. Tests that used the id as a stand-in for a 200K model now use DEFAULT_LIMITS or an unlisted id, so each still covers the default-window branch it was written for. Co-Authored-By: Claude Opus 5 (1M context) --- src/lib/model-limits.ts | 9 ++++----- tests/lib/configure.test.ts | 20 +++++++++++--------- tests/lib/model-limits.test.ts | 19 ++++++++----------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/lib/model-limits.ts b/src/lib/model-limits.ts index 2bca6ee..8e88328 100644 --- a/src/lib/model-limits.ts +++ b/src/lib/model-limits.ts @@ -47,13 +47,12 @@ export const DEFAULT_LIMITS: ModelLimits = limitsFromWindow(DEFAULT_WINDOW); // reports, which is what lands in every agent config. // // M3's and GLM-5.3-Flash's windows are capped at 262144 on the current -// deployment (the models support more, the gateway's resources don't). M3's -// 200K sibling is deliberately absent: DEFAULT_WINDOW already describes it -// correctly, and an entry that merely restates the default is one more thing to -// keep in sync. +// deployment (the models support more, the gateway's resources don't). Models +// served at the 200K default are deliberately absent: DEFAULT_WINDOW already +// describes them correctly, and an entry that merely restates the default is +// one more thing to keep in sync. const TABLE: Record = { "MiniMax/MiniMax-M3": 262144, - "zai-org/GLM-4.7-cc": 200000, "zai-org/GLM-5.3-Flash": 262144, }; diff --git a/tests/lib/configure.test.ts b/tests/lib/configure.test.ts index 5d332c0..190cfa5 100644 --- a/tests/lib/configure.test.ts +++ b/tests/lib/configure.test.ts @@ -311,7 +311,8 @@ describe("configureClaudeCode", () => { expect(read().CLAUDE_CODE_AUTO_COMPACT_WINDOW).toBe("200000"); expect(read().CLAUDE_AUTOCOMPACT_PCT_OVERRIDE).toBe("80"); - configureClaudeCode({ apiKey: "sk", model: "zai-org/GLM-4.7-cc" }); + // And a model on the 200K default lands on the same two numbers. + configureClaudeCode({ apiKey: "sk", model: "other/model" }); expect(read().CLAUDE_CODE_AUTO_COMPACT_WINDOW).toBe("200000"); expect(read().CLAUDE_AUTOCOMPACT_PCT_OVERRIDE).toBe("80"); }); @@ -459,7 +460,7 @@ describe("configureOpenCode", () => { configureOpenCode({ apiKey: "sk-xyz", model: "MiniMax/MiniMax-M3", - models: ["MiniMax/MiniMax-M3", "zai-org/GLM-4.7-cc"], + models: ["MiniMax/MiniMax-M3", "other/model"], }); const filePath = join(tempDir, ".config", "opencode", "opencode.json"); @@ -469,13 +470,13 @@ describe("configureOpenCode", () => { // True windows, so the TUI's "% context used" gauge stays honest. expect(map["MiniMax/MiniMax-M3"].limit.context).toBe(262144); - expect(map["zai-org/GLM-4.7-cc"].limit.context).toBe(200000); + expect(map["other/model"].limit.context).toBe(200000); // ...and each fires where it should, off one shared reserve. Both // models' 90% target + reserve exceeds their window, so declaredInput's // clamp pins input at the window — triggers stay per-model and never // past the ceiling. expect(map["MiniMax/MiniMax-M3"].limit.input - reserved).toBe(222144); - expect(map["zai-org/GLM-4.7-cc"].limit.input - reserved).toBe(160000); + expect(map["other/model"].limit.input - reserved).toBe(160000); }); test("writes every fetched model into the provider's models map", async () => { @@ -962,13 +963,14 @@ describe("configureCodex", () => { expect(readCodexToml().model_context_window).toBe(262144); expect(readCodexToml().model_auto_compact_token_limit).toBe(235930); - configureCodex({ apiKey: "sk-codex", model: "zai-org/GLM-4.7-cc" }); - expect(readCodexToml().model_context_window).toBe(200000); - expect(readCodexToml().model_auto_compact_token_limit).toBe(180000); - configureCodex({ apiKey: "sk-codex", model: "zai-org/GLM-5.3-Flash" }); expect(readCodexToml().model_context_window).toBe(262144); expect(readCodexToml().model_auto_compact_token_limit).toBe(235930); + + // A model with no table entry keeps the 200K default's own numbers. + configureCodex({ apiKey: "sk-codex", model: "other/model" }); + expect(readCodexToml().model_context_window).toBe(200000); + expect(readCodexToml().model_auto_compact_token_limit).toBe(180000); }); test("does not touch ~/.claude.json (Codex-only install)", async () => { @@ -1104,7 +1106,7 @@ describe("configureContinue", () => { configureContinue({ apiKey: "sk", model: "MiniMax/MiniMax-M3", - models: ["MiniMax/MiniMax-M3", "zai-org/GLM-4.7-cc"], + models: ["MiniMax/MiniMax-M3", "other/model"], }); const raw = readContinueYaml(); diff --git a/tests/lib/model-limits.test.ts b/tests/lib/model-limits.test.ts index 2142d25..560d844 100644 --- a/tests/lib/model-limits.test.ts +++ b/tests/lib/model-limits.test.ts @@ -51,10 +51,6 @@ describe("limitsFor", () => { context: 262144, trigger: 235930, }); - expect(limitsFor("zai-org/GLM-4.7-cc")).toEqual({ - context: 200000, - trigger: 180000, - }); expect(limitsFor("zai-org/GLM-5.3-Flash")).toEqual({ context: 262144, trigger: 235930, @@ -85,9 +81,9 @@ describe("limitsFor", () => { test("a model the gateway says nothing about still gets the table entry", () => { writeCachedLimits({ "other/model": { context: 12345, trigger: 9876 } }); - expect(limitsFor("zai-org/GLM-4.7-cc")).toEqual({ - context: 200000, - trigger: 180000, + expect(limitsFor("zai-org/GLM-5.3-Flash")).toEqual({ + context: 262144, + trigger: 235930, }); }); @@ -152,7 +148,9 @@ describe("claudeWindow / claudeCompactPct", () => { // 235930/262144 would be 90, but 235930/200000 is 118 — the raw ratio is // meaningless once the window is clamped, so it must be bounded. expect(claudeCompactPct(limitsFor("MiniMax/MiniMax-M3"))).toBe(80); - expect(claudeCompactPct(limitsFor("zai-org/GLM-4.7-cc"))).toBe(80); + // A model already at the ceiling is capped by the same guard: its own + // 90% target is above Claude Code's 80%. + expect(claudeCompactPct(DEFAULT_LIMITS)).toBe(80); }); test("never exceeds 80%, which Claude Code's Rzq discards anything above", () => { @@ -198,11 +196,10 @@ describe("declaredInput", () => { declaredInput({ context: 1000000, trigger: 900000 }) - COMPACT_RESERVED, ).toBe(900000); const m3 = limitsFor("MiniMax/MiniMax-M3"); - const glm = limitsFor("zai-org/GLM-4.7-cc"); expect(declaredInput(m3)).toBe(262144); expect(declaredInput(m3) - COMPACT_RESERVED).toBe(222144); - expect(declaredInput(glm)).toBe(200000); - expect(declaredInput(glm) - COMPACT_RESERVED).toBe(160000); + expect(declaredInput(DEFAULT_LIMITS)).toBe(200000); + expect(declaredInput(DEFAULT_LIMITS) - COMPACT_RESERVED).toBe(160000); }); test("clamps to the true window so the budget is never overstated", () => {