diff --git a/src/codex/catalog.ts b/src/codex/catalog.ts index 8a0acb06fd..5f6cb092f1 100644 --- a/src/codex/catalog.ts +++ b/src/codex/catalog.ts @@ -2,7 +2,7 @@ // Public surface preserved exactly; importers keep using "src/codex/catalog". export { isMediaGenerationModelId, shouldExposeRoutedModel, readCodexCatalogPath, readCatalog, normalizeRoutedCatalogEntry, catalogModelSlug, filterSupportedNativeSlugs, catalogModelSupportsReasoningSummaries } from "./catalog/parsing"; export type { CatalogModel, MultiAgentMode } from "./catalog/parsing"; -export { accountBoundNativeOpenAiSlugs, accountBoundNativeOpenAiSlugsBySelector, CODEX_NATIVE_ALIAS_CATALOG_KIND, NATIVE_DAYBREAK_BLUE_MODEL, NATIVE_GPT56_CONTEXT_WINDOW, NATIVE_GPT56_MAX_INPUT_TOKENS, NATIVE_GPT56_OPT_IN_CONTEXT_WINDOW, NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS, NATIVE_OPENAI_MODELS, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, isNativeAliasCatalogEntry, isNativeOpenAiCapabilityAliasModel, nativeContextLimits, nativeOpenAiCapabilitySourceSlug, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, observedAccountBoundNativeEntries, observedAccountBoundNativeOpenAiSlugs, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs, nativeInputModalities, nativeReasoningEfforts, nativeDefaultReasoningEffort, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, type NativeContextLimits, type NativeContextLimitsInput } from "./catalog/metadata"; +export { accountBoundNativeOpenAiSlugs, accountBoundNativeOpenAiSlugsBySelector, CODEX_NATIVE_ALIAS_CATALOG_KIND, NATIVE_DAYBREAK_BLUE_MODEL, NATIVE_GPT56_CONTEXT_WINDOW, NATIVE_GPT56_MAX_CONTEXT_WINDOW, NATIVE_GPT56_MAX_INPUT_TOKENS, NATIVE_GPT56_OPT_IN_CONTEXT_WINDOW, NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS, NATIVE_OPENAI_MODELS, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, isNativeAliasCatalogEntry, isNativeOpenAiCapabilityAliasModel, nativeContextLimits, nativeOpenAiCapabilitySourceSlug, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, observedAccountBoundNativeEntries, observedAccountBoundNativeOpenAiSlugs, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs, nativeInputModalities, nativeReasoningEfforts, nativeDefaultReasoningEffort, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, type NativeContextLimits, type NativeContextLimitsInput } from "./catalog/metadata"; export { isSpawnableCodexCandidate, codexExecInvocation, loadBundledCodexCatalog, materializeBundledCodexCatalog, loadCatalogTemplate } from "./catalog/bundled"; export { nativeEffortClamp, shouldApplyNativeEffortClamp, catalogModelEfforts, codexSupportedReasoningEfforts, clampedDefaultEffort, clampEntryToCodexSupportedEfforts, clampCatalogModelsToCodexSupport } from "./catalog/effort"; export { applyProviderConfigHints, isDatedVariantId, filterCatalogVisibleModels, gatherRoutedModels, clearGatherRoutedModelsInflight, augmentRoutedModelsWithRegistryOpenAiApiRows, augmentRoutedModelsWithMetadata, resolveComboCatalogMember, configuredComboTargetModelsByProvider } from "./catalog/provider-fetch"; diff --git a/src/codex/catalog/metadata.ts b/src/codex/catalog/metadata.ts index 6733f5aa6c..c363e6a87f 100644 --- a/src/codex/catalog/metadata.ts +++ b/src/codex/catalog/metadata.ts @@ -137,6 +137,17 @@ export const NATIVE_GPT56_MAX_INPUT_TOKENS = 922_000; /** User-facing 1M opt-in: the largest window the native 5.6 family may advertise. */ export const NATIVE_GPT56_OPT_IN_CONTEXT_WINDOW = NATIVE_GPT56_MAX_INPUT_TOKENS; +/** + * The native/upstream advertised max for the GPT-5.6 family. + * + * The live Codex catalog reports context_window 272000 against + * max_context_window 872000 (devlog E1). That upstream max is the default + * ceiling the catalog should advertise; the measured 922k ceiling + * (NATIVE_GPT56_MAX_INPUT_TOKENS) stays the opt-in target that user levers + * may raise to. + */ +export const NATIVE_GPT56_MAX_CONTEXT_WINDOW = 872_000; + const NATIVE_GPT56_FAMILY = new Set([ "gpt-5.6-sol", "gpt-5.6-terra", @@ -148,15 +159,15 @@ export const NATIVE_OPENAI_CONTEXT_OVERRIDES: Record = new Map( diff --git a/src/codex/catalog/parsing.ts b/src/codex/catalog/parsing.ts index f42049150e..a473a33245 100644 --- a/src/codex/catalog/parsing.ts +++ b/src/codex/catalog/parsing.ts @@ -31,7 +31,7 @@ import { redactSecretString } from "../../lib/redact"; import upstreamModelsSnapshot from "../data/upstream-models.json"; -import { NATIVE_OPENAI_CONTEXT_OVERRIDES, SUPPORTED_NATIVE_OPENAI_SLUGS, UPSTREAM_NATIVE_ENTRIES, isNativeOpenAiCapabilityAliasModel, nativeMultiAgentVersion, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, type NativeContextLimitsInput } from "./metadata"; +import { NATIVE_GPT56_MAX_INPUT_TOKENS, NATIVE_OPENAI_CONTEXT_OVERRIDES, SUPPORTED_NATIVE_OPENAI_SLUGS, UPSTREAM_NATIVE_ENTRIES, isNativeOpenAiCapabilityAliasModel, nativeMultiAgentVersion, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, type NativeContextLimitsInput } from "./metadata"; import { trustedAccountBoundNativeCatalogSlug } from "./account-models"; import { CODEX_NATIVE_ALIAS_CATALOG_KIND } from "./kinds"; @@ -296,10 +296,16 @@ function narrowNativeMaxContextWindow( if (typeof value !== "number" || value <= 0) return value; const resolved = nativeOpenAiContextWindow(slug, limits); const authoritative = nativeOpenAiContextWindow(slug); - // The accessor pair tells us how far the levers moved this slug; apply the same delta to a - // field the accessor does not model, without ever raising it. + // The accessor pair tells us how far the levers moved this slug. Only narrow + // the max when the user actually lowered the window; otherwise keep the native + // max, which upstream advertises above the operating window (272k vs 872k). if (resolved === undefined || authoritative === undefined) return value; - return Math.min(value, Math.max(resolved, 1)); + // A configured provider cap independently clamps the advertised maximum, even + // when the resolved operating window was raised above the authoritative window. + const cap = typeof limits === "number" ? limits : limits?.cap; + const capped = applyProviderContextCap(value, cap) ?? value; + if (resolved < authoritative) return Math.min(capped, Math.max(resolved, 1)); + return capped; } export function applyNativeOpenAiContextOverride(entry: RawEntry, limits?: NativeContextLimitsInput): void { @@ -321,8 +327,17 @@ export function applyNativeOpenAiContextOverride(entry: RawEntry, limits?: Nativ ); } if (typeof override.maxContextWindow === "number") { - const maxContextWindow = narrowNativeMaxContextWindow(nativeSlug, override.maxContextWindow, limits); - entry.max_context_window = maxContextWindow; + const resolvedContext = nativeOpenAiContextWindow(nativeSlug, limits) ?? override.contextWindow; + const raised = typeof resolvedContext === "number" + && typeof override.contextWindow === "number" + && resolvedContext > override.contextWindow; + // Default rows advertise the native max (e.g. 872k for GPT-5.6) even though + // the operating window is 272k. When the user raises the window, the max + // follows the raise, capped at the measured ceiling (922k). + const maxContextWindow = raised + ? Math.min(Math.max(override.maxContextWindow, resolvedContext), NATIVE_GPT56_MAX_INPUT_TOKENS) + : override.maxContextWindow; + entry.max_context_window = narrowNativeMaxContextWindow(nativeSlug, maxContextWindow, limits); } } // providerContextCaps.openai is a ceiling for native OpenAI rows regardless of where the diff --git a/tests/claude-models-discovery.test.ts b/tests/claude-models-discovery.test.ts index ef47458a7f..6b245c2cf6 100644 --- a/tests/claude-models-discovery.test.ts +++ b/tests/claude-models-discovery.test.ts @@ -6,6 +6,7 @@ import { saveConfig } from "../src/config"; import { handleManagementAPI } from "../src/server/management-api"; import { startServer } from "../src/server"; import type { OcxConfig } from "../src/types"; +import { NATIVE_GPT56_CONTEXT_WINDOW, NATIVE_GPT56_MAX_CONTEXT_WINDOW } from "../src/codex/catalog"; import { installIsolatedCodexHome, type IsolatedCodexHome } from "./helpers/isolated-codex-home"; import { ManagementRequest } from "./helpers/management-auth"; @@ -165,7 +166,7 @@ test("Codex discovery applies the OpenAI context cap to native rows (#1430)", as baseUrl: "https://chatgpt.com/backend-api/codex", liveModels: false, }; - config.providerContextCaps = { openai: 272_000 }; + config.providerContextCaps = { openai: NATIVE_GPT56_CONTEXT_WINDOW }; saveConfig(config); const server = startServer(0); try { @@ -180,9 +181,9 @@ test("Codex discovery applies the OpenAI context cap to native rows (#1430)", as }>; }; expect(json.models.find(model => model.slug === "gpt-5.6-sol")).toMatchObject({ - context_window: 272_000, - max_context_window: 272_000, - auto_compact_token_limit: 244_800, + context_window: NATIVE_GPT56_CONTEXT_WINDOW, + max_context_window: NATIVE_GPT56_MAX_CONTEXT_WINDOW, + auto_compact_token_limit: Math.floor(NATIVE_GPT56_CONTEXT_WINDOW * 0.9), }); } finally { await server.stop(true); diff --git a/tests/codex-catalog-sync-hardening.test.ts b/tests/codex-catalog-sync-hardening.test.ts index 0e65cdc814..9ba8b95624 100644 --- a/tests/codex-catalog-sync-hardening.test.ts +++ b/tests/codex-catalog-sync-hardening.test.ts @@ -4,6 +4,7 @@ import { spawnSync } from "node:child_process"; import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import { NATIVE_GPT56_CONTEXT_WINDOW, NATIVE_GPT56_MAX_CONTEXT_WINDOW, NATIVE_GPT56_MAX_INPUT_TOKENS } from "../src/codex/catalog"; const repoRoot = dirname(fileURLToPath(new URL("../package.json", import.meta.url))); @@ -411,9 +412,9 @@ describe("Codex catalog sync hardening", () => { const rows = JSON.parse(readFileSync(catalogPath, "utf8")).models as Array>; expect(rows.find(row => row.slug === "team/gpt-daybreak-blue-latest")).toMatchObject({ - context_window: 272_000, - max_context_window: 272_000, - auto_compact_token_limit: 244_800, + context_window: NATIVE_GPT56_CONTEXT_WINDOW, + max_context_window: NATIVE_GPT56_MAX_CONTEXT_WINDOW, + auto_compact_token_limit: Math.floor(NATIVE_GPT56_CONTEXT_WINDOW * 0.9), comp_hash: "3000", tool_mode: "code_mode_only", use_responses_lite: true, @@ -459,9 +460,9 @@ describe("Codex catalog sync hardening", () => { const daybreak = rows.find(row => row.slug === "openai/gpt-daybreak-blue-latest"); expect(daybreak).toMatchObject({ display_name: "Daybreak Blue", - context_window: 922_000, - max_context_window: 922_000, - auto_compact_token_limit: 829_800, + context_window: NATIVE_GPT56_MAX_INPUT_TOKENS, + max_context_window: NATIVE_GPT56_MAX_INPUT_TOKENS, + auto_compact_token_limit: Math.floor(NATIVE_GPT56_MAX_INPUT_TOKENS * 0.9), comp_hash: "3000", tool_mode: "code_mode_only", use_responses_lite: true, diff --git a/tests/codex-catalog.test.ts b/tests/codex-catalog.test.ts index 7086b6850a..86d6464987 100644 --- a/tests/codex-catalog.test.ts +++ b/tests/codex-catalog.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, spyOn, test } from "bun:test"; import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { applyNativeVisibility, augmentRoutedModelsWithMetadata, augmentRoutedModelsWithRegistryOpenAiApiRows, buildCatalogEntries, buildComboCatalogOmission, catalogModelSlug, clampCatalogModelsToCodexSupport, clampEntryToCodexSupportedEfforts, clampedDefaultEffort, CODEX_ACCOUNT_BOUND_CATALOG_KIND, CODEX_NATIVE_ALIAS_CATALOG_KIND, comboCatalogOmissionReason, deriveComboCatalogModel, exactComboCatalogSlugs, filterCatalogVisibleModels, filterSupportedNativeSlugs, gatherRoutedModels as gatherRoutedModelsDirect, isDatedVariantId, isMediaGenerationModelId, loadBundledCodexCatalog, materializeBundledCodexCatalog, mergeCatalogEntriesForSync, NATIVE_DAYBREAK_BLUE_MODEL, NATIVE_OPENAI_MODELS, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiCapabilitySourceSlug, nativeOpenAiContextWindow, nativeReasoningEfforts, normalizeRoutedCatalogEntry, resetCatalogRuntimeStateForTests, resetOpenAiApiCatalogWarningStateForTests, resolveComboCatalogMember, shouldExposeRoutedModel, upstreamNativeEntry } from "../src/codex/catalog"; + import { applyNativeVisibility, augmentRoutedModelsWithMetadata, augmentRoutedModelsWithRegistryOpenAiApiRows, buildCatalogEntries, buildComboCatalogOmission, catalogModelSlug, clampCatalogModelsToCodexSupport, clampEntryToCodexSupportedEfforts, clampedDefaultEffort, CODEX_ACCOUNT_BOUND_CATALOG_KIND, CODEX_NATIVE_ALIAS_CATALOG_KIND, comboCatalogOmissionReason, deriveComboCatalogModel, exactComboCatalogSlugs, filterCatalogVisibleModels, filterSupportedNativeSlugs, gatherRoutedModels as gatherRoutedModelsDirect, isDatedVariantId, isMediaGenerationModelId, loadBundledCodexCatalog, materializeBundledCodexCatalog, mergeCatalogEntriesForSync, NATIVE_DAYBREAK_BLUE_MODEL, NATIVE_GPT56_CONTEXT_WINDOW, NATIVE_GPT56_MAX_CONTEXT_WINDOW, NATIVE_GPT56_MAX_INPUT_TOKENS, NATIVE_OPENAI_MODELS, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiCapabilitySourceSlug, nativeOpenAiContextWindow, nativeReasoningEfforts, normalizeRoutedCatalogEntry, resetCatalogRuntimeStateForTests, resetOpenAiApiCatalogWarningStateForTests, resolveComboCatalogMember, shouldExposeRoutedModel, upstreamNativeEntry } from "../src/codex/catalog"; import { CODEX_CUSTOM_MODEL_CATALOG_KIND, CODEX_PROVIDER_MODEL_CATALOG_KIND, @@ -2619,9 +2619,11 @@ describe("Codex catalog routed normalization", () => { expect((gpt56?.supported_reasoning_levels as { effort: string }[]).map(l => l.effort)).toEqual([ "low", "medium", "high", "xhigh", "max", "ultra", ]); - expect(gpt56?.context_window).toBe(272_000); - expect(gpt56?.max_context_window).toBe(272_000); - expect(gpt56?.auto_compact_token_limit).toBe(244_800); + expect(gpt56?.context_window).toBe(NATIVE_GPT56_CONTEXT_WINDOW); + // The default operating window stays 272k, but the advertised max follows the + // native/upstream max (872k) instead of being flattened to the window. + expect(gpt56?.max_context_window).toBe(NATIVE_GPT56_MAX_CONTEXT_WINDOW); + expect(gpt56?.auto_compact_token_limit).toBe(Math.floor(NATIVE_GPT56_CONTEXT_WINDOW * 0.9)); expect((gpt55?.supported_reasoning_levels as { effort: string }[]).map(l => l.effort)).toEqual([ "low", "medium", "high", "xhigh", "max", "ultra", ]); @@ -2663,7 +2665,7 @@ describe("Codex catalog routed normalization", () => { expect(e).not.toHaveProperty("minimal_client_version"); expect(e).not.toHaveProperty("prefer_websockets"); expect(e).not.toHaveProperty("supports_websockets"); - expect(e?.context_window).toBe(272_000); + expect(e?.context_window).toBe(NATIVE_GPT56_CONTEXT_WINDOW); expect(e?.tool_mode).toBe("code_mode_only"); expect(e?.use_responses_lite).toBe(true); } @@ -2677,7 +2679,7 @@ describe("Codex catalog routed normalization", () => { }); test("providerContextCaps.openai ceilings native GPT-5.6 catalog rows (#1430)", () => { - const cap = 272_000; + const cap = NATIVE_GPT56_CONTEXT_WINDOW; const entries = buildCatalogEntries( nativeTemplate(), ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"], @@ -2694,13 +2696,13 @@ describe("Codex catalog routed normalization", () => { for (const slug of ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"]) { const entry = entries.find(e => e.slug === slug); expect(entry?.context_window).toBe(cap); - expect(entry?.max_context_window).toBe(cap); - expect(entry?.auto_compact_token_limit).toBe(244_800); + expect(entry?.max_context_window).toBe(NATIVE_GPT56_MAX_CONTEXT_WINDOW); + expect(entry?.auto_compact_token_limit).toBe(Math.floor(cap * 0.9)); } }); test("mergeCatalogEntriesForSync re-applies the openai cap to preserved and upgraded native rows (#1430)", () => { - const cap = 272_000; + const cap = NATIVE_GPT56_CONTEXT_WINDOW; const template = nativeTemplate(); // A preserved genuine row and a fallback-quality row (display_name stamped with // the bare slug) both pass through the final native-override pass on merge. @@ -2708,9 +2710,9 @@ describe("Codex catalog routed normalization", () => { ...template, slug: "gpt-5.6-sol", display_name: "GPT-5.6-Sol", - context_window: 922_000, - max_context_window: 922_000, - auto_compact_token_limit: 829_800, + context_window: NATIVE_GPT56_MAX_INPUT_TOKENS, + max_context_window: NATIVE_GPT56_MAX_INPUT_TOKENS, + auto_compact_token_limit: Math.floor(NATIVE_GPT56_MAX_INPUT_TOKENS * 0.9), supported_reasoning_levels: [ { effort: "low", description: "l" }, { effort: "high", description: "h" }, { effort: "max", description: "m" }, { effort: "ultra", description: "u" }, @@ -2737,13 +2739,13 @@ describe("Codex catalog routed normalization", () => { ); const sol = merged.find(e => e.slug === "gpt-5.6-sol"); expect(sol?.context_window).toBe(cap); - expect(sol?.max_context_window).toBe(cap); - expect(sol?.auto_compact_token_limit).toBe(244_800); + expect(sol?.max_context_window).toBe(NATIVE_GPT56_MAX_CONTEXT_WINDOW); + expect(sol?.auto_compact_token_limit).toBe(Math.floor(cap * 0.9)); // The backfilled luna row (upstream snapshot) is capped the same way. const luna = merged.find(e => e.slug === "gpt-5.6-luna"); expect(luna?.context_window).toBe(cap); - expect(luna?.max_context_window).toBe(cap); - expect(luna?.auto_compact_token_limit).toBe(244_800); + expect(luna?.max_context_window).toBe(NATIVE_GPT56_MAX_CONTEXT_WINDOW); + expect(luna?.auto_compact_token_limit).toBe(Math.floor(cap * 0.9)); }); test("preserved gpt-5.4-mini rows get the openai cap without a hardcoded override (#1430)", () => { @@ -2785,14 +2787,14 @@ describe("Codex catalog routed normalization", () => { }); test("nativeOpenAiContextWindow applies the openai cap as a ceiling only when provided", () => { - expect(nativeOpenAiContextWindow("gpt-5.6-sol")).toBe(272_000); - expect(nativeOpenAiContextWindow("gpt-5.6-sol", 272_000)).toBe(272_000); + expect(nativeOpenAiContextWindow("gpt-5.6-sol")).toBe(NATIVE_GPT56_CONTEXT_WINDOW); + expect(nativeOpenAiContextWindow("gpt-5.6-sol", NATIVE_GPT56_CONTEXT_WINDOW)).toBe(NATIVE_GPT56_CONTEXT_WINDOW); // A 500k cap raises the 272k default; a 2M cap clamps to the measured 922k ceiling. expect(nativeOpenAiContextWindow("gpt-5.6-sol", 500_000)).toBe(500_000); // A cap ABOVE the native value is a ceiling, not a floor. - expect(nativeOpenAiContextWindow("gpt-5.6-sol", 2_000_000)).toBe(922_000); + expect(nativeOpenAiContextWindow("gpt-5.6-sol", 2_000_000)).toBe(NATIVE_GPT56_MAX_INPUT_TOKENS); // Non-5.6 natives are capped the same way. - expect(nativeOpenAiContextWindow("gpt-5.4", 272_000)).toBe(272_000); + expect(nativeOpenAiContextWindow("gpt-5.4", NATIVE_GPT56_CONTEXT_WINDOW)).toBe(NATIVE_GPT56_CONTEXT_WINDOW); }); // Owner decision (devlog 260816_.../011 §4-bis): Daybreak Blue is now a GLOBALLY @@ -2802,7 +2804,7 @@ describe("Codex catalog routed normalization", () => { test("Daybreak Blue inherits Sol capabilities and ships one bare row plus one row per selector", () => { expect(NATIVE_DAYBREAK_BLUE_MODEL).toBe("gpt-daybreak-blue-latest"); expect(nativeOpenAiCapabilitySourceSlug(NATIVE_DAYBREAK_BLUE_MODEL)).toBe("gpt-5.6-sol"); - expect(nativeOpenAiContextWindow(NATIVE_DAYBREAK_BLUE_MODEL)).toBe(272_000); + expect(nativeOpenAiContextWindow(NATIVE_DAYBREAK_BLUE_MODEL)).toBe(NATIVE_GPT56_CONTEXT_WINDOW); expect(nativeInputModalities(NATIVE_DAYBREAK_BLUE_MODEL)).toEqual(["text", "image"]); expect(nativeReasoningEfforts(NATIVE_DAYBREAK_BLUE_MODEL)) .toEqual(["low", "medium", "high", "xhigh", "max", "ultra"]); @@ -2912,9 +2914,9 @@ describe("Codex catalog routed normalization", () => { expect(daybreak).toMatchObject({ slug: `openai/${NATIVE_DAYBREAK_BLUE_MODEL}`, display_name: "Daybreak Blue", - context_window: 922_000, - max_context_window: 922_000, - auto_compact_token_limit: 829_800, + context_window: NATIVE_GPT56_MAX_INPUT_TOKENS, + max_context_window: NATIVE_GPT56_MAX_INPUT_TOKENS, + auto_compact_token_limit: Math.floor(NATIVE_GPT56_MAX_INPUT_TOKENS * 0.9), comp_hash: "3000", tool_mode: "code_mode_only", use_responses_lite: true, diff --git a/tests/codex-convergence-account-selectors.test.ts b/tests/codex-convergence-account-selectors.test.ts index eedd2616d4..63b8fe19f3 100644 --- a/tests/codex-convergence-account-selectors.test.ts +++ b/tests/codex-convergence-account-selectors.test.ts @@ -16,6 +16,8 @@ import { captureCatalogAdmissionSnapshot } from "../src/codex/catalog-admission" import { CODEX_ACCOUNT_BOUND_CATALOG_KIND, loadBundledCodexCatalog, + NATIVE_GPT56_CONTEXT_WINDOW, + NATIVE_GPT56_MAX_CONTEXT_WINDOW, NATIVE_OPENAI_MODELS, resetCatalogRuntimeStateForTests, syncCatalogModels, @@ -377,9 +379,9 @@ test("convergence projects the observed Daybreak row onto its selector and one b expect(daybreak).toMatchObject({ visibility: "list", opencodex_catalog_kind: CODEX_ACCOUNT_BOUND_CATALOG_KIND, - context_window: 272_000, - max_context_window: 272_000, - auto_compact_token_limit: 244_800, + context_window: NATIVE_GPT56_CONTEXT_WINDOW, + max_context_window: NATIVE_GPT56_MAX_CONTEXT_WINDOW, + auto_compact_token_limit: Math.floor(NATIVE_GPT56_CONTEXT_WINDOW * 0.9), comp_hash: "3000", tool_mode: "code_mode_only", use_responses_lite: true, diff --git a/tests/native-model-toggle.test.ts b/tests/native-model-toggle.test.ts index 08e0c4eb19..dbd49e581e 100644 --- a/tests/native-model-toggle.test.ts +++ b/tests/native-model-toggle.test.ts @@ -9,6 +9,9 @@ import { desktopAllowlistSuppressedNativeSlugs, disabledNativeSlugs, mergeCatalogEntriesForSync, + NATIVE_GPT56_CONTEXT_WINDOW, + NATIVE_GPT56_MAX_CONTEXT_WINDOW, + NATIVE_GPT56_MAX_INPUT_TOKENS, NATIVE_OPENAI_MODELS, nativeContextLimits, nativeModelRows, @@ -69,7 +72,7 @@ describe("native GPT model toggles (bare slugs in disabledModels)", () => { expect(rows.find(r => r.slug === "gpt-5.6-sol")?.disabled).toBe(true); expect(rows.find(r => r.slug === "gpt-5.5")?.disabled).toBe(false); // Known context metadata rides along for the dashboard. - expect(rows.find(r => r.slug === "gpt-5.6-sol")?.contextWindow).toBe(272_000); + expect(rows.find(r => r.slug === "gpt-5.6-sol")?.contextWindow).toBe(NATIVE_GPT56_CONTEXT_WINDOW); }); test("a per-model window sets the native row and never exceeds the measured ceiling", () => { @@ -82,31 +85,76 @@ describe("native GPT model toggles (bare slugs in disabledModels)", () => { // window would be the same over-advertising this unit exists to fix. expect(rows.find(r => r.slug === "gpt-5.6-sol")?.maxInputTokens).toBe(500_000); // A sibling slug is untouched: this lever is per-model. - expect(rows.find(r => r.slug === "gpt-5.6-terra")?.contextWindow).toBe(272_000); + expect(rows.find(r => r.slug === "gpt-5.6-terra")?.contextWindow).toBe(NATIVE_GPT56_CONTEXT_WINDOW); // Above the measured ceiling the overlay is inert. A user value must never widen what the // upstream actually accepts. const tooWide = { providers: { openai: { modelContextWindows: { "gpt-5.6-sol": 2_000_000 } } } } as never; - expect(nativeModelRows(tooWide).find(r => r.slug === "gpt-5.6-sol")?.contextWindow).toBe(922_000); + expect(nativeModelRows(tooWide).find(r => r.slug === "gpt-5.6-sol")?.contextWindow).toBe(NATIVE_GPT56_MAX_INPUT_TOKENS); // provider-wide window applies to every native slug, and the cap still wins when lower. + const providerWindow = 500_000; + const cap = 350_000; const both = { - providers: { openai: { contextWindow: 500_000 } }, - providerContextCaps: { openai: 350_000 }, + providers: { openai: { contextWindow: providerWindow } }, + providerContextCaps: { openai: cap }, } as never; - expect(nativeModelRows(both).find(r => r.slug === "gpt-5.6-sol")?.contextWindow).toBe(350_000); + expect(nativeModelRows(both).find(r => r.slug === "gpt-5.6-sol")?.contextWindow).toBe(cap); }); - test("the on-disk catalog entry lands at the same width as the dashboard row", () => { + test("a raised on-disk window keeps the native max above the operating window", () => { // Regression: applyNativeOpenAiContextOverride used to re-read the static table and apply // only the cap, so a saved per-model window showed up in /api/models and was written back - // at 922,000 in the Codex catalog. - const limits = { providers: { openai: { modelContextWindows: { "gpt-5.6-sol": 500_000 } } } } as never; - const entry: Record = { slug: "gpt-5.6-sol", context_window: 922_000, max_context_window: 922_000 }; + // at 922,000 in the Codex catalog. Raising below the native max (872k) must preserve the + // native max instead of flattening max_context_window to the operating window. + const raisedWindow = 500_000; + const limits = { providers: { openai: { modelContextWindows: { "gpt-5.6-sol": raisedWindow } } } } as never; + const entry: Record = { + slug: "gpt-5.6-sol", + context_window: NATIVE_GPT56_MAX_INPUT_TOKENS, + max_context_window: NATIVE_GPT56_MAX_INPUT_TOKENS, + }; + applyNativeOpenAiContextOverride(entry as never, nativeContextLimits(limits)); + expect(entry.context_window).toBe(raisedWindow); + expect(entry.max_context_window).toBe(NATIVE_GPT56_MAX_CONTEXT_WINDOW); + expect(entry.auto_compact_token_limit).toBe(Math.floor(raisedWindow * 0.9)); + }); + + test("a provider cap clamps a raised window and its advertised max independently", () => { + const raisedWindow = 500_000; + const cap = 350_000; + const limits = { + providers: { openai: { modelContextWindows: { "gpt-5.6-sol": raisedWindow } } }, + providerContextCaps: { openai: cap }, + } as never; + const entry: Record = { + slug: "gpt-5.6-sol", + context_window: NATIVE_GPT56_MAX_INPUT_TOKENS, + max_context_window: NATIVE_GPT56_MAX_INPUT_TOKENS, + }; applyNativeOpenAiContextOverride(entry as never, nativeContextLimits(limits)); - expect(entry.context_window).toBe(500_000); - expect(entry.max_context_window).toBe(500_000); - expect(entry.auto_compact_token_limit).toBe(450_000); // 90% of the narrowed window + expect(entry.context_window).toBe(cap); + expect(entry.max_context_window).toBe(cap); + }); + + test("GPT-5.6 max_context_window follows raises between native max and the measured ceiling", () => { + const cases = [ + { window: NATIVE_GPT56_MAX_CONTEXT_WINDOW, expected: NATIVE_GPT56_MAX_CONTEXT_WINDOW }, + { window: NATIVE_GPT56_MAX_CONTEXT_WINDOW + 1_000, expected: NATIVE_GPT56_MAX_CONTEXT_WINDOW + 1_000 }, + { window: NATIVE_GPT56_MAX_INPUT_TOKENS, expected: NATIVE_GPT56_MAX_INPUT_TOKENS }, + { window: NATIVE_GPT56_MAX_INPUT_TOKENS + 1, expected: NATIVE_GPT56_MAX_INPUT_TOKENS }, + ]; + for (const { window, expected } of cases) { + const limits = { providers: { openai: { modelContextWindows: { "gpt-5.6-sol": window } } } } as never; + const entry: Record = { + slug: "gpt-5.6-sol", + context_window: NATIVE_GPT56_CONTEXT_WINDOW, + max_context_window: NATIVE_GPT56_CONTEXT_WINDOW, + }; + applyNativeOpenAiContextOverride(entry as never, nativeContextLimits(limits)); + expect(entry.context_window).toBe(Math.min(window, NATIVE_GPT56_MAX_INPUT_TOKENS)); + expect(entry.max_context_window).toBe(expected); + } }); test("the advertised native window stays inside the measured ceiling after Codex spends 95% of it", () => { @@ -115,7 +163,7 @@ describe("native GPT model toggles (bare slugs in disabledModels)", () => { // turn_context.rs). Shipping 1,050,000 here meant a 997,500-token budget against a // ceiling measured at 922,000 — the client filled past what the upstream accepts. const CODEX_EFFECTIVE_PERCENT = 0.95; - const MEASURED_CEILING = 922_000; // 921,508 accepted / 922,013 refused, 2026-08-17 + const MEASURED_CEILING = NATIVE_GPT56_MAX_INPUT_TOKENS; // 921,508 accepted / 922,013 refused, 2026-08-17 const rows = nativeModelRows({}); const gpt56 = rows.filter(row => row.slug.startsWith("gpt-5.6-") || row.slug.includes("daybreak")); expect(gpt56.length).toBeGreaterThan(0); @@ -125,7 +173,7 @@ describe("native GPT model toggles (bare slugs in disabledModels)", () => { } // And the window is a cap held under the ceiling, not back-solved to sit right on it: // 970,000 would pass the check above (921,500) while leaving no room at all. - expect(rows.find(row => row.slug === "gpt-5.6-sol")?.contextWindow).toBe(272_000); + expect(rows.find(row => row.slug === "gpt-5.6-sol")?.contextWindow).toBe(NATIVE_GPT56_CONTEXT_WINDOW); }); test("the native /api/models rows carry the input ceiling, not just the window", async () => { @@ -133,14 +181,14 @@ describe("native GPT model toggles (bare slugs in disabledModels)", () => { // reports only the window tells the dashboard the whole thing is usable as input. const rows = nativeModelRows({}); const sol = rows.find(row => row.slug === "gpt-5.6-sol"); - expect(sol?.contextWindow).toBe(272_000); - expect(sol?.maxInputTokens).toBe(272_000); + expect(sol?.contextWindow).toBe(NATIVE_GPT56_CONTEXT_WINDOW); + expect(sol?.maxInputTokens).toBe(NATIVE_GPT56_CONTEXT_WINDOW); // A cap lowers both numbers together — an input ceiling above the capped window would // be nonsense. - const capped = nativeModelRows({ providerContextCaps: { openai: 272_000 } }); + const capped = nativeModelRows({ providerContextCaps: { openai: NATIVE_GPT56_CONTEXT_WINDOW } }); const cappedSol = capped.find(row => row.slug === "gpt-5.6-sol"); - expect(cappedSol?.contextWindow).toBe(272_000); - expect(cappedSol?.maxInputTokens).toBe(272_000); + expect(cappedSol?.contextWindow).toBe(NATIVE_GPT56_CONTEXT_WINDOW); + expect(cappedSol?.maxInputTokens).toBe(NATIVE_GPT56_CONTEXT_WINDOW); // A native model with no separate ceiling keeps reporting just its window. const gpt55 = rows.find(row => row.slug === "gpt-5.5"); expect(gpt55?.contextWindow).toBe(272_000); @@ -148,15 +196,15 @@ describe("native GPT model toggles (bare slugs in disabledModels)", () => { }); test("the native 1M switch raises the Codex 272k default up to the measured ceiling", () => { - const raised = nativeModelRows({ providerContextCaps: { openai: 922_000 } }); + const raised = nativeModelRows({ providerContextCaps: { openai: NATIVE_GPT56_MAX_INPUT_TOKENS } }); expect(raised.find(r => r.slug === "gpt-5.6-sol")).toMatchObject({ - contextWindow: 922_000, - maxInputTokens: 922_000, + contextWindow: NATIVE_GPT56_MAX_INPUT_TOKENS, + maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS, }); - expect(raised.find(r => r.slug === "gpt-5.6-luna")?.contextWindow).toBe(922_000); + expect(raised.find(r => r.slug === "gpt-5.6-luna")?.contextWindow).toBe(NATIVE_GPT56_MAX_INPUT_TOKENS); // A value above the ceiling clamps; gpt-5.5 cannot be invented wider. const over = nativeModelRows({ providerContextCaps: { openai: 2_000_000 } }); - expect(over.find(r => r.slug === "gpt-5.6-sol")?.contextWindow).toBe(922_000); + expect(over.find(r => r.slug === "gpt-5.6-sol")?.contextWindow).toBe(NATIVE_GPT56_MAX_INPUT_TOKENS); expect(raised.find(r => r.slug === "gpt-5.5")?.contextWindow).toBe(272_000); expect(raised.find(r => r.slug === "gpt-5.4")?.contextWindow).toBe(1_000_000); }); @@ -164,15 +212,15 @@ describe("native GPT model toggles (bare slugs in disabledModels)", () => { test("nativeModelRows applies providerContextCaps.openai as a ceiling (#1430)", () => { const rows = nativeModelRows({ disabledModels: [], - providerContextCaps: { openai: 272_000 }, + providerContextCaps: { openai: NATIVE_GPT56_CONTEXT_WINDOW }, }); - expect(rows.find(r => r.slug === "gpt-5.6-sol")?.contextWindow).toBe(272_000); - expect(rows.find(r => r.slug === "gpt-5.6-luna")?.contextWindow).toBe(272_000); + expect(rows.find(r => r.slug === "gpt-5.6-sol")?.contextWindow).toBe(NATIVE_GPT56_CONTEXT_WINDOW); + expect(rows.find(r => r.slug === "gpt-5.6-luna")?.contextWindow).toBe(NATIVE_GPT56_CONTEXT_WINDOW); // gpt-5.5 (272k native) is unchanged by the same cap. expect(rows.find(r => r.slug === "gpt-5.5")?.contextWindow).toBe(272_000); // A cap for another provider leaves natives untouched. const other = nativeModelRows({ providerContextCaps: { "openai-apikey": 128_000 } }); - expect(other.find(r => r.slug === "gpt-5.6-sol")?.contextWindow).toBe(272_000); + expect(other.find(r => r.slug === "gpt-5.6-sol")?.contextWindow).toBe(NATIVE_GPT56_CONTEXT_WINDOW); }); test("native aliases suppress their native dashboard row and activate Desktop allowlist pruning", () => { @@ -398,12 +446,13 @@ describe("native GPT model toggles (bare slugs in disabledModels)", () => { }); test("native metadata helpers trust only marked, well-shaped account rows", () => { + const fixtureWindow = 128_000; const trusted = { slug: "side/gpt-5.6-luna", opencodex_catalog_kind: CODEX_ACCOUNT_BOUND_CATALOG_KIND, - context_window: 128_000, - max_context_window: 128_000, - auto_compact_token_limit: 115_200, + context_window: fixtureWindow, + max_context_window: fixtureWindow, + auto_compact_token_limit: Math.floor(fixtureWindow * 0.9), multi_agent_version: "v2", }; const malformed = { @@ -420,19 +469,19 @@ describe("native GPT model toggles (bare slugs in disabledModels)", () => { applyNativeOpenAiContextOverride(malformed); applyNativeOpenAiContextOverride(unmarked); expect(trusted).toMatchObject({ - context_window: 272_000, - max_context_window: 272_000, - auto_compact_token_limit: 244_800, + context_window: NATIVE_GPT56_CONTEXT_WINDOW, + max_context_window: NATIVE_GPT56_MAX_CONTEXT_WINDOW, + auto_compact_token_limit: Math.floor(NATIVE_GPT56_CONTEXT_WINDOW * 0.9), }); expect(malformed).toMatchObject({ - context_window: 128_000, - max_context_window: 128_000, - auto_compact_token_limit: 115_200, + context_window: fixtureWindow, + max_context_window: fixtureWindow, + auto_compact_token_limit: Math.floor(fixtureWindow * 0.9), }); expect(unmarked).toMatchObject({ - context_window: 128_000, - max_context_window: 128_000, - auto_compact_token_limit: 115_200, + context_window: fixtureWindow, + max_context_window: fixtureWindow, + auto_compact_token_limit: Math.floor(fixtureWindow * 0.9), }); applyMultiAgentMode([trusted, malformed, unmarked], "default");