diff --git a/src/codex/catalog/sync.ts b/src/codex/catalog/sync.ts index 3659a0a67..70ac8c270 100644 --- a/src/codex/catalog/sync.ts +++ b/src/codex/catalog/sync.ts @@ -214,6 +214,13 @@ export function deriveEntry( // Routed (namespaced) models inherit the gpt template — correct its OpenAI/GPT identity // and advertise the reasoning ladder Codex accepts. if (isRouted) { + // A routed model is NOT the native template: never inherit its context + // window when /models omits context metadata (#992). Known metadata + // restores exact values below; otherwise the strict-fields fallback + // supplies the conservative 128k triple. + delete e.context_window; + delete e.max_context_window; + delete e.auto_compact_token_limit; // Native id for identity text + metadata lookups — the slug may be an encoded // alias (`provider/vendor-model`); the model object carries the native id. const modelName = model?.id ?? slug.slice(slug.indexOf("/") + 1); diff --git a/tests/codex-catalog.test.ts b/tests/codex-catalog.test.ts index 2debabd4d..d04efdf24 100644 --- a/tests/codex-catalog.test.ts +++ b/tests/codex-catalog.test.ts @@ -1049,7 +1049,7 @@ describe("Codex catalog routed normalization", () => { expect(anthropic?.supports_parallel_tool_calls).toBe(false); }); - test("routed entries fill auto compact when context already exists on the template", () => { + test("routed entries fall to the conservative triple instead of inheriting template context (#992)", () => { const template = { ...nativeTemplate(), context_window: 272_000, @@ -1060,9 +1060,11 @@ describe("Codex catalog routed normalization", () => { ]); const routed = entries.find(e => e.slug === "local/qwen3-coder"); - expect(routed?.context_window).toBe(272_000); - expect(routed?.max_context_window).toBe(272_000); - expect(routed?.auto_compact_token_limit).toBe(244_800); + // A routed model is not the native template: without known metadata the + // entry falls back to the conservative 128k triple. + expect(routed?.context_window).toBe(128_000); + expect(routed?.max_context_window).toBe(128_000); + expect(routed?.auto_compact_token_limit).toBe(115_200); }); test("native gpt-5.4 uses its 1M context window override", () => { @@ -1196,7 +1198,7 @@ describe("Codex catalog routed normalization", () => { expect(sol?.priority).toBe(1); }); - test("routed entries still cap stale native max context to their active context window", () => { + test("routed entries drop stale native max context with the template window (#992)", () => { const template = { ...nativeTemplate(), context_window: 272_000, @@ -1207,9 +1209,9 @@ describe("Codex catalog routed normalization", () => { ]); const routed = entries.find(e => e.slug === "local/qwen3-coder"); - expect(routed?.context_window).toBe(272_000); - expect(routed?.max_context_window).toBe(272_000); - expect(routed?.auto_compact_token_limit).toBe(244_800); + expect(routed?.context_window).toBe(128_000); + expect(routed?.max_context_window).toBe(128_000); + expect(routed?.auto_compact_token_limit).toBe(115_200); }); test("buildCatalogEntries preserves native bare GPT template fields", () => { @@ -2301,6 +2303,35 @@ describe("Codex catalog routed normalization", () => { expect(routed?.default_reasoning_summary).toBe("none"); }); + test("a routed model never inherits the native template's context window (#992)", () => { + // /models returns only the id: the routed entry must fall to the + // conservative 128k triple, never the native template's larger window. + const entries = buildCatalogEntries({ context_window: 372_000 }, [], [ + { provider: "relay", id: "relay-model" }, + ]); + const routed = entries.find(e => e.slug === "relay/relay-model"); + expect(routed?.context_window).toBe(128_000); + expect(routed?.max_context_window).toBe(128_000); + expect(routed?.auto_compact_token_limit).toBe(115_200); + }); + + test("a provider context cap never invents routed capacity (#992)", () => { + const entries = buildCatalogEntries({ context_window: 372_000 }, [], [ + { provider: "relay", id: "relay-model", contextCap: 950_000 }, + ]); + const routed = entries.find(e => e.slug === "relay/relay-model"); + expect(routed?.context_window).toBe(128_000); + }); + + test("known routed metadata still restores the exact context window (#992)", () => { + const entries = buildCatalogEntries({ context_window: 372_000 }, [], [ + { provider: "relay", id: "relay-model", contextWindow: 256_000 }, + ]); + const routed = entries.find(e => e.slug === "relay/relay-model"); + expect(routed?.context_window).toBe(256_000); + expect(routed?.auto_compact_token_limit).toBe(Math.floor(256_000 * 0.9)); + }); + test("model-specific reasoning-summary opt-out reaches the routed catalog (#323)", async () => { const models = await gatherRoutedModels({ providers: {