Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,15 @@ and `modelAutoCompactTokenLimits` can narrow its window and soft compaction budg
`modelAutoCompactTokenLimits: { "gpt-6-astra": 700000 }` lowers the long-window default of 784,800.
An explicit smaller provider cap or target limit still wins, including native-alias combos.

Without a configured hard limit, the native catalog keeps the advertised `max_context_window`
separate from the default `context_window` (for Astra, 872,000 versus 272,000). Compaction still
uses the effective default window. To give one model 500,000 tokens and the others 300,000, set
`providers.openai.contextWindow` to `300000` and
`providers.openai.modelContextWindows` to `{ "gpt-6.1-sol": 500000 }`.
The per-model value overrides the provider window; `providerContextCaps.openai`, when enabled,
remains a ceiling for every native model and must be at least `500000` for this example.
Each model's own capacity still bounds the result.

The API row has 1,050,000 context, 922,000 maximum input, 128,000 maximum output, text/image input,
and API reasoning efforts through `max`. OpenCodex's routed synthetic Ultra control retains its
existing wire-effort mapping; it is not an additional API effort. There is no Astra `-pro` alias.
Expand Down
2 changes: 1 addition & 1 deletion scripts/test-layout/layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"version": 1,
"root": "tests",
"explicitShards": ["layout-clients.json"],
"explicit": { "protocol-argon-thought-boundary.test.ts": "responses", "service-log.test.ts": "service", "messages-native-empty-inline.test.ts": "claude-integration",
"explicit": { "catalog-native-max-context.test.ts": "codex-integration", "protocol-argon-thought-boundary.test.ts": "responses", "service-log.test.ts": "service", "messages-native-empty-inline.test.ts": "claude-integration",
"owner-registry-isolation.test.ts": "config",
"codex-history-pending-wal.test.ts": "codex-integration", "responses-anthropic-billing-send-budget.test.ts": "responses", "anthropic-billing-ownership.test.ts": "adapters/anthropic", "server-anthropic-billing.test.ts": "server", "server-anthropic-billing-boundaries.test.ts": "server", "router-anthropic-billing-failover.test.ts": "routing", "anthropic-billing-refusal.test.ts": "adapters/anthropic", "messages-request-id-headers.test.ts": "claude-integration", "bounded-file-read.test.ts": "lib", "responses-undeclared-tool-guard-inbound.test.ts": "responses", "explicit-empty-tool-catalog.test.ts": "responses", "explicit-empty-tool-catalog-history.test.ts": "responses",
"messages-request-id-endpoint.test.ts": "claude-integration",
Expand Down
20 changes: 11 additions & 9 deletions src/codex/catalog/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,14 @@ function asLimits(input: NativeContextLimitsInput): NativeContextLimits {
return typeof input === "number" ? { cap: input } : input;
}

/** Configured hard ceiling only; an advertised default window is not a user limit. */
export function nativeOpenAiConfiguredContextLimit(slug: string, input?: NativeContextLimitsInput): number | undefined {
const limits = asLimits(input);
const overlay = positiveInt(limits.modelWindows?.[slug]) ?? positiveInt(limits.providerWindow);
const cap = positiveInt(limits.cap);
return overlay === undefined ? cap : cap === undefined ? overlay : Math.min(overlay, cap);
}

/** Read both levers out of a config once, for call sites that hold one. */
export function nativeContextLimits(
config: Pick<OcxConfig, "providers" | "providerContextCaps">,
Expand Down Expand Up @@ -326,18 +334,12 @@ function longWindowOptInCeiling(slug: string): number | undefined {

function narrowToLimits(raw: number | undefined, slug: string, input: NativeContextLimitsInput): number | undefined {
if (raw === undefined) return undefined;
const limits = asLimits(input);
const overlay = positiveInt(limits.modelWindows?.[slug]) ?? positiveInt(limits.providerWindow);
const cap = positiveInt(limits.cap);
const limit = nativeOpenAiConfiguredContextLimit(slug, input);
const optInCeiling = longWindowOptInCeiling(slug);
if (optInCeiling !== undefined) {
const ceiling = optInCeiling;
const chosen = overlay ?? cap ?? raw;
const window = Math.min(chosen, ceiling);
return overlay !== undefined && cap !== undefined ? Math.min(window, cap) : window;
return Math.min(limit ?? raw, optInCeiling);
}
const narrowed = overlay === undefined ? raw : Math.min(raw, overlay);
return applyProviderContextCap(narrowed, cap) ?? narrowed;
return applyProviderContextCap(raw, limit);
}

export function nativeOpenAiContextWindow(slug: string, limits?: NativeContextLimitsInput): number | undefined {
Expand Down
14 changes: 5 additions & 9 deletions src/codex/catalog/parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,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, hasNativeOpenAiCapabilityMetadata, nativeMultiAgentVersion, nativeOpenAiAutoCompactTokenLimit, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, type NativeContextLimitsInput } from "./metadata";
import { NATIVE_OPENAI_CONTEXT_OVERRIDES, SUPPORTED_NATIVE_OPENAI_SLUGS, UPSTREAM_NATIVE_ENTRIES, hasNativeOpenAiCapabilityMetadata, nativeMultiAgentVersion, nativeOpenAiAutoCompactTokenLimit, nativeOpenAiConfiguredContextLimit, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, type NativeContextLimitsInput } from "./metadata";
import { clampAutoCompactTokenLimit } from "../../providers/auto-compact-budget";
import { trustedAccountBoundNativeCatalogSlug } from "./account-models";
import { CODEX_NATIVE_ALIAS_CATALOG_KIND } from "./kinds";
Expand Down Expand Up @@ -506,20 +506,15 @@ export function isNativeOpenAiEntry(entry: RawEntry): boolean {
* Narrow any already-resolved native window by the user levers.
*
* Used for the fields the accessors do not own (`max_context_window`, and preserved rows
* that carry no static override) so every field on a row lands at the same width.
* that carry no static override). The advertised maximum may exceed the default window.
*/
function narrowNativeMaxContextWindow(
slug: string,
value: number | undefined,
limits?: NativeContextLimitsInput,
): number | undefined {
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.
if (resolved === undefined || authoritative === undefined) return value;
return Math.min(value, Math.max(resolved, 1));
return applyProviderContextCap(value, nativeOpenAiConfiguredContextLimit(slug, limits));
}

export function applyNativeOpenAiContextOverride(entry: RawEntry, limits?: NativeContextLimitsInput): void {
Expand Down Expand Up @@ -636,7 +631,8 @@ export function ensureStrictCatalogFields(
if (
typeof entry.max_context_window !== "number"
|| entry.max_context_window <= 0
|| ((options.isRouted === true || !isNativeOpenAiEntry(entry)) && entry.max_context_window > contextWindow)
|| ((options.isRouted === true || (!isNativeOpenAiEntry(entry)
&& trustedAccountBoundNativeCatalogSlug(entry) === undefined)) && entry.max_context_window > contextWindow)
) {
entry.max_context_window = contextWindow;
}
Expand Down
13 changes: 13 additions & 0 deletions structure/catalog-vendor-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,16 @@ not `context_window`, so routing requirements still see unknown capacity. Existi
modality/tool evidence remains usable. Shared client catalogs, Claude context markers and exports
consume the estimated window; operators must configure a lower window or Context cap when a relay
serves less. Tests: `tests/codex-integration/vendor-context.test.ts`.

## Native maximum windows

`src/codex/catalog/parsing.ts` preserves native `max_context_window` independently of the
default `context_window`. Only configured hard limits narrow that maximum: the exact model's
window overrides the provider window, then the provider context cap bounds either. Both paths
share validation and precedence through `nativeOpenAiConfiguredContextLimit` in
`src/codex/catalog/metadata.ts`. Strict normalization preserves the maximum for trusted
account-selector native rows too; ordinary routed rows remain bounded by their effective window.
Compaction and max input still follow the effective default
window, including retained lower soft budgets. Native capacity remains defined by the
[native passthrough contract](catalog.md#native-passthrough). Covered by
`tests/codex-integration/catalog-native-max-context.test.ts`.
2 changes: 1 addition & 1 deletion structure/catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ it does not relabel HTTP model listings or virtual `*-pro` rows.

Astra has its own pinned native row: 272,000 default context, 872,000 opt-in ceiling,
low-through-ultra effort, low default, and native multi-agent effort `xhigh`. The native-alias
fallback passes the same configured limits to context, max input and compaction. Unrelated routed
fallback passes configured limits to context, max input and compaction; [maximum windows](catalog-vendor-context.md#native-maximum-windows) remain separate. Unrelated routed
templates clear the native multi-agent effort; canonical Astra-forward custom rows retain it and
the pinned Fast speed description. Sync repairs only the exact old built-in Astra Fast description,
preserving custom descriptions and other stored row fields.
Expand Down
109 changes: 109 additions & 0 deletions tests/codex-integration/catalog-native-max-context.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { describe, expect, test } from "bun:test";
import { CODEX_ACCOUNT_BOUND_CATALOG_KIND } from "../../src/codex/catalog/account-models";
import { nativeContextLimits, type NativeContextLimitsInput } from "../../src/codex/catalog/metadata";
import { applyNativeOpenAiContextOverride, ensureStrictCatalogFields, type RawEntry } from "../../src/codex/catalog/parsing";

function nativeRow(slug = "gpt-6-astra"): RawEntry {
return { slug, context_window: 272_000, max_context_window: 872_000 };
}

describe("#7075 native advertised maximum is independent of the default window", () => {
test.each([
["gpt-6-astra", 272_000, 872_000],
["gpt-6-sol", 272_000, 872_000],
["gpt-6-luna", 272_000, 872_000],
["gpt-6.1-sol", 500_000, 872_000],
["gpt-5.6-sol", 272_000, 922_000],
["gpt-5.6-terra", 272_000, 922_000],
["gpt-5.6-luna", 272_000, 922_000],
])("%s retains its maximum without changing default compaction", (slug, window, maximum) => {
const row = nativeRow(slug);
applyNativeOpenAiContextOverride(row, nativeContextLimits({}));
expect(row).toMatchObject({
context_window: window,
max_context_window: maximum,
auto_compact_token_limit: Math.floor(window * 0.9),
});
applyNativeOpenAiContextOverride(row, nativeContextLimits({}));
expect(row.max_context_window).toBe(maximum);
});

test.each([
[undefined, 272_000, 872_000],
[{}, 272_000, 872_000],
[{ cap: 0 }, 272_000, 872_000],
[{ cap: NaN, providerWindow: -1 }, 272_000, 872_000],
[{ cap: 1.5, modelWindows: { "gpt-6-astra": Infinity } }, 272_000, 872_000],
[{ modelAutoCompactTokenLimits: { "gpt-6-astra": 100_000 } }, 272_000, 872_000],
[200_000, 200_000, 200_000],
[{ cap: 272_000 }, 272_000, 272_000],
[{ cap: 500_000 }, 500_000, 500_000],
[{ cap: 2_000_000 }, 872_000, 872_000],
[{ providerWindow: 300_000 }, 300_000, 300_000],
[{ modelWindows: { "gpt-6-astra": 272_000 } }, 272_000, 272_000],
[{ providerWindow: 300_000, modelWindows: { "gpt-6-astra": 500_000 } }, 500_000, 500_000],
[{ cap: 350_000, modelWindows: { "gpt-6-astra": 500_000 } }, 350_000, 350_000],
] satisfies [NativeContextLimitsInput, number, number][])("limits %j preserve cap precedence", (limits, window, maximum) => {
const row = nativeRow();
applyNativeOpenAiContextOverride(row, limits);
expect(row.context_window).toBe(window);
expect(row.max_context_window).toBe(maximum);
expect(row.auto_compact_token_limit).toBe(
typeof limits === "object" && limits?.modelAutoCompactTokenLimits ? 100_000 : Math.floor(window * 0.9),
);
});

test("a per-model window leaves siblings at their defaults under a provider fallback", () => {
const limits = nativeContextLimits({ providers: { openai: {
contextWindow: 300_000,
modelContextWindows: { "gpt-6.1-sol": 500_000 },
} } });
const sol = nativeRow("gpt-6.1-sol");
const astra = nativeRow();
applyNativeOpenAiContextOverride(sol, limits);
applyNativeOpenAiContextOverride(astra, limits);
expect(sol).toMatchObject({ context_window: 500_000, max_context_window: 500_000 });
expect(astra).toMatchObject({ context_window: 300_000, max_context_window: 300_000 });
});

test("a sibling's per-model setting and a retained soft budget do not cap the maximum", () => {
const row = { ...nativeRow(), auto_compact_token_limit: 100_000 };
applyNativeOpenAiContextOverride(row, { modelWindows: { "gpt-6-sol": 300_000 } });
expect(row).toMatchObject({
context_window: 272_000, max_context_window: 872_000, auto_compact_token_limit: 100_000,
});
});

test("account-qualified native rows use the same maximum and exact model limits", () => {
const row = { ...nativeRow("work/gpt-6-astra"), opencodex_catalog_kind: CODEX_ACCOUNT_BOUND_CATALOG_KIND };
applyNativeOpenAiContextOverride(row);
expect(row.max_context_window).toBe(872_000);
applyNativeOpenAiContextOverride(row, { modelWindows: { "gpt-6-astra": 300_000 } });
expect(row).toMatchObject({ context_window: 300_000, max_context_window: 300_000 });
});

test("preserved native rows without static metadata only narrow for configured limits", () => {
const row = nativeRow("future-native-model");
applyNativeOpenAiContextOverride(row);
expect(row).toMatchObject({ context_window: 272_000, max_context_window: 872_000 });
applyNativeOpenAiContextOverride(row, { cap: 300_000 });
expect(row).toMatchObject({ context_window: 272_000, max_context_window: 300_000 });
});

test.each([
[CODEX_ACCOUNT_BOUND_CATALOG_KIND, "work/gpt-6-astra", false, 872_000],
[undefined, "work/gpt-6-astra", false, 272_000],
[CODEX_ACCOUNT_BOUND_CATALOG_KIND, "work/nested/gpt-6-astra", false, 272_000],
[CODEX_ACCOUNT_BOUND_CATALOG_KIND, "work/gpt-6-astra", true, 272_000],
])("strict normalization preserves only trusted native selector maxima (%s, %s, routed=%s)", (kind, slug, isRouted, maximum) => {
const row = { ...nativeRow(slug), opencodex_catalog_kind: kind };
ensureStrictCatalogFields(row, { isRouted });
expect(row).toMatchObject({ context_window: 272_000, max_context_window: maximum });
});

test("configured values above a short model's capacity never raise its maximum", () => {
const row = nativeRow("gpt-5.5");
applyNativeOpenAiContextOverride(row, { providerWindow: 500_000 });
expect(row).toMatchObject({ context_window: 272_000, max_context_window: 272_000 });
});
});
5 changes: 2 additions & 3 deletions tests/codex-integration/codex-catalog-restore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,11 +415,10 @@ describe("Codex catalog restore", () => {
expect(synced.map(m => m.slug)).toContain("gpt-5.6-luna");
// gpt-5.4 is no longer a native catalog member, and no surviving native has a 1M window.
expect(synced.map(m => m.slug)).not.toContain("gpt-5.4");
// No long-window opt-in: sync narrows the emitted max to Astra's default window.
// The pinned 872k ceiling is available only when the operator enables the larger window.
// Without a configured hard limit, sync keeps Astra's default and advertised maximum separate.
expect(synced.find(m => m.slug === "gpt-6-astra")).toMatchObject({
context_window: 272_000,
max_context_window: 272_000,
max_context_window: 872_000,
auto_compact_token_limit: 244_800,
});
}, { timeout: 15_000 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ describe("Codex catalog sync hardening", () => {
const rows = JSON.parse(readFileSync(catalogPath, "utf8")).models as Array<Record<string, unknown>>;
expect(rows.find(row => row.slug === "team/gpt-daybreak-blue-latest")).toMatchObject({
context_window: 272_000,
max_context_window: 272_000,
max_context_window: 922_000,
auto_compact_token_limit: 244_800,
comp_hash: "3000",
tool_mode: "code_mode_only",
Expand Down
6 changes: 3 additions & 3 deletions tests/codex-integration/codex-catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3531,7 +3531,7 @@ describe("Codex catalog routed normalization", () => {
"low", "medium", "high", "xhigh", "max", "ultra",
]);
expect(gpt56?.context_window).toBe(272_000);
expect(gpt56?.max_context_window).toBe(272_000);
expect(gpt56?.max_context_window).toBe(922_000);
expect(gpt56?.auto_compact_token_limit).toBe(244_800);
expect((gpt55?.supported_reasoning_levels as { effort: string }[]).map(l => l.effort)).toEqual([
"low", "medium", "high", "xhigh", "max", "ultra",
Expand Down Expand Up @@ -4321,12 +4321,12 @@ describe("Codex catalog routed normalization", () => {

expect(bare).toMatchObject({
context_window: 272_000,
max_context_window: 272_000,
max_context_window: 922_000,
auto_compact_token_limit: 120_000,
});
expect(account).toMatchObject({
context_window: 272_000,
max_context_window: 272_000,
max_context_window: 922_000,
auto_compact_token_limit: 120_000,
opencodex_catalog_kind: CODEX_ACCOUNT_BOUND_CATALOG_KIND,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ test("convergence preserves one configured soft budget on bare and account-nativ
for (const slug of ["gpt-5.6-sol", "desktop/gpt-5.6-sol", "team/gpt-5.6-sol"]) {
expect(models.find(entry => entry.slug === slug)).toMatchObject({
context_window: 272_000,
max_context_window: 272_000,
max_context_window: 922_000,
auto_compact_token_limit: 120_000,
});
}
Expand Down Expand Up @@ -496,7 +496,7 @@ test("convergence projects the observed Daybreak row onto its selector and one b
visibility: "list",
opencodex_catalog_kind: CODEX_ACCOUNT_BOUND_CATALOG_KIND,
context_window: 272_000,
max_context_window: 272_000,
max_context_window: 922_000,
auto_compact_token_limit: 244_800,
comp_hash: "3000",
tool_mode: "code_mode_only",
Expand Down
4 changes: 2 additions & 2 deletions tests/codex-integration/discovered-native-models.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ describe("authenticated native discovery", () => {
const future = built.find(entry => entry.slug === FUTURE)!;
expect(future).toMatchObject({ display_name: row().display_name, description: row().description,
supported_reasoning_levels: row().supported_reasoning_levels, default_reasoning_level: "high",
context_window: 123_000, max_context_window: 123_000, base_instructions: "Upstream future instructions" });
context_window: 123_000, max_context_window: 456_000, base_instructions: "Upstream future instructions" });
expect(built.filter(entry => entry.slug === FUTURE)).toHaveLength(1);
expect(nativeOpenAiContextTier(FUTURE)).toEqual({ defaultWindow: 123_000, longWindow: 456_000 });
expect(nativeOpenAiContextWindow(FUTURE, { modelWindows: { [FUTURE]: 1_000_000 } })).toBe(456_000);
Expand Down Expand Up @@ -141,7 +141,7 @@ describe("authenticated native discovery", () => {
expect(nativeOpenAiContextTier("gpt-6.1-sol")).toEqual({ defaultWindow: 500_000, longWindow: 872_000 });
expect(nativeOpenAiContextWindow("gpt-6.1-sol", { modelWindows: { "gpt-6.1-sol": 1_000_000 } })).toBe(872_000);
expect(buildCatalogEntries(null, ["gpt-6.1-sol"], []).find(entry => entry.slug === "gpt-6.1-sol"))
.toMatchObject({ context_window: 500_000, max_context_window: 500_000, auto_compact_token_limit: 450_000 });
.toMatchObject({ context_window: 500_000, max_context_window: 872_000, auto_compact_token_limit: 450_000 });
});

test("failed or empty roster fetches cannot register models", async () => {
Expand Down
2 changes: 1 addition & 1 deletion tests/codex-integration/gpt6-native-rows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ test("GPT-6 Sol, Luna and Astra keep their 272k default and 872k long window", (
expect(nativeOpenAiContextTier(slug)).toEqual({ defaultWindow: 272_000, longWindow: 872_000 });
expect(nativeOpenAiContextWindow(slug, { modelWindows: { [slug]: 872_000 } })).toBe(872_000);
expect(entries.find(entry => entry.slug === slug)).toMatchObject({
context_window: 272_000, max_context_window: 272_000, auto_compact_token_limit: 244_800,
context_window: 272_000, max_context_window: 872_000, auto_compact_token_limit: 244_800,
});
}
});
Expand Down
Loading
Loading