@bitkyc08/opencodex 2.23.0-preview.20260816 → 2.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/index-CFqJKF2L.js +102 -0
- package/gui/dist/index.html +1 -1
- package/package.json +2 -2
- package/src/adapters/anthropic.ts +39 -7
- package/src/adapters/cursor/tool-definitions.ts +48 -0
- package/src/adapters/google.ts +18 -12
- package/src/adapters/openai-chat.ts +106 -13
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +3 -0
- package/src/bridge.ts +16 -5
- package/src/chat/inbound.ts +5 -11
- package/src/claude/context-windows.ts +20 -4
- package/src/claude/desktop-3p.ts +11 -6
- package/src/claude/inbound.ts +39 -1
- package/src/claude/model-info.ts +28 -8
- package/src/cli/account-api.ts +5 -1
- package/src/cli/claude-desktop.ts +3 -0
- package/src/cli/config-command.ts +37 -14
- package/src/codex/app-server-restart-service.ts +1 -1
- package/src/codex/auth-api.ts +5 -0
- package/src/codex/auth-context.ts +43 -2
- package/src/codex/catalog/metadata.ts +135 -12
- package/src/codex/catalog/native-models.ts +32 -2
- package/src/codex/catalog/parsing.ts +61 -13
- package/src/codex/catalog/provider-fetch.ts +37 -7
- package/src/codex/catalog/sync.ts +49 -25
- package/src/codex/catalog-refresh-status.ts +21 -3
- package/src/codex/catalog.ts +1 -1
- package/src/codex/convergence-types.ts +23 -2
- package/src/codex/desired-state.ts +1 -1
- package/src/codex/inject.ts +38 -7
- package/src/codex/injected-marker.ts +28 -0
- package/src/codex/journal.ts +40 -1
- package/src/codex/management-convergence.ts +55 -2
- package/src/codex/quota-rejection.ts +61 -1
- package/src/codex/quota.ts +60 -6
- package/src/codex/routing.ts +30 -3
- package/src/combos/failover.ts +20 -0
- package/src/config.ts +271 -4
- package/src/generated/compatibility-version.json +90 -78
- package/src/grok/sync.ts +3 -1
- package/src/lab/artifacts/sanitize.ts +1 -1
- package/src/lab/live/manifest.ts +1 -1
- package/src/lib/codex-restart-contract.ts +1 -1
- package/src/lib/config-ownership.ts +1 -0
- package/src/lib/errors.ts +9 -0
- package/src/lib/lab-activation.ts +1 -1
- package/src/lib/optional-shutdown-hooks.ts +1 -1
- package/src/lib/pinned-http.ts +7 -2
- package/src/lib/windows-elevation.ts +3 -3
- package/src/providers/quota.ts +10 -4
- package/src/providers/registry.ts +2 -2
- package/src/responses/parser.ts +42 -7
- package/src/responses/provider-opaque-metadata.ts +1 -1
- package/src/responses/thought-signature-replay.ts +261 -0
- package/src/router.ts +6 -1
- package/src/routing/compatibility/provider-slot.ts +1 -1
- package/src/routing/evaluator.ts +12 -2
- package/src/routing/health.ts +16 -5
- package/src/routing/history/schema.ts +1 -1
- package/src/routing/trace.ts +1 -1
- package/src/server/auth-cors.ts +96 -21
- package/src/server/chat-completions.ts +6 -2
- package/src/server/chat-native.ts +32 -6
- package/src/server/index.ts +5 -3
- package/src/server/management/agent-settings-routes.ts +26 -4
- package/src/server/management/config-routes.ts +79 -2
- package/src/server/management/context.ts +1 -1
- package/src/server/management/model-rows.ts +5 -0
- package/src/server/management/native-integration-routes.ts +4 -1
- package/src/server/management/provider-routes.ts +19 -0
- package/src/server/management/shared.ts +3 -3
- package/src/server/management-api.ts +13 -6
- package/src/server/passive-route-linker.ts +1 -1
- package/src/server/relay.ts +16 -0
- package/src/server/responses/compact.ts +10 -3
- package/src/server/responses/core.ts +160 -33
- package/src/server/responses/fetch-helpers.ts +34 -2
- package/src/server/responses/input-admission.ts +17 -9
- package/src/server/responses-undeclared-tool-guard.ts +153 -0
- package/src/server/system-env.ts +4 -2
- package/src/service.ts +22 -7
- package/src/types.ts +35 -1
- package/gui/dist/assets/index-Ch-YtWdA.js +0 -102
|
@@ -99,16 +99,54 @@ export function isUnsupportedOpenAiNativeSlug(slug: string): boolean {
|
|
|
99
99
|
return /^(?:gpt|codex)-/.test(slug);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Advertised context for the Codex-login native GPT-5.6 family.
|
|
104
|
+
*
|
|
105
|
+
* This is an OPERATING CAP, not the hard ceiling — the same shape upstream uses. The live
|
|
106
|
+
* catalog reports `context_window: 272000` against a `max_context_window: 872000` for these
|
|
107
|
+
* slugs, and gpt-5.4 runs 272,000 against 1,000,000: the advertised window is always well
|
|
108
|
+
* inside what the model can take.
|
|
109
|
+
*
|
|
110
|
+
* The hard ceiling here was measured on 2026-08-17 against a real Codex-login account:
|
|
111
|
+
* `POST /backend-api/codex/responses` admitted 921,508 input tokens and refused 922,013 with
|
|
112
|
+
* `error.code: context_length_exceeded` on sol, terra and luna alike.
|
|
113
|
+
*
|
|
114
|
+
* Codex spends `context_window * effective_context_window_percent`, which defaults to 95%
|
|
115
|
+
* (codex-rs `openai_models.rs` / `turn_context.rs`). So this value yields a 875,900-token
|
|
116
|
+
* budget and leaves ~46k of headroom under the measured ceiling. An earlier release shipped
|
|
117
|
+
* 1,050,000 here, which spent 997,500 — past what the upstream accepts.
|
|
118
|
+
*
|
|
119
|
+
* Do NOT back-solve this from the ceiling (970,000 would land the budget at 921,500, inside
|
|
120
|
+
* the 1,840-token gap between the last success and the first refusal). The 95% is a safety
|
|
121
|
+
* margin to keep, not a discount to cancel out.
|
|
122
|
+
*
|
|
123
|
+
* Evidence: devlog/_plan/260817_native_gpt56_1m_context/001_measurement_evidence.md
|
|
124
|
+
* and 014_final_922k_with_margin.md.
|
|
125
|
+
*/
|
|
126
|
+
export const NATIVE_GPT56_CONTEXT_WINDOW = 922_000;
|
|
103
127
|
|
|
104
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Hard ceiling: the largest input the native GPT-5.6 family actually accepts (measured).
|
|
130
|
+
*
|
|
131
|
+
* Equal to the advertised window above rather than below it, because that window is already
|
|
132
|
+
* capped under this ceiling. The clamp stays because routed and API-key rows carry the same
|
|
133
|
+
* family at a 1,050,000 window, where 90% (945,000) WOULD overshoot this limit.
|
|
134
|
+
*/
|
|
135
|
+
export const NATIVE_GPT56_MAX_INPUT_TOKENS = 922_000;
|
|
136
|
+
|
|
137
|
+
export const NATIVE_OPENAI_CONTEXT_OVERRIDES: Record<string, { contextWindow?: number; maxContextWindow?: number; maxInputTokens?: number }> = {
|
|
105
138
|
"gpt-5.5": { contextWindow: 272_000, maxContextWindow: 272_000 },
|
|
106
139
|
"gpt-5.4": { contextWindow: 1_000_000, maxContextWindow: 1_000_000 },
|
|
107
140
|
"gpt-5.3-codex-spark": { contextWindow: 100_000, maxContextWindow: 100_000 },
|
|
108
|
-
"gpt-5.6-sol": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW },
|
|
109
|
-
"gpt-5.6-terra": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW },
|
|
110
|
-
"gpt-5.6-luna": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW },
|
|
111
|
-
|
|
141
|
+
"gpt-5.6-sol": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
142
|
+
"gpt-5.6-terra": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
143
|
+
"gpt-5.6-luna": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
144
|
+
// Daybreak Blue borrows Sol's capability metadata and rides the same family contract.
|
|
145
|
+
// Unlike sol/terra/luna its window was NOT measured here: this account cannot reach it
|
|
146
|
+
// (`400 "The 'gpt-daybreak-blue-latest' model is not supported when using Codex with a
|
|
147
|
+
// ChatGPT account."`), so the promotion rests on a report from an account that has
|
|
148
|
+
// access rather than on a probe. Treat it as the weaker evidence of the four.
|
|
149
|
+
[NATIVE_DAYBREAK_BLUE_MODEL]: { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
112
150
|
};
|
|
113
151
|
|
|
114
152
|
const PINNED_UPSTREAM_MODELS: Map<string, RawEntry> = new Map(
|
|
@@ -132,12 +170,89 @@ const PINNED_NATIVE_CAPABILITY_ENTRIES: Map<string, RawEntry> = new Map(
|
|
|
132
170
|
}),
|
|
133
171
|
);
|
|
134
172
|
|
|
135
|
-
|
|
173
|
+
/**
|
|
174
|
+
* The user-owned levers that narrow a native window, carried together.
|
|
175
|
+
*
|
|
176
|
+
* Both only ever lower: the authoritative window is measured against what the upstream
|
|
177
|
+
* accepts, so a user value above it would re-create the over-advertising this unit fixed.
|
|
178
|
+
*
|
|
179
|
+
* This travels as an ARGUMENT rather than module state on purpose. `grok/sync.ts` runs in
|
|
180
|
+
* the `ocx ensure` parent process, outside the server, so an injected global would never
|
|
181
|
+
* reach it — that failure is recorded in
|
|
182
|
+
* devlog/_plan/260817_native_gpt56_1m_context/006_root_cause_replan.md. Every call site
|
|
183
|
+
* already holds a config or a cap, so passing one more field costs nothing.
|
|
184
|
+
*
|
|
185
|
+
* A bare number is still accepted for the many call sites that only know the cap.
|
|
186
|
+
*/
|
|
187
|
+
export interface NativeContextLimits {
|
|
188
|
+
/** `providerContextCaps.openai` */
|
|
189
|
+
readonly cap?: number;
|
|
190
|
+
/** `providers.openai.contextWindow` — a floor-wide user override. */
|
|
191
|
+
readonly providerWindow?: number;
|
|
192
|
+
/** `providers.openai.modelContextWindows` — per-model, wins over `providerWindow`. */
|
|
193
|
+
readonly modelWindows?: Readonly<Record<string, number>>;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export type NativeContextLimitsInput = NativeContextLimits | number | undefined;
|
|
197
|
+
|
|
198
|
+
function positiveInt(value: unknown): number | undefined {
|
|
199
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function asLimits(input: NativeContextLimitsInput): NativeContextLimits {
|
|
203
|
+
if (input === undefined) return {};
|
|
204
|
+
return typeof input === "number" ? { cap: input } : input;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Read both levers out of a config once, for call sites that hold one. */
|
|
208
|
+
export function nativeContextLimits(
|
|
209
|
+
config: Pick<OcxConfig, "providers" | "providerContextCaps">,
|
|
210
|
+
): NativeContextLimits {
|
|
211
|
+
const provider = config.providers?.[OPENAI_CODEX_PROVIDER_ID];
|
|
212
|
+
const modelWindows: Record<string, number> = {};
|
|
213
|
+
for (const [slug, value] of Object.entries(provider?.modelContextWindows ?? {})) {
|
|
214
|
+
const window = positiveInt(value);
|
|
215
|
+
if (window !== undefined) modelWindows[slug] = window;
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
...(positiveInt(providerContextCap(config, OPENAI_CODEX_PROVIDER_ID)) !== undefined
|
|
219
|
+
? { cap: providerContextCap(config, OPENAI_CODEX_PROVIDER_ID) }
|
|
220
|
+
: {}),
|
|
221
|
+
...(positiveInt(provider?.contextWindow) !== undefined ? { providerWindow: provider!.contextWindow } : {}),
|
|
222
|
+
...(Object.keys(modelWindows).length > 0 ? { modelWindows } : {}),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Apply the user levers to an authoritative value. Lowering only, in a fixed order. */
|
|
227
|
+
function narrowToLimits(raw: number | undefined, slug: string, input: NativeContextLimitsInput): number | undefined {
|
|
228
|
+
if (raw === undefined) return undefined;
|
|
229
|
+
const limits = asLimits(input);
|
|
230
|
+
const overlay = positiveInt(limits.modelWindows?.[slug]) ?? positiveInt(limits.providerWindow);
|
|
231
|
+
const narrowed = overlay === undefined ? raw : Math.min(raw, overlay);
|
|
232
|
+
return applyProviderContextCap(narrowed, limits.cap) ?? narrowed;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function nativeOpenAiContextWindow(slug: string, limits?: NativeContextLimitsInput): number | undefined {
|
|
136
236
|
const raw = NATIVE_OPENAI_CONTEXT_OVERRIDES[slug]?.contextWindow
|
|
137
237
|
?? (typeof PINNED_NATIVE_CAPABILITY_ENTRIES.get(slug)?.context_window === "number"
|
|
138
238
|
? PINNED_NATIVE_CAPABILITY_ENTRIES.get(slug)!.context_window as number
|
|
139
239
|
: undefined);
|
|
140
|
-
return
|
|
240
|
+
return narrowToLimits(raw, slug, limits);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Largest input a native slug accepts, or undefined when no separate limit is known
|
|
245
|
+
* (the caller then falls back to the context window).
|
|
246
|
+
*
|
|
247
|
+
* A provider context cap lowers this too: a capped 272k window must not keep advertising a
|
|
248
|
+
* 922k input ceiling, or the cap would be cosmetic on every input-side surface.
|
|
249
|
+
*/
|
|
250
|
+
export function nativeOpenAiMaxInputTokens(slug: string, limits?: NativeContextLimitsInput): number | undefined {
|
|
251
|
+
const raw = NATIVE_OPENAI_CONTEXT_OVERRIDES[slug]?.maxInputTokens;
|
|
252
|
+
if (raw === undefined) return undefined;
|
|
253
|
+
const window = nativeOpenAiContextWindow(slug, limits);
|
|
254
|
+
const narrowed = narrowToLimits(raw, slug, limits) ?? raw;
|
|
255
|
+
return window === undefined ? narrowed : Math.min(narrowed, window);
|
|
141
256
|
}
|
|
142
257
|
|
|
143
258
|
export function nativeInputModalities(slug: string): string[] {
|
|
@@ -250,13 +365,21 @@ export function desktopVisibleNativeSlugs(
|
|
|
250
365
|
]);
|
|
251
366
|
}
|
|
252
367
|
|
|
253
|
-
export function nativeModelRows(config: Pick<OcxConfig, "disabledModels" | "combos" | "providerContextCaps">): Array<{ slug: string; disabled: boolean; contextWindow?: number }> {
|
|
368
|
+
export function nativeModelRows(config: Pick<OcxConfig, "disabledModels" | "combos" | "providerContextCaps" | "providers">): Array<{ slug: string; disabled: boolean; contextWindow?: number; maxInputTokens?: number }> {
|
|
254
369
|
const disabled = disabledNativeSlugs(config);
|
|
255
370
|
const shadowed = configuredNativeAliasSlugs(config);
|
|
256
|
-
|
|
371
|
+
// Both user levers, not just the cap: a per-model window set from the dashboard has to show
|
|
372
|
+
// up on the row the dashboard itself renders.
|
|
373
|
+
const limits = nativeContextLimits(config);
|
|
257
374
|
return NATIVE_OPENAI_MODELS.filter(slug => !shadowed.has(slug)).map(slug => {
|
|
258
|
-
const contextWindow = nativeOpenAiContextWindow(slug,
|
|
259
|
-
|
|
375
|
+
const contextWindow = nativeOpenAiContextWindow(slug, limits);
|
|
376
|
+
const maxInputTokens = nativeOpenAiMaxInputTokens(slug, limits);
|
|
377
|
+
return {
|
|
378
|
+
slug,
|
|
379
|
+
disabled: disabled.has(slug),
|
|
380
|
+
...(contextWindow !== undefined ? { contextWindow } : {}),
|
|
381
|
+
...(maxInputTokens !== undefined ? { maxInputTokens } : {}),
|
|
382
|
+
};
|
|
260
383
|
});
|
|
261
384
|
}
|
|
262
385
|
|
|
@@ -12,7 +12,19 @@ const NATIVE_OPENAI_CAPABILITY_SOURCES: Readonly<Record<string, string>> = Objec
|
|
|
12
12
|
[NATIVE_DAYBREAK_BLUE_MODEL]: "gpt-5.6-sol",
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Native ids whose capability metadata is inherited from another pinned native row.
|
|
17
|
+
*
|
|
18
|
+
* Membership here is about METADATA INHERITANCE only, and is independent of whether the
|
|
19
|
+
* slug is also globally allowlisted in `NATIVE_OPENAI_MODELS`. `gpt-daybreak-blue-latest`
|
|
20
|
+
* is now in BOTH: it inherits Sol's capability shape AND ships as a globally supported
|
|
21
|
+
* native row (owner decision, devlog 260816_codexrs_multiagent_v2_and_history_perf/011).
|
|
22
|
+
*
|
|
23
|
+
* The maps that consume the union of these two lists (`PINNED_NATIVE_CAPABILITY_ENTRIES`,
|
|
24
|
+
* `UPSTREAM_NATIVE_ENTRIES`) are keyed by slug, so an overlapping id collapses to one
|
|
25
|
+
* entry. Catalog row generation iterates `NATIVE_OPENAI_MODELS` alone, so it still emits
|
|
26
|
+
* exactly one bare row and one row per account selector.
|
|
27
|
+
*/
|
|
16
28
|
export const NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS = Object.freeze(
|
|
17
29
|
Object.keys(NATIVE_OPENAI_CAPABILITY_SOURCES),
|
|
18
30
|
);
|
|
@@ -25,10 +37,28 @@ export function nativeOpenAiCapabilitySourceSlug(slug: string): string {
|
|
|
25
37
|
return NATIVE_OPENAI_CAPABILITY_SOURCES[slug] ?? slug;
|
|
26
38
|
}
|
|
27
39
|
|
|
28
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Native OpenAI model ids that this release can route and restore with authoritative metadata.
|
|
42
|
+
*
|
|
43
|
+
* `gpt-daybreak-blue-latest` is entitlement-gated upstream: it is absent from codex-rs's
|
|
44
|
+
* bundled catalog and reaches a client only through an authenticated `/models` response.
|
|
45
|
+
* It is listed here by explicit owner decision so the row exists without waiting for an
|
|
46
|
+
* observation, because opencodex injects `model_catalog_json` and codex-rs therefore builds
|
|
47
|
+
* a `StaticModelsManager` whose refresh is a no-op — an entitled account had no way to
|
|
48
|
+
* discover it on a clean install.
|
|
49
|
+
*
|
|
50
|
+
* Accepted tradeoff: an UNENTITLED account also sees the row. Catalog sync still succeeds;
|
|
51
|
+
* selecting the model reaches the canonical OpenAI provider and the backend answers 400
|
|
52
|
+
* "model not supported for this account", which is relayed (a bare pooled route may first
|
|
53
|
+
* retry one alternate account on that exact body; a selector-qualified route is fixed and
|
|
54
|
+
* relays immediately). `disabledModels` hides the row but is NOT a runtime routing denial.
|
|
55
|
+
*
|
|
56
|
+
* Devlog: 260816_codexrs_multiagent_v2_and_history_perf/011 §4-bis.
|
|
57
|
+
*/
|
|
29
58
|
export const NATIVE_OPENAI_MODELS = [
|
|
30
59
|
"gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex-spark",
|
|
31
60
|
"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
|
|
61
|
+
NATIVE_DAYBREAK_BLUE_MODEL,
|
|
32
62
|
];
|
|
33
63
|
|
|
34
64
|
export const SUPPORTED_NATIVE_OPENAI_SLUGS = new Set(NATIVE_OPENAI_MODELS);
|
|
@@ -31,7 +31,7 @@ import { redactSecretString } from "../../lib/redact";
|
|
|
31
31
|
import upstreamModelsSnapshot from "../data/upstream-models.json";
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
import { NATIVE_OPENAI_CONTEXT_OVERRIDES, SUPPORTED_NATIVE_OPENAI_SLUGS, UPSTREAM_NATIVE_ENTRIES, isNativeOpenAiCapabilityAliasModel, nativeMultiAgentVersion } from "./metadata";
|
|
34
|
+
import { NATIVE_OPENAI_CONTEXT_OVERRIDES, SUPPORTED_NATIVE_OPENAI_SLUGS, UPSTREAM_NATIVE_ENTRIES, isNativeOpenAiCapabilityAliasModel, nativeMultiAgentVersion, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, type NativeContextLimitsInput } from "./metadata";
|
|
35
35
|
import { trustedAccountBoundNativeCatalogSlug } from "./account-models";
|
|
36
36
|
import { CODEX_NATIVE_ALIAS_CATALOG_KIND } from "./kinds";
|
|
37
37
|
|
|
@@ -266,32 +266,81 @@ export function isNativeOpenAiEntry(entry: RawEntry): boolean {
|
|
|
266
266
|
return typeof entry.slug === "string" && !entry.slug.includes("/");
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
|
|
269
|
+
/**
|
|
270
|
+
* Auto-compaction threshold for a native row.
|
|
271
|
+
*
|
|
272
|
+
* The usual rule is 90% of the window, but a row whose input ceiling sits below that has to
|
|
273
|
+
* clamp to the ceiling instead — otherwise the client keeps filling until upstream answers
|
|
274
|
+
* `context_length_exceeded` and compaction never gets a chance to run. Native GPT-5.6 no
|
|
275
|
+
* longer trips this (922,000 window, 829,800 at 90%), but the routed and API-key rows carry
|
|
276
|
+
* the same family at a 1,050,000 window where 90% would be 945,000 — past the ceiling.
|
|
277
|
+
*/
|
|
278
|
+
function nativeAutoCompactLimit(contextWindow: number, maxInputTokens: number | undefined, contextCap?: number): number {
|
|
279
|
+
const ninety = Math.floor(contextWindow * 0.9);
|
|
280
|
+
if (typeof maxInputTokens !== "number" || maxInputTokens <= 0) return ninety;
|
|
281
|
+
const cappedMaxInput = applyProviderContextCap(maxInputTokens, contextCap) ?? maxInputTokens;
|
|
282
|
+
return Math.min(ninety, cappedMaxInput, contextWindow);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Narrow any already-resolved native window by the user levers.
|
|
287
|
+
*
|
|
288
|
+
* Used for the fields the accessors do not own (`max_context_window`, and preserved rows
|
|
289
|
+
* that carry no static override) so every field on a row lands at the same width.
|
|
290
|
+
*/
|
|
291
|
+
function narrowNativeMaxContextWindow(
|
|
292
|
+
slug: string,
|
|
293
|
+
value: number | undefined,
|
|
294
|
+
limits?: NativeContextLimitsInput,
|
|
295
|
+
): number | undefined {
|
|
296
|
+
if (typeof value !== "number" || value <= 0) return value;
|
|
297
|
+
const resolved = nativeOpenAiContextWindow(slug, limits);
|
|
298
|
+
const authoritative = nativeOpenAiContextWindow(slug);
|
|
299
|
+
// The accessor pair tells us how far the levers moved this slug; apply the same delta to a
|
|
300
|
+
// field the accessor does not model, without ever raising it.
|
|
301
|
+
if (resolved === undefined || authoritative === undefined) return value;
|
|
302
|
+
return Math.min(value, Math.max(resolved, 1));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function applyNativeOpenAiContextOverride(entry: RawEntry, limits?: NativeContextLimitsInput): void {
|
|
270
306
|
const nativeSlug = trustedAccountBoundNativeCatalogSlug(entry)
|
|
271
307
|
?? (isNativeOpenAiEntry(entry) ? entry.slug as string : undefined);
|
|
272
308
|
if (!nativeSlug) return;
|
|
273
309
|
const override = NATIVE_OPENAI_CONTEXT_OVERRIDES[nativeSlug];
|
|
274
310
|
if (override) {
|
|
311
|
+
// Read the effective values through the accessors rather than re-deriving them from the
|
|
312
|
+
// static table: this function used to apply only the provider cap, so a per-model window
|
|
313
|
+
// the dashboard had already accepted was silently written back at full width here.
|
|
275
314
|
if (typeof override.contextWindow === "number") {
|
|
276
|
-
const contextWindow =
|
|
315
|
+
const contextWindow = nativeOpenAiContextWindow(nativeSlug, limits) ?? override.contextWindow;
|
|
277
316
|
entry.context_window = contextWindow;
|
|
278
|
-
entry.auto_compact_token_limit =
|
|
317
|
+
entry.auto_compact_token_limit = nativeAutoCompactLimit(
|
|
318
|
+
contextWindow,
|
|
319
|
+
nativeOpenAiMaxInputTokens(nativeSlug, limits) ?? override.maxInputTokens,
|
|
320
|
+
undefined,
|
|
321
|
+
);
|
|
279
322
|
}
|
|
280
323
|
if (typeof override.maxContextWindow === "number") {
|
|
281
|
-
|
|
324
|
+
const maxContextWindow = narrowNativeMaxContextWindow(nativeSlug, override.maxContextWindow, limits);
|
|
325
|
+
entry.max_context_window = maxContextWindow;
|
|
282
326
|
}
|
|
283
327
|
}
|
|
284
328
|
// providerContextCaps.openai is a ceiling for native OpenAI rows regardless of where the
|
|
285
329
|
// advertised window came from (#1430): preserved rows without a hardcoded override (e.g.
|
|
286
330
|
// gpt-5.4-mini) must stay under the cap too, and auto-compaction follows the capped window.
|
|
331
|
+
// The per-model window narrows the same rows for the same reason.
|
|
287
332
|
const currentContext = typeof entry.context_window === "number" ? entry.context_window : undefined;
|
|
288
|
-
const cappedContext =
|
|
333
|
+
const cappedContext = narrowNativeMaxContextWindow(nativeSlug, currentContext, limits);
|
|
289
334
|
if (cappedContext !== currentContext && typeof cappedContext === "number") {
|
|
290
335
|
entry.context_window = cappedContext;
|
|
291
|
-
entry.auto_compact_token_limit =
|
|
336
|
+
entry.auto_compact_token_limit = nativeAutoCompactLimit(
|
|
337
|
+
cappedContext,
|
|
338
|
+
nativeOpenAiMaxInputTokens(nativeSlug, limits) ?? override?.maxInputTokens,
|
|
339
|
+
undefined,
|
|
340
|
+
);
|
|
292
341
|
}
|
|
293
342
|
const currentMax = typeof entry.max_context_window === "number" ? entry.max_context_window : undefined;
|
|
294
|
-
const cappedMax =
|
|
343
|
+
const cappedMax = narrowNativeMaxContextWindow(nativeSlug, currentMax, limits);
|
|
295
344
|
if (cappedMax !== currentMax) {
|
|
296
345
|
entry.max_context_window = cappedMax;
|
|
297
346
|
}
|
|
@@ -462,16 +511,15 @@ export function normalizeRoutedCatalogEntry(entry: RawEntry, parallelToolCalls =
|
|
|
462
511
|
// tool_search round-trip (upstream codex-rs code_mode suite; live canary 2026-08-13: routed
|
|
463
512
|
// kimi/k3 called tools.mcp__node_repl__js → isError:false). Stamping false here instead forces
|
|
464
513
|
// every MCP declaration into exec.description — a measured 2.7x turn-1 payload regression
|
|
465
|
-
// (96,699 → 258,929 chars; devlog/_plan/260813_tool_catalog_deferral/010). So
|
|
466
|
-
//
|
|
467
|
-
//
|
|
468
|
-
// the web-search sidecar and has no proven deferred path.
|
|
514
|
+
// (96,699 → 258,929 chars; devlog/_plan/260813_tool_catalog_deferral/010). So every routed
|
|
515
|
+
// code-mode row advertises deferred discovery. Cursor still omits hosted web-search metadata below,
|
|
516
|
+
// but disabling this separate exposure bit can inflate `exec` past Cursor's 120 KB wire cap (#1830).
|
|
469
517
|
if (isCursorEntry) {
|
|
470
518
|
delete entry.web_search_tool_type;
|
|
471
519
|
} else {
|
|
472
520
|
entry.web_search_tool_type = "text_and_image";
|
|
473
521
|
}
|
|
474
|
-
entry.supports_search_tool =
|
|
522
|
+
entry.supports_search_tool = true;
|
|
475
523
|
// Cursor's transport already serializes overlapping tool calls into atomic Responses tool events.
|
|
476
524
|
// Advertising parallel calls lets Codex send the same native capability bit it sends for OpenAI.
|
|
477
525
|
// Opt-in providers (OcxProviderConfig.parallelToolCalls, e.g. xAI) advertise it too: the
|
|
@@ -74,7 +74,7 @@ import { createAdmissionGate, ResourceAdmissionError, type AdmissionMetrics } fr
|
|
|
74
74
|
|
|
75
75
|
import { CODEX_CUSTOM_MODEL_CATALOG_KIND, JAWCODE_CATALOG_AUGMENT_PROVIDERS, catalogModelSlug, shouldExposeRoutedModel } from "./parsing";
|
|
76
76
|
import type { CatalogModel } from "./parsing";
|
|
77
|
-
import { disabledNativeSlugs, hasComboTargets, isNativeOpenAiCapabilityAliasModel, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiContextWindow, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
|
|
77
|
+
import { disabledNativeSlugs, hasComboTargets, isNativeOpenAiCapabilityAliasModel, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
|
|
78
78
|
import { deriveComboCatalogModel, normalizedOpenAiApiSignature, openAiApiCollisionWarnings, replaceLastComboCatalogOmissions, warnUncataloguedComboOnce } from "./aggregation";
|
|
79
79
|
import type { ComboCatalogOmission } from "./aggregation";
|
|
80
80
|
import type { CatalogGatherProviderAuthEvidence } from "./filesystem-evidence";
|
|
@@ -703,6 +703,8 @@ const COMBO_MEMBER_CONTEXT_FALLBACK = 128_000;
|
|
|
703
703
|
|
|
704
704
|
interface ComboCatalogMemberFallback {
|
|
705
705
|
readonly contextWindow?: number;
|
|
706
|
+
/** Input ceiling when it is lower than the window (native GPT-5.6: 922k under 1.05M). */
|
|
707
|
+
readonly maxInputTokens?: number;
|
|
706
708
|
readonly inputModalities?: readonly string[];
|
|
707
709
|
readonly reasoningEfforts?: readonly string[];
|
|
708
710
|
}
|
|
@@ -744,7 +746,11 @@ export function resolveComboCatalogMember(
|
|
|
744
746
|
if (!addMaxInput && !addModalities && !addReasoning) return member;
|
|
745
747
|
return {
|
|
746
748
|
...member,
|
|
747
|
-
|
|
749
|
+
// Never claim a larger input budget than the window, and prefer the model's own
|
|
750
|
+
// measured ceiling when the fallback carries one.
|
|
751
|
+
...(addMaxInput
|
|
752
|
+
? { maxInputTokens: Math.min(fallback.maxInputTokens ?? contextWindow!, contextWindow!) }
|
|
753
|
+
: {}),
|
|
748
754
|
...(addModalities ? { inputModalities: [...fallback.inputModalities!] } : {}),
|
|
749
755
|
...(addReasoning ? { reasoningEfforts: [...fallback.reasoningEfforts!] } : {}),
|
|
750
756
|
};
|
|
@@ -765,7 +771,7 @@ export function resolveComboCatalogMember(
|
|
|
765
771
|
}
|
|
766
772
|
const maxInput = typeof existing.maxInputTokens === "number" && existing.maxInputTokens > 0
|
|
767
773
|
? Math.min(existing.maxInputTokens, capped)
|
|
768
|
-
: capped;
|
|
774
|
+
: Math.min(fallback?.maxInputTokens ?? capped, capped);
|
|
769
775
|
return withFallbackMetadata({
|
|
770
776
|
...existing,
|
|
771
777
|
contextWindow: capped,
|
|
@@ -790,6 +796,10 @@ export function resolveComboCatalogMember(
|
|
|
790
796
|
: (typeof base.maxInputTokens === "number" && base.maxInputTokens > 0
|
|
791
797
|
? base.maxInputTokens
|
|
792
798
|
: undefined);
|
|
799
|
+
// Kept OUT of knownMaxInput on purpose: that value doubles as a context-window fallback
|
|
800
|
+
// below, and a native alias whose input ceiling (922k) is lower than its window (1.05M)
|
|
801
|
+
// would otherwise shrink the advertised window to the input limit.
|
|
802
|
+
const fallbackMaxInput = existing || prov ? fallback?.maxInputTokens : undefined;
|
|
793
803
|
// Real discovery/config values win. A native alias is the next fallback tier.
|
|
794
804
|
// The generic 128k/text synthesis from #1305 remains the final fallback.
|
|
795
805
|
const fallbackContext = existing || prov ? fallback?.contextWindow : undefined;
|
|
@@ -814,8 +824,11 @@ export function resolveComboCatalogMember(
|
|
|
814
824
|
?? (prov ? configuredReasoningEfforts(prov, target.model) : undefined)
|
|
815
825
|
?? base.reasoningEfforts
|
|
816
826
|
?? (fallback?.reasoningEfforts ? [...fallback.reasoningEfforts] : undefined);
|
|
817
|
-
|
|
818
|
-
|
|
827
|
+
// The model's own measured input ceiling still applies when discovery gave us nothing:
|
|
828
|
+
// GPT-5.6 advertises a 1.05M window but refuses input past 922k.
|
|
829
|
+
const effectiveMaxInput = knownMaxInput ?? fallbackMaxInput;
|
|
830
|
+
const maxInputTokens = effectiveMaxInput !== undefined
|
|
831
|
+
? Math.min(effectiveMaxInput, contextWindow)
|
|
819
832
|
: contextWindow;
|
|
820
833
|
|
|
821
834
|
return {
|
|
@@ -1712,7 +1725,11 @@ async function gatherRoutedModelsUncached(
|
|
|
1712
1725
|
id: slug,
|
|
1713
1726
|
owned_by: "openai",
|
|
1714
1727
|
contextWindow,
|
|
1715
|
-
|
|
1728
|
+
// Input limit, not the total window. These coincide for native GPT-5.6 today (the
|
|
1729
|
+
// advertised 922,000 window is already capped at its measured ceiling), but the two
|
|
1730
|
+
// stay separate fields because routed/API rows of the same family run a wider window.
|
|
1731
|
+
// Falls back to the window for slugs with no separate ceiling.
|
|
1732
|
+
maxInputTokens: Math.min(nativeOpenAiMaxInputTokens(slug, openaiContextCap) ?? contextWindow, contextWindow),
|
|
1716
1733
|
inputModalities: nativeInputModalities(slug),
|
|
1717
1734
|
reasoningEfforts: nativeReasoningEfforts(slug),
|
|
1718
1735
|
...(nativeParallelToolCalls(slug) ? { parallelToolCalls: true } : {}),
|
|
@@ -1730,11 +1747,15 @@ async function gatherRoutedModelsUncached(
|
|
|
1730
1747
|
const combo = getCombo(config, id);
|
|
1731
1748
|
if (!combo) continue;
|
|
1732
1749
|
const nativeContextWindow = combo.nativeAlias && combo.alias
|
|
1733
|
-
? nativeOpenAiContextWindow(combo.alias)
|
|
1750
|
+
? nativeOpenAiContextWindow(combo.alias, providerContextCap(config, OPENAI_CODEX_PROVIDER_ID))
|
|
1751
|
+
: undefined;
|
|
1752
|
+
const nativeAliasMaxInput = combo.nativeAlias && combo.alias
|
|
1753
|
+
? nativeOpenAiMaxInputTokens(combo.alias, providerContextCap(config, OPENAI_CODEX_PROVIDER_ID))
|
|
1734
1754
|
: undefined;
|
|
1735
1755
|
const nativeAliasFallback = combo.nativeAlias && combo.alias && nativeContextWindow !== undefined
|
|
1736
1756
|
? {
|
|
1737
1757
|
contextWindow: nativeContextWindow,
|
|
1758
|
+
...(nativeAliasMaxInput !== undefined ? { maxInputTokens: Math.min(nativeAliasMaxInput, nativeContextWindow) } : {}),
|
|
1738
1759
|
inputModalities: nativeInputModalities(combo.alias),
|
|
1739
1760
|
reasoningEfforts: nativeReasoningEfforts(combo.alias),
|
|
1740
1761
|
}
|
|
@@ -1788,6 +1809,14 @@ async function gatherRoutedModelsUncached(
|
|
|
1788
1809
|
? Math.min(cm.contextWindow, nativeAliasContextWindow)
|
|
1789
1810
|
: cm.contextWindow
|
|
1790
1811
|
: nativeAliasContextWindow;
|
|
1812
|
+
// Input ceiling for a native capability alias, clamped to whatever window we settled on
|
|
1813
|
+
// above. A custom row that lowered the window must not keep the full native input budget.
|
|
1814
|
+
const nativeAliasMaxInputTokens = codexForwardNativeCapabilityAlias
|
|
1815
|
+
? nativeOpenAiMaxInputTokens(cm.modelId, providerContextCap(config, OPENAI_CODEX_PROVIDER_ID))
|
|
1816
|
+
: undefined;
|
|
1817
|
+
const customMaxInputTokens = nativeAliasMaxInputTokens !== undefined && customContextWindow !== undefined
|
|
1818
|
+
? Math.min(nativeAliasMaxInputTokens, customContextWindow)
|
|
1819
|
+
: nativeAliasMaxInputTokens;
|
|
1791
1820
|
const nativeAliasDefaultEffort = codexForwardNativeCapabilityAlias
|
|
1792
1821
|
? nativeDefaultReasoningEffort(cm.modelId)
|
|
1793
1822
|
: undefined;
|
|
@@ -1804,6 +1833,7 @@ async function gatherRoutedModelsUncached(
|
|
|
1804
1833
|
? { displayName: cm.displayName }
|
|
1805
1834
|
: codexForwardNativeCapabilityAlias ? { displayName: "Daybreak Blue" } : {}),
|
|
1806
1835
|
...(customContextWindow !== undefined ? { contextWindow: customContextWindow } : {}),
|
|
1836
|
+
...(customMaxInputTokens !== undefined ? { maxInputTokens: customMaxInputTokens } : {}),
|
|
1807
1837
|
...(cm.inputModalities
|
|
1808
1838
|
? { inputModalities: cm.inputModalities }
|
|
1809
1839
|
: codexForwardNativeCapabilityAlias ? { inputModalities: nativeInputModalities(cm.modelId) } : {}),
|
|
@@ -35,7 +35,7 @@ import { codexAccountNamespaceEntries, isMainCodexAccountTarget } from "../accou
|
|
|
35
35
|
|
|
36
36
|
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";
|
|
37
37
|
import type { CatalogModel, MultiAgentMode, RawCatalog, RawEntry } from "./parsing";
|
|
38
|
-
import { accountBoundNativeOpenAiSlugs, accountBoundNativeOpenAiSlugsBySelector, applyNativeVisibility, CODEX_NATIVE_ALIAS_CATALOG_KIND, desktopAllowlistSuppressedNativeSlugs, disabledNativeSlugs, isNativeAliasCatalogEntry, isUnsupportedOpenAiNativeSlug, NATIVE_OPENAI_MODELS, observedAccountBoundNativeEntries, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, shouldUpgradeToUpstreamEntry, SUPPORTED_NATIVE_OPENAI_SLUGS, upstreamNativeEntry } from "./metadata";
|
|
38
|
+
import { accountBoundNativeOpenAiSlugs, accountBoundNativeOpenAiSlugsBySelector, applyNativeVisibility, CODEX_NATIVE_ALIAS_CATALOG_KIND, desktopAllowlistSuppressedNativeSlugs, disabledNativeSlugs, isNativeAliasCatalogEntry, isUnsupportedOpenAiNativeSlug, NATIVE_OPENAI_MODELS, nativeContextLimits, observedAccountBoundNativeEntries, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, shouldUpgradeToUpstreamEntry, SUPPORTED_NATIVE_OPENAI_SLUGS, upstreamNativeEntry, type NativeContextLimitsInput } from "./metadata";
|
|
39
39
|
import {
|
|
40
40
|
bundledCatalogCacheState,
|
|
41
41
|
loadBundledCodexCatalog,
|
|
@@ -90,21 +90,36 @@ export type SubagentRosterExclusionReason =
|
|
|
90
90
|
/**
|
|
91
91
|
* Whether a catalog entry may be offered as a V2 subagent model.
|
|
92
92
|
*
|
|
93
|
-
* Upstream
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
93
|
+
* Upstream changed this rule in codex-rs `6d4d9442c` ("Support leaf models in
|
|
94
|
+
* multi-agent v2"). `model_supports_multi_agent_backend`
|
|
95
|
+
* (core/src/tools/handlers/multi_agents_common.rs:36-42) now admits EVERY model
|
|
96
|
+
* except one explicitly marked `disabled`; the older `== Some(V2)` equality that
|
|
97
|
+
* `92938d880` introduced is gone.
|
|
97
98
|
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
99
|
+
* The field no longer answers "may I be a delegation target". It answers "does the
|
|
100
|
+
* CHILD get collaboration tools": `collab_tools_enabled`
|
|
101
|
+
* (core/src/tools/spec_plan.rs:599-610) grants a child recursive tools only when its
|
|
102
|
+
* own catalog value is exactly `Some(V2)`. The three-way distinction survives, but it
|
|
103
|
+
* now means eligible-recursive / eligible-LEAF / excluded:
|
|
104
|
+
*
|
|
105
|
+
* - `"v2"` -> eligible, and the child may itself delegate.
|
|
106
|
+
* - `"v1"` -> eligible LEAF worker. This is upstream's pin for `gpt-5.6-luna`
|
|
107
|
+
* (models-manager/models.json); excluding it here is exactly what
|
|
108
|
+
* kept Luna out of opencodex's roster.
|
|
109
|
+
* - absent/null -> eligible LEAF worker (routed or unpinned-native model).
|
|
110
|
+
* - `"disabled"` -> the sole capability-based exclusion.
|
|
111
|
+
*
|
|
112
|
+
* This is the roster filter only. Catalog STAMPING is a separate concern owned by
|
|
113
|
+
* `applyMultiAgentMode`, including the `keepNativeChatGptOnV1` policy (#1728) that
|
|
114
|
+
* keeps ChatGPT-native rows on `v1` so a native parent can still spawn a routed child
|
|
115
|
+
* despite backend-encrypted NEW_TASK bodies (#92). Recognizing those `v1` rows as
|
|
116
|
+
* eligible leaves here is what makes that policy usable, not a contradiction of it.
|
|
117
|
+
*
|
|
118
|
+
* Devlog: 260816_codexrs_multiagent_v2_and_history_perf/011 (C1), superseding the
|
|
119
|
+
* option-B decision in 260730_codex_rs_upstream_v2_live_handoff/060.
|
|
104
120
|
*/
|
|
105
121
|
export function isEligibleV2SubagentEntry(entry: RawEntry): boolean {
|
|
106
|
-
|
|
107
|
-
return pinned === "v2" || pinned === null || pinned === undefined;
|
|
122
|
+
return entry.multi_agent_version !== "disabled";
|
|
108
123
|
}
|
|
109
124
|
|
|
110
125
|
export interface EffectiveSubagentModel {
|
|
@@ -211,7 +226,7 @@ export function effectiveSubagentRoster(
|
|
|
211
226
|
return { candidates, advertised, excluded };
|
|
212
227
|
}
|
|
213
228
|
|
|
214
|
-
export function finishUpstreamNativeEntry(clone: RawEntry, priority: number, contextCap?:
|
|
229
|
+
export function finishUpstreamNativeEntry(clone: RawEntry, priority: number, contextCap?: NativeContextLimitsInput): RawEntry {
|
|
215
230
|
if (priority !== 9) clone.priority = priority;
|
|
216
231
|
applyNativeOpenAiContextOverride(clone, contextCap);
|
|
217
232
|
// GPT-5.6 natives keep their exact upstream ladders (e.g. luna has max but no ultra).
|
|
@@ -264,7 +279,7 @@ export function deriveEntry(
|
|
|
264
279
|
priority: number,
|
|
265
280
|
model?: CatalogModel,
|
|
266
281
|
exactComboSlugs: ReadonlySet<string> = new Set(),
|
|
267
|
-
contextCap?:
|
|
282
|
+
contextCap?: NativeContextLimitsInput,
|
|
268
283
|
): RawEntry {
|
|
269
284
|
const preserveExact = isExactComboCatalogModel(model, exactComboSlugs);
|
|
270
285
|
const codexForwardNativeCapabilityAlias = model?.codexForwardNativeCapabilityAlias === true
|
|
@@ -343,10 +358,9 @@ export function deriveEntry(
|
|
|
343
358
|
});
|
|
344
359
|
}
|
|
345
360
|
// Fallback when no template is available (best-effort; strict parser may need more).
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
// 260813_tool_catalog_deferral/010+020); search=false costs a measured 2.7x turn-1 payload.
|
|
361
|
+
// All routed fallbacks enable deferred code-mode tool exposure; otherwise the nested catalog
|
|
362
|
+
// expands into `exec.description` and can exceed Cursor's 120 KB serialized tool limit (#1830).
|
|
363
|
+
// Cursor still omits hosted web-search metadata because runTurn bypasses that separate sidecar.
|
|
350
364
|
const isCursorFallback = isRouted && model?.provider === "cursor";
|
|
351
365
|
const entry: RawEntry = {
|
|
352
366
|
slug, display_name: routedDisplayName(slug), description: desc,
|
|
@@ -354,7 +368,7 @@ export function deriveEntry(
|
|
|
354
368
|
priority, base_instructions: "You are a helpful coding assistant.",
|
|
355
369
|
...(isRouted
|
|
356
370
|
? isCursorFallback
|
|
357
|
-
? { supports_search_tool:
|
|
371
|
+
? { supports_search_tool: true }
|
|
358
372
|
: { web_search_tool_type: "text_and_image", supports_search_tool: true }
|
|
359
373
|
: {}),
|
|
360
374
|
};
|
|
@@ -391,7 +405,7 @@ export interface ObservedCatalogEntryBuildInput {
|
|
|
391
405
|
readonly disabledNativeAccountSlugs: ReadonlySet<string>;
|
|
392
406
|
readonly multiAgentV2Enabled: boolean;
|
|
393
407
|
readonly keepNativeChatGptOnV1?: boolean;
|
|
394
|
-
readonly openaiContextCap?:
|
|
408
|
+
readonly openaiContextCap?: NativeContextLimitsInput;
|
|
395
409
|
/** Additional native ids to clone under account selectors, without creating bare rows. */
|
|
396
410
|
readonly accountNativeSlugs?: readonly string[];
|
|
397
411
|
/** Per-selector account ids; unknown observations must not be copied to unrelated accounts. */
|
|
@@ -410,7 +424,7 @@ export function buildCatalogEntries(
|
|
|
410
424
|
accountSelectors: readonly string[] = [],
|
|
411
425
|
suppressedBareNativeSlugs: ReadonlySet<string> = new Set(),
|
|
412
426
|
disabledNativeAccountSlugs: ReadonlySet<string> = new Set(),
|
|
413
|
-
contextCap?:
|
|
427
|
+
contextCap?: NativeContextLimitsInput,
|
|
414
428
|
accountNativeSlugs?: readonly string[],
|
|
415
429
|
accountNativeSlugsBySelector?: ReadonlyMap<string, readonly string[]>,
|
|
416
430
|
keepNativeChatGptOnV1 = false,
|
|
@@ -746,7 +760,7 @@ export interface ObservedCatalogMergeInput {
|
|
|
746
760
|
readonly accountBoundEntries: readonly RawEntry[];
|
|
747
761
|
readonly suppressedBareNativeSlugs?: ReadonlySet<string>;
|
|
748
762
|
readonly policy: ObservedCatalogMergePolicy;
|
|
749
|
-
readonly openaiContextCap?:
|
|
763
|
+
readonly openaiContextCap?: NativeContextLimitsInput;
|
|
750
764
|
}
|
|
751
765
|
|
|
752
766
|
/**
|
|
@@ -1410,7 +1424,9 @@ function writeRetainedCatalogSync({
|
|
|
1410
1424
|
const hasPhysicalComboProvider = Object.hasOwn(config.providers, COMBO_NAMESPACE);
|
|
1411
1425
|
const includeNativeOpenAi = shouldIncludeNativeOpenAi(config);
|
|
1412
1426
|
const includeAccountBoundNativeOpenAi = shouldIncludeAccountBoundNativeOpenAi(config);
|
|
1413
|
-
|
|
1427
|
+
// Both user levers. Passing only the cap here is what let a per-model window the dashboard
|
|
1428
|
+
// had accepted get written back at full width in the on-disk catalog.
|
|
1429
|
+
const openaiContextCap = nativeContextLimits(config);
|
|
1414
1430
|
const accountSelectors = includeAccountBoundNativeOpenAi
|
|
1415
1431
|
? visibleCodexAccountSelectors(config)
|
|
1416
1432
|
: [];
|
|
@@ -1672,8 +1688,16 @@ export async function syncCatalogModels(config: OcxConfig): Promise<RetainedCata
|
|
|
1672
1688
|
export function restoreCodexCatalogWithPermit(
|
|
1673
1689
|
permit: CatalogWritePermit,
|
|
1674
1690
|
owningCodexHome: string,
|
|
1691
|
+
/**
|
|
1692
|
+
* The catalog this injection actually wrote, when it is known (#1798).
|
|
1693
|
+
*
|
|
1694
|
+
* Re-resolving from the CURRENT config is wrong after a Codex app rewrite that dropped
|
|
1695
|
+
* `model_catalog_json`: that sends restore to the default catalog while the routed file we
|
|
1696
|
+
* really wrote is left untouched. The recorded path is the file whose routing is ours.
|
|
1697
|
+
*/
|
|
1698
|
+
injectedCatalogPath?: string | null,
|
|
1675
1699
|
): { removed: number; kept: number; path: string } {
|
|
1676
|
-
const catalogPath = readCodexCatalogPath();
|
|
1700
|
+
const catalogPath = injectedCatalogPath ?? readCodexCatalogPath();
|
|
1677
1701
|
const catalog = readCatalog(catalogPath);
|
|
1678
1702
|
if (!catalog || !Array.isArray(catalog.models)) return { removed: 0, kept: 0, path: catalogPath };
|
|
1679
1703
|
const disabledModels = currentDisabledModelsForRestore();
|