diff --git a/src/server/responses/input-admission.ts b/src/server/responses/input-admission.ts index 219c3b6f89..a2a06e5b73 100644 --- a/src/server/responses/input-admission.ts +++ b/src/server/responses/input-admission.ts @@ -13,6 +13,7 @@ import { nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, type NativeContextLimitsInput } from "../../codex/catalog/metadata"; import { estimateTokens } from "../../lib/token-estimate"; import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers"; +import { modelRecordValue } from "../../reasoning-effort"; import type { OcxContentPart, OcxParsedRequest, OcxProviderConfig } from "../../types"; /** @@ -133,7 +134,11 @@ export function resolveInputCeiling( // config here so this stays pure: no filesystem, no catalog, no registry scan. nativeContextCap?: NativeContextLimitsInput, ): number | null { - const configured = positive(provider.modelContextWindows?.[modelId]) ?? positive(provider.contextWindow); + // `modelRecordValue`, not a bare lookup: the catalog resolves these same two maps that + // way, so a `gpt-oss` entry covers `gpt-oss:120b`. Reading raw here made the gate fall + // back to the provider-wide window and refuse turns the model can plainly hold. + const configured = positive(modelRecordValue(provider.modelContextWindows, modelId)) + ?? positive(provider.contextWindow); // The canonical `openai` registry entry declares no context fields, so without this the // gate would be inert on the default Codex route. All three clauses are load-bearing: a @@ -156,7 +161,7 @@ export function resolveInputCeiling( const window = canonicalNativeBare ? native : configured; // modelMaxInputTokens is an input-only cap, so it can only tighten the window. - const configuredMaxInput = positive(provider.modelMaxInputTokens?.[modelId]); + const configuredMaxInput = positive(modelRecordValue(provider.modelMaxInputTokens, modelId)); const limits = [window, configuredMaxInput, nativeMaxInput].filter((v): v is number => v !== null); return limits.length === 0 ? null : Math.min(...limits); } diff --git a/tests/input-admission.test.ts b/tests/input-admission.test.ts index 44422d502c..d97e0fe82e 100644 --- a/tests/input-admission.test.ts +++ b/tests/input-admission.test.ts @@ -5,6 +5,7 @@ import { estimateInputTokens, resolveInputCeiling, } from "../src/server/responses/input-admission"; +import { modelRecordValue } from "../src/reasoning-effort"; import type { OcxMessage, OcxParsedRequest, OcxProviderConfig, OcxTool } from "../src/types"; const CANONICAL_NATIVE: OcxProviderConfig = { @@ -91,6 +92,42 @@ describe("resolveInputCeiling", () => { const pinned: OcxProviderConfig = { ...CANONICAL_NATIVE, modelContextWindows: { "gpt-5.6-sol": 50_000 } }; expect(resolveInputCeiling(pinned, "openai", "gpt-5.6-sol")).toBe(50_000); }); + + test("a family entry covers its tagged siblings, like the catalog", () => { + // The catalog resolves modelContextWindows through modelRecordValue, so it advertises + // 131_072 for gpt-oss:120b off this config. A bare lookup here resolved nothing and + // fell back to contextWindow, leaving the gate refusing turns the model can hold. + const provider: OcxProviderConfig = { + adapter: "openai-chat", + baseUrl: "https://example.test/v1", + contextWindow: 8_000, + modelContextWindows: { "gpt-oss": 131_072 }, + }; + expect(modelRecordValue(provider.modelContextWindows, "gpt-oss:120b")).toBe(131_072); + expect(resolveInputCeiling(provider, "custom", "gpt-oss:120b")).toBe(131_072); + // An id with no tag and no entry still falls back to the provider-wide window. + expect(resolveInputCeiling(provider, "custom", "other")).toBe(8_000); + }); + + test("an exact entry still beats the family entry", () => { + const provider: OcxProviderConfig = { + adapter: "openai-chat", + baseUrl: "https://example.test/v1", + modelContextWindows: { "gpt-oss": 131_072, "gpt-oss:20b": 32_000 }, + }; + expect(resolveInputCeiling(provider, "custom", "gpt-oss:20b")).toBe(32_000); + expect(resolveInputCeiling(provider, "custom", "gpt-oss:120b")).toBe(131_072); + }); + + test("a family modelMaxInputTokens tightens its tagged siblings", () => { + const provider: OcxProviderConfig = { + adapter: "openai-chat", + baseUrl: "https://example.test/v1", + modelContextWindows: { "gpt-oss": 131_072 }, + modelMaxInputTokens: { "gpt-oss": 40_000 }, + }; + expect(resolveInputCeiling(provider, "custom", "gpt-oss:120b")).toBe(40_000); + }); }); describe("estimateInputTokens", () => {