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
10 changes: 6 additions & 4 deletions src/codex/catalog/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,12 @@ const PINNED_NATIVE_CAPABILITY_ENTRIES: Map<string, RawEntry> = new Map(
.map(m => [m.slug as string, m]),
);

export function nativeOpenAiContextWindow(slug: string): number | undefined {
return NATIVE_OPENAI_CONTEXT_OVERRIDES[slug]?.contextWindow
export function nativeOpenAiContextWindow(slug: string, contextCap?: number): number | undefined {
const raw = NATIVE_OPENAI_CONTEXT_OVERRIDES[slug]?.contextWindow
?? (typeof PINNED_NATIVE_CAPABILITY_ENTRIES.get(slug)?.context_window === "number"
? PINNED_NATIVE_CAPABILITY_ENTRIES.get(slug)!.context_window as number
: undefined);
return applyProviderContextCap(raw, contextCap) ?? raw;
}

export function nativeInputModalities(slug: string): string[] {
Expand Down Expand Up @@ -199,11 +200,12 @@ export function desktopVisibleNativeSlugs(config: Pick<OcxConfig, "claudeCode" |
return visibleNativeSlugs(config);
}

export function nativeModelRows(config: Pick<OcxConfig, "disabledModels" | "combos">): Array<{ slug: string; disabled: boolean; contextWindow?: number }> {
export function nativeModelRows(config: Pick<OcxConfig, "disabledModels" | "combos" | "providerContextCaps">): Array<{ slug: string; disabled: boolean; contextWindow?: number }> {
const disabled = disabledNativeSlugs(config);
const shadowed = configuredNativeAliasSlugs(config);
const openaiContextCap = providerContextCap(config, OPENAI_CODEX_PROVIDER_ID);
return NATIVE_OPENAI_MODELS.filter(slug => !shadowed.has(slug)).map(slug => {
const contextWindow = nativeOpenAiContextWindow(slug);
const contextWindow = nativeOpenAiContextWindow(slug, openaiContextCap);
return { slug, disabled: disabled.has(slug), ...(contextWindow !== undefined ? { contextWindow } : {}) };
});
}
Expand Down
30 changes: 23 additions & 7 deletions src/codex/catalog/parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,18 +259,34 @@ export function isNativeOpenAiEntry(entry: RawEntry): boolean {
return typeof entry.slug === "string" && !entry.slug.includes("/");
}

export function applyNativeOpenAiContextOverride(entry: RawEntry): void {
export function applyNativeOpenAiContextOverride(entry: RawEntry, contextCap?: number): void {
const nativeSlug = trustedAccountBoundNativeCatalogSlug(entry)
?? (isNativeOpenAiEntry(entry) ? entry.slug as string : undefined);
if (!nativeSlug) return;
const override = NATIVE_OPENAI_CONTEXT_OVERRIDES[nativeSlug];
if (!override) return;
if (typeof override.contextWindow === "number") {
entry.context_window = override.contextWindow;
entry.auto_compact_token_limit = Math.floor(override.contextWindow * 0.9);
if (override) {
if (typeof override.contextWindow === "number") {
const contextWindow = applyProviderContextCap(override.contextWindow, contextCap) ?? override.contextWindow;
entry.context_window = contextWindow;
entry.auto_compact_token_limit = Math.floor(contextWindow * 0.9);
}
if (typeof override.maxContextWindow === "number") {
entry.max_context_window = applyProviderContextCap(override.maxContextWindow, contextCap) ?? override.maxContextWindow;
}
}
// providerContextCaps.openai is a ceiling for native OpenAI rows regardless of where the
// advertised window came from (#1430): preserved rows without a hardcoded override (e.g.
// gpt-5.4-mini) must stay under the cap too, and auto-compaction follows the capped window.
const currentContext = typeof entry.context_window === "number" ? entry.context_window : undefined;
const cappedContext = applyProviderContextCap(currentContext, contextCap);
if (cappedContext !== currentContext && typeof cappedContext === "number") {
entry.context_window = cappedContext;
entry.auto_compact_token_limit = Math.floor(cappedContext * 0.9);
}
if (typeof override.maxContextWindow === "number") {
entry.max_context_window = override.maxContextWindow;
const currentMax = typeof entry.max_context_window === "number" ? entry.max_context_window : undefined;
const cappedMax = applyProviderContextCap(currentMax, contextCap);
if (cappedMax !== currentMax) {
entry.max_context_window = cappedMax;
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/codex/catalog/provider-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1639,6 +1639,7 @@ async function gatherRoutedModelsUncached(
// configs that will never need it.
} else {
const disabled = disabledNativeSlugs(config);
const openaiContextCap = providerContextCap(config, OPENAI_CODEX_PROVIDER_ID);
const requiredNativeComboTargets = new Set(listComboIds(config).flatMap(id => {
const combo = getCombo(config, id);
return combo?.targets.flatMap(target => (
Expand All @@ -1649,7 +1650,7 @@ async function gatherRoutedModelsUncached(
// A bare native disable key hides the native row, not a combo that targets it.
// Keep synthetic native metadata available to those combos.
if (disabled.has(slug) && !requiredNativeComboTargets.has(slug)) continue;
const contextWindow = nativeOpenAiContextWindow(slug);
const contextWindow = nativeOpenAiContextWindow(slug, openaiContextCap);
if (contextWindow === undefined) continue;
const synthetic: CatalogModel = {
provider: "openai",
Expand Down
33 changes: 25 additions & 8 deletions src/codex/catalog/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type { NormalizedComboConfig } from "../../combos/types";
import { providerDestinationResolvedError } from "../../lib/destination-policy";
import { redactSecretString } from "../../lib/redact";
import upstreamModelsSnapshot from "../data/upstream-models.json";
import { OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";


import { CODEX_CUSTOM_MODEL_CATALOG_KIND, CODEX_PROVIDER_MODEL_CATALOG_KIND, activeCodexModelsCachePath, applyCatalogMetadata, applyMultiAgentMode, applyNativeOpenAiContextOverride, applyRoutedCodexToolMode, catalogBackupPathFor, catalogHasRoutedEntries, catalogModelSlug, ensureStrictCatalogFields, findNativeTemplate, isDefaultCatalogPath, isRoutedModelCompatibilityExcluded, legacyCatalogBackupPath, normalizeRoutedCatalogEntry, normalizeServiceTiers, readCatalog, readCatalogBackup, readCodexCatalogPath, readNativeBaseline } from "./parsing";
Expand Down Expand Up @@ -190,9 +191,9 @@ export function effectiveSubagentRoster(
return { candidates, advertised, excluded };
}

export function finishUpstreamNativeEntry(clone: RawEntry, priority: number): RawEntry {
export function finishUpstreamNativeEntry(clone: RawEntry, priority: number, contextCap?: number): RawEntry {
if (priority !== 9) clone.priority = priority;
applyNativeOpenAiContextOverride(clone);
applyNativeOpenAiContextOverride(clone, contextCap);
// GPT-5.6 natives keep their exact upstream ladders (e.g. luna has max but no ultra).
// Older natives (gpt-5.5 / 5.4 / 5.4-mini / 5.3-codex-spark) get mock max + ultra
// (wire-clamped to xhigh). Ultra is always advertised regardless of v2 toggle.
Expand Down Expand Up @@ -243,6 +244,7 @@ export function deriveEntry(
priority: number,
model?: CatalogModel,
exactComboSlugs: ReadonlySet<string> = new Set(),
contextCap?: number,
): RawEntry {
const preserveExact = isExactComboCatalogModel(model, exactComboSlugs);
const isRouted = model !== undefined;
Expand All @@ -251,7 +253,7 @@ export function deriveEntry(
// reasoning ladder — e.g. luna has no ultra — default effort, identity, model_messages)
// instead of cloning an older template.
const upstream = upstreamNativeEntry(slug);
if (upstream) return finishUpstreamNativeEntry(upstream, priority);
if (upstream) return finishUpstreamNativeEntry(upstream, priority, contextCap);
}
if (template) {
const e = JSON.parse(JSON.stringify(template)) as RawEntry;
Expand Down Expand Up @@ -286,7 +288,7 @@ export function deriveEntry(
applyCatalogModelMetadata(e, model);
if (model?.catalogKind) e.opencodex_catalog_kind = model.catalogKind;
} else {
applyNativeOpenAiContextOverride(e);
applyNativeOpenAiContextOverride(e, contextCap);
if (isGpt56NativeSlug(slug)) ensureGpt56ReasoningLevels(e);
else ensureUltraReasoningLevel(e);
// Non-5.6 natives (5.5, 5.4, 5.4-mini, spark) do not support responses-lite;
Expand Down Expand Up @@ -324,7 +326,7 @@ export function deriveEntry(
if (model && isRouted) applyCatalogMetadata(entry, model.provider, model.id, model.contextCap);
applyCatalogModelMetadata(entry, model);
if (model?.catalogKind) entry.opencodex_catalog_kind = model.catalogKind;
if (!isRouted) applyNativeOpenAiContextOverride(entry);
if (!isRouted) applyNativeOpenAiContextOverride(entry, contextCap);
return ensureStrictCatalogFields(normalizeServiceTiers(entry), {
preserveExactInputModalities: preserveExact,
isRouted,
Expand All @@ -343,6 +345,7 @@ export interface ObservedCatalogEntryBuildInput {
readonly suppressedBareNativeSlugs: ReadonlySet<string>;
readonly disabledNativeAccountSlugs: ReadonlySet<string>;
readonly multiAgentV2Enabled: boolean;
readonly openaiContextCap?: number;
}

/** Build entries with the process-observed Codex feature state. */
Expand All @@ -357,6 +360,7 @@ export function buildCatalogEntries(
accountSelectors: readonly string[] = [],
suppressedBareNativeSlugs: ReadonlySet<string> = new Set(),
disabledNativeAccountSlugs: ReadonlySet<string> = new Set(),
contextCap?: number,
): RawEntry[] {
return buildCatalogEntriesFromObservedState({
template,
Expand All @@ -370,6 +374,7 @@ export function buildCatalogEntries(
suppressedBareNativeSlugs,
disabledNativeAccountSlugs,
multiAgentV2Enabled: isMultiAgentV2Enabled(),
openaiContextCap: contextCap,
});
}

Expand All @@ -386,6 +391,7 @@ export function buildCatalogEntriesFromObservedState({
suppressedBareNativeSlugs,
disabledNativeAccountSlugs,
multiAgentV2Enabled,
openaiContextCap,
}: ObservedCatalogEntryBuildInput): RawEntry[] {
// Codex's models-manager sorts by `priority` ASC and advertises the first 5 picker-visible
// models to spawn_agent (sort_by_key(priority) + MAX_MODEL_OVERRIDES_IN_SPAWN_AGENT=5). Catalog
Expand Down Expand Up @@ -420,7 +426,7 @@ export function buildCatalogEntriesFromObservedState({
.filter(model => model.provider === COMBO_NAMESPACE)
.map(catalogModelSlug));
for (const slug of gptSlugs) {
const native = deriveEntry(template, slug, "OpenAI native model (Codex OAuth passthrough).", 9);
const native = deriveEntry(template, slug, "OpenAI native model (Codex OAuth passthrough).", 9, undefined, new Set(), openaiContextCap);
if (rank.has(slug)) native.priority = rank.get(slug)!;
nativeEntries.push(native);
const nativeAlias = nativeAliasesBySlug.get(slug);
Expand Down Expand Up @@ -623,6 +629,7 @@ export interface ObservedCatalogMergeInput {
readonly accountBoundEntries: readonly RawEntry[];
readonly suppressedBareNativeSlugs?: ReadonlySet<string>;
readonly policy: ObservedCatalogMergePolicy;
readonly openaiContextCap?: number;
}

/**
Expand Down Expand Up @@ -652,6 +659,7 @@ export function mergeCatalogEntriesFromObservedState({
accountBoundEntries,
suppressedBareNativeSlugs = new Set(),
policy,
openaiContextCap,
}: ObservedCatalogMergeInput): RawEntry[] {
// Raw catalog rows contain nested arrays/objects that normalization mutates. Detach every row at
// the observed-core boundary so callers can safely retain evidence objects or repeat the merge.
Expand Down Expand Up @@ -786,7 +794,7 @@ export function mergeCatalogEntriesFromObservedState({
// genuine catalog entry (real display name) is preserved untouched.
if (shouldUpgradeToUpstreamEntry(m)) {
const upstream = upstreamNativeEntry(slug)!;
const finished = finishUpstreamNativeEntry(upstream, 9);
const finished = finishUpstreamNativeEntry(upstream, 9, openaiContextCap);
finished.priority = nativePriority(slug, upstream.priority);
return finished;
}
Expand Down Expand Up @@ -815,6 +823,9 @@ export function mergeCatalogEntriesFromObservedState({
slug,
"OpenAI native model (Codex OAuth passthrough).",
nativePriority(slug, upstreamNativeEntry(slug)?.priority),
undefined,
new Set(),
openaiContextCap,
);
entry.priority = nativePriority(slug, upstreamNativeEntry(slug)?.priority);
native.push(entry);
Expand Down Expand Up @@ -927,7 +938,7 @@ export function mergeCatalogEntriesFromObservedState({
const managedEntries = [...finalRoutedEntries, ...alignedAccountBoundEntries];
const mergedEntries = [...native, ...managedEntries].map(m => {
const normalized = normalizeServiceTiers(m);
if (!isNativeAliasCatalogEntry(normalized)) applyNativeOpenAiContextOverride(normalized);
if (!isNativeAliasCatalogEntry(normalized)) applyNativeOpenAiContextOverride(normalized, openaiContextCap);
const exactCombo = isExactComboCatalogEntry(m, exactComboSlugs);
const e = ensureStrictCatalogFields(normalized, {
preserveExactInputModalities: exactCombo,
Expand Down Expand Up @@ -997,6 +1008,7 @@ export function mergeCatalogEntriesForSync(
isNativeAliasCatalogEntry(entry) && typeof entry.slug === "string" ? [entry.slug] : []
)),
),
openaiContextCap?: number,
): RawEntry[] {
// Retained for source compatibility with the original helper contract. Raw provider ids must
// not suppress same-named native rows; actual admitted combo entries own that decision now.
Expand Down Expand Up @@ -1031,6 +1043,7 @@ export function mergeCatalogEntriesForSync(
includeNativeOpenAi,
accountBoundEntries,
suppressedBareNativeSlugs,
openaiContextCap,
policy: {
...CANONICAL_NATIVE_CATALOG_CONTENT_POLICY,
warningPolicy: "emit",
Expand Down Expand Up @@ -1248,6 +1261,7 @@ function writeRetainedCatalogSync({
const hasPhysicalComboProvider = Object.hasOwn(config.providers, COMBO_NAMESPACE);
const includeNativeOpenAi = shouldIncludeNativeOpenAi(config);
const includeAccountBoundNativeOpenAi = shouldIncludeAccountBoundNativeOpenAi(config);
const openaiContextCap = providerContextCap(config, OPENAI_CODEX_PROVIDER_ID);
const accountSelectors = includeAccountBoundNativeOpenAi
? visibleCodexAccountSelectors(config)
: [];
Expand All @@ -1265,6 +1279,7 @@ function writeRetainedCatalogSync({
suppressedBareNativeSlugs,
disabledNativeAccountSlugs: new Set(),
multiAgentV2Enabled,
openaiContextCap,
});
// Keep genuine native entries (gpt-*, codex-*) with their real per-model fields and append
// routed providers as namespaced slugs. Cursor and other adopted providers can expose model ids
Expand Down Expand Up @@ -1309,6 +1324,7 @@ function writeRetainedCatalogSync({
suppressedBareNativeSlugs,
disabledNativeAccountSlugs: new Set([...disabledNativeSlugs(config)].filter(slug => suppressedBareNativeSlugs.has(slug))),
multiAgentV2Enabled,
openaiContextCap,
}).filter(entry => trustedAccountBoundNativeCatalogSlug(entry) !== undefined)
: [];
catalog.models = mergeCatalogEntriesFromObservedState({
Expand All @@ -1331,6 +1347,7 @@ function writeRetainedCatalogSync({
includeNativeOpenAi,
accountBoundEntries,
suppressedBareNativeSlugs,
openaiContextCap,
policy: {
...CANONICAL_NATIVE_CATALOG_CONTENT_POLICY,
warningPolicy: "emit",
Expand Down
6 changes: 0 additions & 6 deletions src/providers/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,12 +300,6 @@ const MINIMAX_M3_REASONING_EFFORT_MAP: Record<string, string> = {
const OPENAI_GPT56_MODELS = ["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"];
const OPENAI_GPT56_PRO_MODELS = ["gpt-5.6-sol-pro", "gpt-5.6-terra-pro", "gpt-5.6-luna-pro"];
const OPENAI_API_GPT56_CONTEXT_WINDOW = 1_050_000;
const OPENAI_CODEX_GPT56_CONTEXT_WINDOW = 372_000;
const OPENAI_GPT56_CONTEXT_WINDOWS = {
"gpt-5.6-sol": OPENAI_CODEX_GPT56_CONTEXT_WINDOW,
"gpt-5.6-terra": OPENAI_CODEX_GPT56_CONTEXT_WINDOW,
"gpt-5.6-luna": OPENAI_CODEX_GPT56_CONTEXT_WINDOW,
};
const OPENAI_API_GPT56_CONTEXT_WINDOWS: Record<string, number> = {
...Object.fromEntries([...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS].map(id => [id, OPENAI_API_GPT56_CONTEXT_WINDOW])),
"gpt-5.5": OPENAI_API_GPT56_CONTEXT_WINDOW,
Expand Down
12 changes: 9 additions & 3 deletions src/routing/capability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
*/

import type { OcxConfig } from "../types";
import { isCanonicalOpenAiForwardProvider } from "../providers/openai-tiers";
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
import { applyProviderContextCap, providerContextCap } from "../providers/context-cap";
import { PROVIDER_REGISTRY } from "../providers/registry";
import {
nativeInputModalities,
Expand Down Expand Up @@ -145,13 +146,18 @@ export function candidateCapabilityEvidence(
const provider = config.providers[providerName];
const registryEntry = PROVIDER_REGISTRY.find(entry => entry.id === providerName);
const catalogRow = cachedCatalogModels().find(model => model.provider === providerName && model.id === modelId);
const isNative = providerName === "openai" && !modelId.includes("/");
const isNative = providerName === OPENAI_CODEX_PROVIDER_ID && !modelId.includes("/");

const contextWindow = provider?.modelContextWindows?.[modelId]
const rawContextWindow = provider?.modelContextWindows?.[modelId]
?? provider?.contextWindow
?? registryEntry?.modelContextWindows?.[modelId]
?? catalogRow?.contextWindow
?? (isNative ? nativeOpenAiContextWindow(modelId) : undefined);
// providerContextCaps.openai also ceilings native OpenAI rows (#1430), so routing
// evidence never contradicts a capped catalog entry.
const contextWindow = isNative
? applyProviderContextCap(rawContextWindow, providerContextCap(config, OPENAI_CODEX_PROVIDER_ID)) ?? rawContextWindow
: rawContextWindow;

const modalities = provider?.modelInputModalities?.[modelId]
?? registryEntry?.modelInputModalities?.[modelId]
Expand Down
17 changes: 15 additions & 2 deletions src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ import {
import { acquireServerBackgroundLifecycle } from "./background-lifecycle";
import { runOpenAiTierStartupMigration } from "../providers/openai-tier-startup";
import { runAlibabaRegionStartupMigration } from "../providers/alibaba-region-startup";
import { isCanonicalOpenAiForwardProvider } from "../providers/openai-tiers";
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
import { providerContextCap } from "../providers/context-cap";
import { providerCodexAccountMode } from "../providers/registry";
import type { StorageCleanupPolicy } from "../types";
import {
Expand Down Expand Up @@ -911,7 +912,19 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
const catalogNativeSlugs = accountSelectors.length > 0
? NATIVE_OPENAI_MODELS
: nativeSlugs;
const entries = buildCatalogEntries(loadCatalogTemplate(), catalogNativeSlugs, goOrdered, config.subagentModels, websocketsEnabled(config), maMode as "v1" | "default" | "v2", exactComboCatalogSlugs(config), accountSelectors, suppressedBareNativeSlugs);
const entries = buildCatalogEntries(
loadCatalogTemplate(),
catalogNativeSlugs,
goOrdered,
config.subagentModels,
websocketsEnabled(config),
maMode as "v1" | "default" | "v2",
exactComboCatalogSlugs(config),
accountSelectors,
suppressedBareNativeSlugs,
new Set(),
providerContextCap(config, OPENAI_CODEX_PROVIDER_ID),
);
return jsonResponse({
models: applyNativeVisibility(
entries,
Expand Down
Loading
Loading