@bitkyc08/opencodex 2.14.0 → 2.14.2
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/README.md +55 -0
- package/gui/dist/assets/index-DUCH59lJ.css +1 -0
- package/gui/dist/assets/index-DUyQeU1j.js +76 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/command-code.ts +46 -6
- package/src/adapters/cursor/request-builder.ts +54 -10
- package/src/adapters/cursor/tool-definitions.ts +24 -0
- package/src/adapters/kiro.ts +10 -1
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +7 -4
- package/src/adapters/openai-responses-url.ts +14 -0
- package/src/adapters/openai-responses.ts +111 -2
- package/src/adapters/tool-catalog-nudge.ts +26 -4
- package/src/bridge.ts +50 -3
- package/src/cli/init.ts +4 -17
- package/src/codex/auth-api.ts +2 -74
- package/src/codex/catalog/effort.ts +2 -1
- package/src/codex/catalog/metadata.ts +62 -12
- package/src/codex/catalog/native-models.ts +27 -0
- package/src/codex/catalog/parsing.ts +27 -8
- package/src/codex/catalog/provider-fetch.ts +47 -5
- package/src/codex/catalog/sync.ts +31 -8
- package/src/codex/catalog.ts +1 -1
- package/src/codex/features.ts +14 -3
- package/src/codex/model-cache.ts +7 -1
- package/src/codex/native-main-claim.ts +13 -2
- package/src/config.ts +79 -4
- package/src/generated/compatibility-version.json +74 -46
- package/src/lab/ledger/store.ts +0 -18
- package/src/lab/subject/installation-salt.ts +13 -2
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/tool-argument-integers.ts +158 -0
- package/src/oauth/nous.ts +58 -9
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/command-code-efforts.ts +18 -0
- package/src/providers/model-rename-migration.ts +202 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/quota.ts +9 -2
- package/src/providers/registry.ts +17 -10
- package/src/responses/spill-store.ts +5 -1
- package/src/responses/state.ts +50 -2
- package/src/router.ts +12 -1
- package/src/server/index.ts +3 -2
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/config-routes.ts +51 -16
- package/src/server/management/logs-usage-routes.ts +48 -10
- package/src/server/management/provider-routes.ts +2 -1
- package/src/server/management/usage-summary-cache.ts +7 -1
- package/src/server/responses/collaboration.ts +12 -2
- package/src/server/responses/core.ts +33 -17
- package/src/server/responses/fetch-helpers.ts +12 -1
- package/src/server/responses/ws-upstream.ts +199 -0
- package/src/server/startup-health-cache.ts +12 -0
- package/src/usage/log.ts +430 -12
- package/src/vision/index.ts +25 -4
- package/src/vision/timeout-bounds.ts +9 -0
- package/gui/dist/assets/index-BNVYzdn0.css +0 -1
- package/gui/dist/assets/index-Co12XTT-.js +0 -76
package/src/cli/init.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as readline from "node:readline";
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
3
3
|
import { injectCodexConfig } from "../codex/inject";
|
|
4
|
-
import { classifyOpenAiTierBackup, getConfigPath, getDefaultConfig, isValidProviderName, saveConfig } from "../config";
|
|
4
|
+
import { classifyOpenAiTierBackup, getConfigPath, getDefaultConfig, isValidProviderName, preserveOpenAiTierRollbackSnapshot, saveConfig } from "../config";
|
|
5
5
|
import { enrichProviderFromCatalog } from "../oauth/key-providers";
|
|
6
6
|
import { deriveInitProviders } from "../providers/derive";
|
|
7
7
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
@@ -80,21 +80,8 @@ export function cleanupOpenAiTierBackupAfterInit(configPath = getConfigPath()):
|
|
|
80
80
|
unlinkSync(backup);
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// silently overwrite another rollback snapshot; retry with a sequence suffix.
|
|
86
|
-
for (let attempt = 0; attempt < 16; attempt++) {
|
|
87
|
-
const preserved = `${configPath}.pre-openai-tiers-v1-rollback.${Date.now()}${attempt ? `-${attempt}` : ""}.bak`;
|
|
88
|
-
try {
|
|
89
|
-
copyFileSync(backup, preserved, fsConstants.COPYFILE_EXCL);
|
|
90
|
-
} catch (error) {
|
|
91
|
-
if ((error as NodeJS.ErrnoException).code === "EEXIST") continue;
|
|
92
|
-
throw error;
|
|
93
|
-
}
|
|
94
|
-
unlinkSync(backup);
|
|
95
|
-
console.warn(`⚠️ Kept your pre-migration config rollback snapshot at ${preserved}`);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
83
|
+
const preserved = preserveOpenAiTierRollbackSnapshot(configPath);
|
|
84
|
+
console.warn(`⚠️ Kept your pre-migration config rollback snapshot at ${preserved}`);
|
|
98
85
|
} catch { /* cleanup is best-effort; never block init on backup housekeeping */ }
|
|
99
86
|
}
|
|
100
87
|
|
package/src/codex/auth-api.ts
CHANGED
|
@@ -82,7 +82,7 @@ export {
|
|
|
82
82
|
setAccountQuotaFromParsed,
|
|
83
83
|
updateAccountQuota,
|
|
84
84
|
} from "./quota";
|
|
85
|
-
import { extractAccountId
|
|
85
|
+
import { extractAccountId } from "../oauth/chatgpt";
|
|
86
86
|
import { getMainAccountPlan, MAIN_CODEX_ACCOUNT_ID, setMainAccountPlan } from "./main-account";
|
|
87
87
|
import { captureConfigGeneration, registerStateSweepAfterTick } from "../lib/state-store-sweeper";
|
|
88
88
|
import { reconcileLiveStateStores } from "../lib/state-store-registrations";
|
|
@@ -147,7 +147,6 @@ function nativeMainProfileBusyResponse(): Response {
|
|
|
147
147
|
return response;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
const MANUAL_IMPORT_ENV = "OPENCODEX_ENABLE_UNVERIFIED_CODEX_IMPORT";
|
|
151
150
|
const CODEX_CREDENTIAL_PERSISTENCE_ERROR = "Account was saved, but credential setup did not complete. Reauthenticate or remove the account.";
|
|
152
151
|
const CODEX_CREDENTIAL_PERSISTENCE_CODE = "codex_credential_persistence_failed";
|
|
153
152
|
|
|
@@ -375,10 +374,6 @@ async function readResetCreditJson(
|
|
|
375
374
|
}
|
|
376
375
|
}
|
|
377
376
|
|
|
378
|
-
export function isUnverifiedCodexImportEnabled(): boolean {
|
|
379
|
-
return process.env[MANUAL_IMPORT_ENV] === "1";
|
|
380
|
-
}
|
|
381
|
-
|
|
382
377
|
function manualImportDisabledResponse(): Response {
|
|
383
378
|
return jsonResponse({
|
|
384
379
|
error: "Manual Codex account import is disabled. Use OAuth login to add a pool account.",
|
|
@@ -1368,74 +1363,7 @@ export async function handleCodexAuthAPI(
|
|
|
1368
1363
|
}
|
|
1369
1364
|
|
|
1370
1365
|
if (url.pathname === "/api/codex-auth/accounts" && req.method === "POST") {
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
let body: { id: string; email: string; plan?: unknown; accessToken: string; refreshToken: string; chatgptAccountId: string };
|
|
1374
|
-
try { body = (await req.json()) as typeof body; } catch { return jsonResponse({ error: "Invalid JSON" }, 400); }
|
|
1375
|
-
if (!body.id || !body.email || !body.accessToken || !body.refreshToken || !body.chatgptAccountId) {
|
|
1376
|
-
return jsonResponse({ error: "Missing required fields" }, 400);
|
|
1377
|
-
}
|
|
1378
|
-
if (!isValidCodexAccountId(body.id)) {
|
|
1379
|
-
return jsonResponse({ error: "Invalid account id format" }, 400);
|
|
1380
|
-
}
|
|
1381
|
-
if (body.accessToken.length > 10_000 || body.refreshToken.length > 10_000) {
|
|
1382
|
-
return jsonResponse({ error: "Input too large" }, 400);
|
|
1383
|
-
}
|
|
1384
|
-
const runtimeConfig = getRuntimeConfig(config);
|
|
1385
|
-
const preflightConflict = codexAccountPersistenceConflict(runtimeConfig, body.id, "create");
|
|
1386
|
-
if (preflightConflict) return jsonResponse({ error: preflightConflict }, 400);
|
|
1387
|
-
// 1.1: Duplicate check is scoped by personal vs workspace plan bucket.
|
|
1388
|
-
const plan = codexPlanValue(body.plan);
|
|
1389
|
-
const derivedAccountId = extractAccountId(undefined, body.accessToken) ?? body.chatgptAccountId;
|
|
1390
|
-
const collision = checkAccountIdCollision(derivedAccountId, body.email, plan);
|
|
1391
|
-
if (collision.collision) {
|
|
1392
|
-
return jsonResponse({ error: collision.reason }, 400);
|
|
1393
|
-
}
|
|
1394
|
-
// 4.2: use JWT exp for expiresAt instead of hardcoded 1 hour
|
|
1395
|
-
const payload = decodeJwtPayload(body.accessToken);
|
|
1396
|
-
const exp = typeof payload?.exp === "number" ? payload.exp * 1000 : Date.now() + 3600_000;
|
|
1397
|
-
const warmup = await verifyCodexAccountWarmup(body.id, body.accessToken, derivedAccountId);
|
|
1398
|
-
if (!warmup.ok) return warmup.response;
|
|
1399
|
-
const latestConfig = getRuntimeConfig(config);
|
|
1400
|
-
const commitConflict = codexAccountPersistenceConflict(latestConfig, body.id, "create");
|
|
1401
|
-
if (commitConflict) return jsonResponse({ error: commitConflict }, 400);
|
|
1402
|
-
const addedAccount = withCodexAccountLogLabel(
|
|
1403
|
-
{
|
|
1404
|
-
id: body.id,
|
|
1405
|
-
email: body.email,
|
|
1406
|
-
...(plan !== undefined ? { plan } : {}),
|
|
1407
|
-
isMain: false,
|
|
1408
|
-
},
|
|
1409
|
-
latestConfig.codexAccounts ?? [],
|
|
1410
|
-
);
|
|
1411
|
-
const persistence = persistNewCodexAccount(
|
|
1412
|
-
config,
|
|
1413
|
-
latestConfig,
|
|
1414
|
-
addedAccount,
|
|
1415
|
-
{
|
|
1416
|
-
credential: {
|
|
1417
|
-
accessToken: body.accessToken,
|
|
1418
|
-
refreshToken: body.refreshToken,
|
|
1419
|
-
expiresAt: exp,
|
|
1420
|
-
chatgptAccountId: derivedAccountId,
|
|
1421
|
-
},
|
|
1422
|
-
validatedAt: warmup.validatedAt,
|
|
1423
|
-
},
|
|
1424
|
-
);
|
|
1425
|
-
reconcileLiveStateStores();
|
|
1426
|
-
if (persistence.status === "publication-failed") markAccountNeedsReauth(body.id);
|
|
1427
|
-
const catalogRefresh = await convergeAccountNamespaceCatalog(
|
|
1428
|
-
latestConfig,
|
|
1429
|
-
persistence.pickerVisibilityChanged,
|
|
1430
|
-
convergeCodexCatalog,
|
|
1431
|
-
);
|
|
1432
|
-
if (persistence.status === "publication-failed") {
|
|
1433
|
-
return jsonResponse({
|
|
1434
|
-
ok: false,
|
|
1435
|
-
...codexCredentialPersistenceFailure(body.id, catalogRefresh.catalogRefreshPending === true),
|
|
1436
|
-
}, 500);
|
|
1437
|
-
}
|
|
1438
|
-
return jsonResponse({ ok: true, ...catalogRefresh });
|
|
1366
|
+
return manualImportDisabledResponse();
|
|
1439
1367
|
}
|
|
1440
1368
|
|
|
1441
1369
|
if (url.pathname === "/api/codex-auth/accounts" && req.method === "DELETE") {
|
|
@@ -34,6 +34,7 @@ import upstreamModelsSnapshot from "../data/upstream-models.json";
|
|
|
34
34
|
import { readCatalog, readCodexCatalogPath } from "./parsing";
|
|
35
35
|
import type { CatalogModel, RawEntry } from "./parsing";
|
|
36
36
|
import { UPSTREAM_NATIVE_ENTRIES } from "./metadata";
|
|
37
|
+
import { nativeOpenAiCapabilitySourceSlug } from "./native-models";
|
|
37
38
|
import { loadBundledCodexCatalog } from "./bundled";
|
|
38
39
|
import type { BundledCatalogDeps, ReadonlyRawCatalog } from "./bundled";
|
|
39
40
|
import { deriveEntry } from "./sync";
|
|
@@ -180,7 +181,7 @@ export function applyReasoningLevels(
|
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
export function isGpt56NativeSlug(slug: string): boolean {
|
|
183
|
-
return !slug.includes("/") && slug.startsWith("gpt-5.6-");
|
|
184
|
+
return !slug.includes("/") && nativeOpenAiCapabilitySourceSlug(slug).startsWith("gpt-5.6-");
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
export function ensureGpt56ReasoningLevels(entry: RawEntry): void {
|
|
@@ -15,7 +15,7 @@ import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../..
|
|
|
15
15
|
import { getProviderRegistryEntry } from "../../providers/registry";
|
|
16
16
|
import { applyProviderContextCap, providerContextCap } from "../../providers/context-cap";
|
|
17
17
|
import { routedSlug, slugEquals, slugsEquivalent } from "../../providers/slug-codec";
|
|
18
|
-
import {
|
|
18
|
+
import { identifyRoutedModel } from "../../adapters/identity";
|
|
19
19
|
import { filterCursorConfiguredModelsByLiveDiscovery } from "../../adapters/cursor/discovery";
|
|
20
20
|
import { fetchCursorUsableModels } from "../../adapters/cursor/live-models";
|
|
21
21
|
import { isCanonicalOpenAiForwardProvider, OPENAI_API_PROVIDER_ID, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
|
|
@@ -37,9 +37,23 @@ import type { RawEntry } from "./parsing";
|
|
|
37
37
|
import { readCurrentCatalogOrCache, readCurrentCodexCatalog, readCurrentCodexModelsCache, unique } from "./bundled";
|
|
38
38
|
import { trustedAccountBoundNativeCatalogSlug, visibleCodexAccountSelectors } from "./account-models";
|
|
39
39
|
import { CODEX_NATIVE_ALIAS_CATALOG_KIND } from "./kinds";
|
|
40
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
NATIVE_DAYBREAK_BLUE_MODEL,
|
|
42
|
+
NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS,
|
|
43
|
+
NATIVE_OPENAI_MODELS,
|
|
44
|
+
SUPPORTED_NATIVE_OPENAI_SLUGS,
|
|
45
|
+
isNativeOpenAiCapabilityAliasModel,
|
|
46
|
+
nativeOpenAiCapabilitySourceSlug,
|
|
47
|
+
} from "./native-models";
|
|
41
48
|
export { CODEX_NATIVE_ALIAS_CATALOG_KIND } from "./kinds";
|
|
42
|
-
export {
|
|
49
|
+
export {
|
|
50
|
+
NATIVE_DAYBREAK_BLUE_MODEL,
|
|
51
|
+
NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS,
|
|
52
|
+
NATIVE_OPENAI_MODELS,
|
|
53
|
+
SUPPORTED_NATIVE_OPENAI_SLUGS,
|
|
54
|
+
isNativeOpenAiCapabilityAliasModel,
|
|
55
|
+
nativeOpenAiCapabilitySourceSlug,
|
|
56
|
+
} from "./native-models";
|
|
43
57
|
|
|
44
58
|
export const DOCUMENTED_NATIVE_OPENAI_ADDITIONS = [
|
|
45
59
|
"gpt-5.3-codex-spark",
|
|
@@ -94,18 +108,28 @@ export const NATIVE_OPENAI_CONTEXT_OVERRIDES: Record<string, { contextWindow?: n
|
|
|
94
108
|
"gpt-5.6-sol": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW },
|
|
95
109
|
"gpt-5.6-terra": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW },
|
|
96
110
|
"gpt-5.6-luna": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW },
|
|
111
|
+
[NATIVE_DAYBREAK_BLUE_MODEL]: { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW },
|
|
97
112
|
};
|
|
98
113
|
|
|
114
|
+
const PINNED_UPSTREAM_MODELS: Map<string, RawEntry> = new Map(
|
|
115
|
+
((upstreamModelsSnapshot as unknown as { models?: RawEntry[] }).models ?? [])
|
|
116
|
+
.flatMap(model => typeof model.slug === "string" ? [[model.slug, model] as const] : []),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
function pinnedNativeCapabilityEntry(slug: string): RawEntry | undefined {
|
|
120
|
+
return PINNED_UPSTREAM_MODELS.get(nativeOpenAiCapabilitySourceSlug(slug));
|
|
121
|
+
}
|
|
122
|
+
|
|
99
123
|
/**
|
|
100
124
|
* Pinned capability metadata is safe to use as a fallback for every supported native model.
|
|
101
125
|
* Keep it separate from UPSTREAM_NATIVE_ENTRIES: that narrower map also authorizes replacing
|
|
102
126
|
* persisted native rows during sync, which is currently intentional only for the GPT-5.6 family.
|
|
103
127
|
*/
|
|
104
128
|
const PINNED_NATIVE_CAPABILITY_ENTRIES: Map<string, RawEntry> = new Map(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
129
|
+
[...NATIVE_OPENAI_MODELS, ...NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS].flatMap(slug => {
|
|
130
|
+
const entry = pinnedNativeCapabilityEntry(slug);
|
|
131
|
+
return entry ? [[slug, entry] as const] : [];
|
|
132
|
+
}),
|
|
109
133
|
);
|
|
110
134
|
|
|
111
135
|
export function nativeOpenAiContextWindow(slug: string, contextCap?: number): number | undefined {
|
|
@@ -259,12 +283,38 @@ export function applyNativeVisibility(
|
|
|
259
283
|
return entries;
|
|
260
284
|
}
|
|
261
285
|
|
|
286
|
+
function upstreamNativeEntryForSlug(slug: string): RawEntry | undefined {
|
|
287
|
+
const sourceSlug = nativeOpenAiCapabilitySourceSlug(slug);
|
|
288
|
+
if (!sourceSlug.startsWith("gpt-5.6-")) return undefined;
|
|
289
|
+
const source = PINNED_UPSTREAM_MODELS.get(sourceSlug);
|
|
290
|
+
if (!source) return undefined;
|
|
291
|
+
if (slug === sourceSlug) return source;
|
|
292
|
+
|
|
293
|
+
const alias = structuredClone(source) as RawEntry;
|
|
294
|
+
alias.slug = slug;
|
|
295
|
+
alias.display_name = "Daybreak Blue";
|
|
296
|
+
alias.description = "Frontier general-purpose model with safeguards for defensive cybersecurity work.";
|
|
297
|
+
if (typeof alias.base_instructions === "string") {
|
|
298
|
+
alias.base_instructions = identifyRoutedModel(alias.base_instructions, slug);
|
|
299
|
+
}
|
|
300
|
+
if (alias.model_messages && typeof alias.model_messages === "object" && !Array.isArray(alias.model_messages)) {
|
|
301
|
+
const modelMessages = alias.model_messages as Record<string, unknown>;
|
|
302
|
+
if (typeof modelMessages.instructions_template === "string") {
|
|
303
|
+
alias.model_messages = {
|
|
304
|
+
...modelMessages,
|
|
305
|
+
instructions_template: identifyRoutedModel(modelMessages.instructions_template, slug),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
delete alias.availability_nux;
|
|
310
|
+
return alias;
|
|
311
|
+
}
|
|
312
|
+
|
|
262
313
|
export const UPSTREAM_NATIVE_ENTRIES: Map<string, RawEntry> = new Map(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
.map(m => [m.slug as string, m]),
|
|
314
|
+
[...NATIVE_OPENAI_MODELS, ...NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS].flatMap(slug => {
|
|
315
|
+
const entry = upstreamNativeEntryForSlug(slug);
|
|
316
|
+
return entry ? [[slug, entry] as const] : [];
|
|
317
|
+
}),
|
|
268
318
|
);
|
|
269
319
|
|
|
270
320
|
export function upstreamNativeEntry(slug: string): RawEntry | null {
|
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
/** ChatGPT/Codex wire id observed for the account-native Daybreak Blue surface. */
|
|
2
|
+
export const NATIVE_DAYBREAK_BLUE_MODEL = "gpt-daybreak-blue-latest";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Account-native aliases whose Codex capabilities track another pinned native row.
|
|
6
|
+
*
|
|
7
|
+
* This is catalog metadata inheritance only. Routing always preserves the requested
|
|
8
|
+
* wire id, so the ChatGPT/Codex `gpt-daybreak-*` surface never collapses into the
|
|
9
|
+
* separately billed API-key `daybreak-*-latest` surface or into `gpt-5.6-sol`.
|
|
10
|
+
*/
|
|
11
|
+
const NATIVE_OPENAI_CAPABILITY_SOURCES: Readonly<Record<string, string>> = Object.freeze({
|
|
12
|
+
[NATIVE_DAYBREAK_BLUE_MODEL]: "gpt-5.6-sol",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/** Account-scoped native ids that may inherit metadata but never enter the bare allowlist. */
|
|
16
|
+
export const NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS = Object.freeze(
|
|
17
|
+
Object.keys(NATIVE_OPENAI_CAPABILITY_SOURCES),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export function isNativeOpenAiCapabilityAliasModel(slug: string): boolean {
|
|
21
|
+
return Object.hasOwn(NATIVE_OPENAI_CAPABILITY_SOURCES, slug);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function nativeOpenAiCapabilitySourceSlug(slug: string): string {
|
|
25
|
+
return NATIVE_OPENAI_CAPABILITY_SOURCES[slug] ?? slug;
|
|
26
|
+
}
|
|
27
|
+
|
|
1
28
|
/** Native OpenAI model ids that this release can route and restore with authoritative metadata. */
|
|
2
29
|
export const NATIVE_OPENAI_MODELS = [
|
|
3
30
|
"gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex-spark",
|
|
@@ -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, nativeMultiAgentVersion } from "./metadata";
|
|
34
|
+
import { NATIVE_OPENAI_CONTEXT_OVERRIDES, SUPPORTED_NATIVE_OPENAI_SLUGS, UPSTREAM_NATIVE_ENTRIES, isNativeOpenAiCapabilityAliasModel, nativeMultiAgentVersion } from "./metadata";
|
|
35
35
|
import { trustedAccountBoundNativeCatalogSlug } from "./account-models";
|
|
36
36
|
import { CODEX_NATIVE_ALIAS_CATALOG_KIND } from "./kinds";
|
|
37
37
|
|
|
@@ -116,6 +116,11 @@ export interface CatalogModel {
|
|
|
116
116
|
inputModalities?: string[];
|
|
117
117
|
/** Provider opted into parallel tool calls (OcxProviderConfig.parallelToolCalls). */
|
|
118
118
|
parallelToolCalls?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* This routed row is an explicitly configured account-native alias on the canonical ChatGPT
|
|
121
|
+
* forward provider. It may inherit pinned native Codex metadata without changing its wire id.
|
|
122
|
+
*/
|
|
123
|
+
codexForwardNativeCapabilityAlias?: boolean;
|
|
119
124
|
/** Whether Codex may send Responses text.verbosity for this routed model. */
|
|
120
125
|
supportsVerbosity?: boolean;
|
|
121
126
|
supportsReasoningSummaries?: boolean;
|
|
@@ -359,9 +364,19 @@ export function applyMultiAgentMode(entries: RawEntry[], mode: MultiAgentMode, v
|
|
|
359
364
|
for (const entry of entries) {
|
|
360
365
|
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
361
366
|
const nativeAlias = entry.opencodex_catalog_kind === CODEX_NATIVE_ALIAS_CATALOG_KIND;
|
|
367
|
+
const routedNativeSlug = slug.startsWith(`${OPENAI_CODEX_PROVIDER_ID}/`)
|
|
368
|
+
? slug.slice(OPENAI_CODEX_PROVIDER_ID.length + 1)
|
|
369
|
+
: "";
|
|
370
|
+
const codexForwardCapabilityAlias = entry.opencodex_catalog_kind === CODEX_CUSTOM_MODEL_CATALOG_KIND
|
|
371
|
+
&& entry.use_responses_lite === true
|
|
372
|
+
&& isNativeOpenAiCapabilityAliasModel(routedNativeSlug)
|
|
373
|
+
? routedNativeSlug
|
|
374
|
+
: undefined;
|
|
362
375
|
const upstreamPin = nativeAlias
|
|
363
376
|
? nativeMultiAgentVersion(slug)
|
|
364
|
-
:
|
|
377
|
+
: codexForwardCapabilityAlias
|
|
378
|
+
? nativeMultiAgentVersion(codexForwardCapabilityAlias)
|
|
379
|
+
: UPSTREAM_NATIVE_ENTRIES.get(trustedAccountBoundNativeCatalogSlug(entry) ?? slug)?.multi_agent_version;
|
|
365
380
|
if (typeof upstreamPin === "string") {
|
|
366
381
|
entry.multi_agent_version = upstreamPin;
|
|
367
382
|
} else if (v2FeatureEnabled) {
|
|
@@ -394,17 +409,21 @@ export function normalizeRoutedCatalogEntry(entry: RawEntry, parallelToolCalls =
|
|
|
394
409
|
delete entry.supports_reasoning_summaries;
|
|
395
410
|
const isCursorEntry = typeof entry.slug === "string" && entry.slug.startsWith("cursor/");
|
|
396
411
|
// `supports_search_tool` selects Codex's deferred tool-discovery surface; it is not the hosted
|
|
397
|
-
// web-search capability.
|
|
398
|
-
//
|
|
399
|
-
//
|
|
400
|
-
//
|
|
401
|
-
//
|
|
412
|
+
// web-search capability. Routed rows also carry tool_mode=code_mode_only (below), and under code
|
|
413
|
+
// mode DEFERRED MCP tools remain callable through exec's `tools` global / ALL_TOOLS without any
|
|
414
|
+
// tool_search round-trip (upstream codex-rs code_mode suite; live canary 2026-08-13: routed
|
|
415
|
+
// kimi/k3 called tools.mcp__node_repl__js → isError:false). Stamping false here instead forces
|
|
416
|
+
// every MCP declaration into exec.description — a measured 2.7x turn-1 payload regression
|
|
417
|
+
// (96,699 → 258,929 chars; devlog/_plan/260813_tool_catalog_deferral/010). So non-Cursor routed
|
|
418
|
+
// rows advertise deferred discovery; the #1522 reachability concern is covered by the code-mode
|
|
419
|
+
// path, not by paying the full-catalog tax. Cursor stays false: its runTurn transport bypasses
|
|
420
|
+
// the web-search sidecar and has no proven deferred path.
|
|
402
421
|
if (isCursorEntry) {
|
|
403
422
|
delete entry.web_search_tool_type;
|
|
404
423
|
} else {
|
|
405
424
|
entry.web_search_tool_type = "text_and_image";
|
|
406
425
|
}
|
|
407
|
-
entry.supports_search_tool =
|
|
426
|
+
entry.supports_search_tool = !isCursorEntry;
|
|
408
427
|
// Cursor's transport already serializes overlapping tool calls into atomic Responses tool events.
|
|
409
428
|
// Advertising parallel calls lets Codex send the same native capability bit it sends for OpenAI.
|
|
410
429
|
// Opt-in providers (OcxProviderConfig.parallelToolCalls, e.g. xAI) advertise it too: the
|
|
@@ -69,7 +69,7 @@ import { createAdmissionGate, ResourceAdmissionError, type AdmissionMetrics } fr
|
|
|
69
69
|
|
|
70
70
|
import { CODEX_CUSTOM_MODEL_CATALOG_KIND, JAWCODE_CATALOG_AUGMENT_PROVIDERS, catalogModelSlug, shouldExposeRoutedModel } from "./parsing";
|
|
71
71
|
import type { CatalogModel } from "./parsing";
|
|
72
|
-
import { disabledNativeSlugs, hasComboTargets, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiContextWindow, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
|
|
72
|
+
import { disabledNativeSlugs, hasComboTargets, isNativeOpenAiCapabilityAliasModel, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiContextWindow, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
|
|
73
73
|
import { deriveComboCatalogModel, normalizedOpenAiApiSignature, openAiApiCollisionWarnings, replaceLastComboCatalogOmissions, warnUncataloguedComboOnce } from "./aggregation";
|
|
74
74
|
import type { ComboCatalogOmission } from "./aggregation";
|
|
75
75
|
import type { CatalogGatherProviderAuthEvidence } from "./filesystem-evidence";
|
|
@@ -209,6 +209,15 @@ interface GatherInflightEntry {
|
|
|
209
209
|
readonly promise: Promise<GatherFlightResult>;
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
function withCanonicalOpenAiForwardAuthDefault(
|
|
213
|
+
name: string,
|
|
214
|
+
provider: OcxProviderConfig,
|
|
215
|
+
): OcxProviderConfig {
|
|
216
|
+
if (name !== OPENAI_CODEX_PROVIDER_ID || provider.authMode !== undefined) return provider;
|
|
217
|
+
const candidate = { ...provider, authMode: "forward" as const };
|
|
218
|
+
return isCanonicalOpenAiForwardProvider(candidate) ? candidate : provider;
|
|
219
|
+
}
|
|
220
|
+
|
|
212
221
|
const gatherInflight = new Map<string, GatherInflightEntry[]>();
|
|
213
222
|
const CATALOG_GATHER_AUTHORITY_KEY = randomBytes(32);
|
|
214
223
|
const REQUEST_CREDENTIAL_SENTINEL = `ocx-catalog-credential-${randomBytes(16).toString("hex")}`;
|
|
@@ -390,7 +399,7 @@ function captureProviderGather(
|
|
|
390
399
|
authResolver: ModelsAuthResolver,
|
|
391
400
|
retainConfiguredModelIds?: ReadonlySet<string>,
|
|
392
401
|
): CapturedProviderGather {
|
|
393
|
-
const enriched = detachedClone(configured);
|
|
402
|
+
const enriched = detachedClone(withCanonicalOpenAiForwardAuthDefault(name, configured));
|
|
394
403
|
enrichProviderFromRegistry(name, enriched);
|
|
395
404
|
const provider = recursivelyFreeze(enriched);
|
|
396
405
|
const observedAuth = authResolver.kind === "observed"
|
|
@@ -1714,16 +1723,49 @@ async function gatherRoutedModelsUncached(
|
|
|
1714
1723
|
const replacedByRoutedSlug = new Map(all.map(model => [routedSlug(model.provider, model.id), model]));
|
|
1715
1724
|
const customModels = (config.customModels ?? []).map(cm => {
|
|
1716
1725
|
const rawProvider = config.providers[cm.provider];
|
|
1726
|
+
// Registry routing backfills an omitted authMode on the built-in OpenAI provider to
|
|
1727
|
+
// forward. Keep the catalog projection on the same contract while still failing closed
|
|
1728
|
+
// for every explicit non-forward mode and every non-canonical endpoint.
|
|
1729
|
+
const providerForCanonicalCheck = rawProvider
|
|
1730
|
+
? withCanonicalOpenAiForwardAuthDefault(cm.provider, rawProvider)
|
|
1731
|
+
: undefined;
|
|
1732
|
+
const codexForwardNativeCapabilityAlias = cm.provider === OPENAI_CODEX_PROVIDER_ID
|
|
1733
|
+
&& providerForCanonicalCheck !== undefined
|
|
1734
|
+
&& isCanonicalOpenAiForwardProvider(providerForCanonicalCheck)
|
|
1735
|
+
&& isNativeOpenAiCapabilityAliasModel(cm.modelId);
|
|
1736
|
+
const nativeAliasContextWindow = codexForwardNativeCapabilityAlias
|
|
1737
|
+
? nativeOpenAiContextWindow(cm.modelId, providerContextCap(config, OPENAI_CODEX_PROVIDER_ID))
|
|
1738
|
+
: undefined;
|
|
1739
|
+
const customContextWindow = cm.contextWindow
|
|
1740
|
+
? nativeAliasContextWindow !== undefined
|
|
1741
|
+
? Math.min(cm.contextWindow, nativeAliasContextWindow)
|
|
1742
|
+
: cm.contextWindow
|
|
1743
|
+
: nativeAliasContextWindow;
|
|
1744
|
+
const nativeAliasDefaultEffort = codexForwardNativeCapabilityAlias
|
|
1745
|
+
? nativeDefaultReasoningEffort(cm.modelId)
|
|
1746
|
+
: undefined;
|
|
1717
1747
|
const supportsReasoningSummaries = configuredReasoningSummarySupport(rawProvider, cm.modelId);
|
|
1718
1748
|
const base: CatalogModel = {
|
|
1719
1749
|
id: cm.modelId,
|
|
1720
1750
|
provider: cm.provider,
|
|
1721
1751
|
catalogKind: CODEX_CUSTOM_MODEL_CATALOG_KIND,
|
|
1722
1752
|
// Display-only label: never feeds routing (customModels are keyed by routedSlug below).
|
|
1723
|
-
...(cm.displayName
|
|
1724
|
-
|
|
1725
|
-
|
|
1753
|
+
...(cm.displayName
|
|
1754
|
+
? { displayName: cm.displayName }
|
|
1755
|
+
: codexForwardNativeCapabilityAlias ? { displayName: "Daybreak Blue" } : {}),
|
|
1756
|
+
...(customContextWindow !== undefined ? { contextWindow: customContextWindow } : {}),
|
|
1757
|
+
...(cm.inputModalities
|
|
1758
|
+
? { inputModalities: cm.inputModalities }
|
|
1759
|
+
: codexForwardNativeCapabilityAlias ? { inputModalities: nativeInputModalities(cm.modelId) } : {}),
|
|
1726
1760
|
...(typeof supportsReasoningSummaries === "boolean" ? { supportsReasoningSummaries } : {}),
|
|
1761
|
+
...(codexForwardNativeCapabilityAlias
|
|
1762
|
+
? {
|
|
1763
|
+
codexForwardNativeCapabilityAlias: true,
|
|
1764
|
+
reasoningEfforts: nativeReasoningEfforts(cm.modelId),
|
|
1765
|
+
parallelToolCalls: nativeParallelToolCalls(cm.modelId),
|
|
1766
|
+
...(nativeAliasDefaultEffort ? { defaultReasoningEffort: nativeAliasDefaultEffort } : {}),
|
|
1767
|
+
}
|
|
1768
|
+
: {}),
|
|
1727
1769
|
};
|
|
1728
1770
|
// #962: the dedupe below drops the provider-derived row this custom row replaces. Inherit that
|
|
1729
1771
|
// row's provider capability metadata (reasoning ladder, default effort, parallel tool calls,
|
|
@@ -248,6 +248,9 @@ export function deriveEntry(
|
|
|
248
248
|
contextCap?: number,
|
|
249
249
|
): RawEntry {
|
|
250
250
|
const preserveExact = isExactComboCatalogModel(model, exactComboSlugs);
|
|
251
|
+
const codexForwardNativeCapabilityAlias = model?.codexForwardNativeCapabilityAlias === true
|
|
252
|
+
? upstreamNativeEntry(model.id)
|
|
253
|
+
: null;
|
|
251
254
|
const isRouted = model !== undefined;
|
|
252
255
|
if (!isRouted && !slug.includes("/")) {
|
|
253
256
|
// Supported native slug covered by the upstream snapshot: use the REAL entry (exact
|
|
@@ -256,8 +259,8 @@ export function deriveEntry(
|
|
|
256
259
|
const upstream = upstreamNativeEntry(slug);
|
|
257
260
|
if (upstream) return finishUpstreamNativeEntry(upstream, priority, contextCap);
|
|
258
261
|
}
|
|
259
|
-
if (template) {
|
|
260
|
-
const e = JSON.parse(JSON.stringify(template)) as RawEntry;
|
|
262
|
+
if (template || codexForwardNativeCapabilityAlias) {
|
|
263
|
+
const e = JSON.parse(JSON.stringify(codexForwardNativeCapabilityAlias ?? template)) as RawEntry;
|
|
261
264
|
e.slug = slug;
|
|
262
265
|
e.display_name = routedDisplayName(slug);
|
|
263
266
|
e.description = desc;
|
|
@@ -272,9 +275,11 @@ export function deriveEntry(
|
|
|
272
275
|
// window when /models omits context metadata (#992). Known metadata
|
|
273
276
|
// restores exact values below; otherwise the strict-fields fallback
|
|
274
277
|
// supplies the conservative 128k triple.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
+
if (!codexForwardNativeCapabilityAlias) {
|
|
279
|
+
delete e.context_window;
|
|
280
|
+
delete e.max_context_window;
|
|
281
|
+
delete e.auto_compact_token_limit;
|
|
282
|
+
}
|
|
278
283
|
// Native id for identity text + metadata lookups — the slug may be an encoded
|
|
279
284
|
// alias (`provider/vendor-model`); the model object carries the native id.
|
|
280
285
|
const modelName = model?.id ?? slug.slice(slug.indexOf("/") + 1);
|
|
@@ -283,8 +288,17 @@ export function deriveEntry(
|
|
|
283
288
|
// (leaking that into base_instructions is a non-first-party signature → ToS risk).
|
|
284
289
|
e.base_instructions = identifyRoutedModel(e.base_instructions, modelName);
|
|
285
290
|
}
|
|
286
|
-
applyReasoningLevels(
|
|
287
|
-
|
|
291
|
+
applyReasoningLevels(
|
|
292
|
+
e,
|
|
293
|
+
model?.reasoningEfforts,
|
|
294
|
+
model?.defaultReasoningEffort,
|
|
295
|
+
preserveExact || codexForwardNativeCapabilityAlias !== null,
|
|
296
|
+
);
|
|
297
|
+
// This exact provider/model pair is the ChatGPT/Codex forward surface. Keep the pinned
|
|
298
|
+
// native tool/search/responses-lite contract while preserving the routed slug and wire id.
|
|
299
|
+
if (!codexForwardNativeCapabilityAlias) {
|
|
300
|
+
normalizeRoutedCatalogEntry(e, model?.parallelToolCalls === true);
|
|
301
|
+
}
|
|
288
302
|
if (model) applyCatalogMetadata(e, model.provider, model.id, model.contextCap);
|
|
289
303
|
applyCatalogModelMetadata(e, model);
|
|
290
304
|
if (model?.catalogKind) e.opencodex_catalog_kind = model.catalogKind;
|
|
@@ -310,11 +324,20 @@ export function deriveEntry(
|
|
|
310
324
|
});
|
|
311
325
|
}
|
|
312
326
|
// Fallback when no template is available (best-effort; strict parser may need more).
|
|
327
|
+
// Cursor fallback rows mirror normalizeRoutedCatalogEntry: no deferred discovery, no hosted
|
|
328
|
+
// web-search metadata (runTurn transport bypasses the sidecar). Non-Cursor routed fallbacks
|
|
329
|
+
// advertise deferred discovery — code mode keeps deferred MCP callable (devlog
|
|
330
|
+
// 260813_tool_catalog_deferral/010+020); search=false costs a measured 2.7x turn-1 payload.
|
|
331
|
+
const isCursorFallback = isRouted && model?.provider === "cursor";
|
|
313
332
|
const entry: RawEntry = {
|
|
314
333
|
slug, display_name: routedDisplayName(slug), description: desc,
|
|
315
334
|
shell_type: "shell_command", visibility: "list", supported_in_api: true,
|
|
316
335
|
priority, base_instructions: "You are a helpful coding assistant.",
|
|
317
|
-
...(isRouted
|
|
336
|
+
...(isRouted
|
|
337
|
+
? isCursorFallback
|
|
338
|
+
? { supports_search_tool: false }
|
|
339
|
+
: { web_search_tool_type: "text_and_image", supports_search_tool: true }
|
|
340
|
+
: {}),
|
|
318
341
|
};
|
|
319
342
|
if (isRouted) {
|
|
320
343
|
applyRoutedCodexToolMode(entry);
|
package/src/codex/catalog.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Public surface preserved exactly; importers keep using "src/codex/catalog".
|
|
3
3
|
export { isMediaGenerationModelId, shouldExposeRoutedModel, readCodexCatalogPath, readCatalog, normalizeRoutedCatalogEntry, catalogModelSlug, filterSupportedNativeSlugs, catalogModelSupportsReasoningSummaries } from "./catalog/parsing";
|
|
4
4
|
export type { CatalogModel, MultiAgentMode } from "./catalog/parsing";
|
|
5
|
-
export { accountBoundNativeOpenAiSlugs, accountBoundNativeOpenAiSlugsBySelector, CODEX_NATIVE_ALIAS_CATALOG_KIND, NATIVE_OPENAI_MODELS, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, isNativeAliasCatalogEntry, nativeOpenAiContextWindow, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, observedAccountBoundNativeEntries, observedAccountBoundNativeOpenAiSlugs, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs, nativeInputModalities, nativeReasoningEfforts, nativeDefaultReasoningEffort, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi } from "./catalog/metadata";
|
|
5
|
+
export { accountBoundNativeOpenAiSlugs, accountBoundNativeOpenAiSlugsBySelector, CODEX_NATIVE_ALIAS_CATALOG_KIND, NATIVE_DAYBREAK_BLUE_MODEL, NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS, NATIVE_OPENAI_MODELS, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, isNativeAliasCatalogEntry, isNativeOpenAiCapabilityAliasModel, nativeOpenAiCapabilitySourceSlug, nativeOpenAiContextWindow, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, observedAccountBoundNativeEntries, observedAccountBoundNativeOpenAiSlugs, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs, nativeInputModalities, nativeReasoningEfforts, nativeDefaultReasoningEffort, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi } from "./catalog/metadata";
|
|
6
6
|
export { isSpawnableCodexCandidate, codexExecInvocation, loadBundledCodexCatalog, materializeBundledCodexCatalog, loadCatalogTemplate } from "./catalog/bundled";
|
|
7
7
|
export { nativeEffortClamp, shouldApplyNativeEffortClamp, catalogModelEfforts, codexSupportedReasoningEfforts, clampedDefaultEffort, clampEntryToCodexSupportedEfforts, clampCatalogModelsToCodexSupport } from "./catalog/effort";
|
|
8
8
|
export { applyProviderConfigHints, isDatedVariantId, filterCatalogVisibleModels, gatherRoutedModels, clearGatherRoutedModelsInflight, augmentRoutedModelsWithRegistryOpenAiApiRows, augmentRoutedModelsWithMetadata, resolveComboCatalogMember, configuredComboTargetModelsByProvider } from "./catalog/provider-fetch";
|
package/src/codex/features.ts
CHANGED
|
@@ -1036,7 +1036,18 @@ export function setMultiAgentModeHintText(value: string | null, configPath?: str
|
|
|
1036
1036
|
return setV2StringField("multi_agent_mode_hint_text", value, configPath);
|
|
1037
1037
|
}
|
|
1038
1038
|
|
|
1039
|
-
const
|
|
1039
|
+
export const MODE_HINT_CAPABILITY_CACHE_MAX_ENTRIES = 8;
|
|
1040
|
+
export const modeHintCapabilityCache = new Map<string, boolean | null>();
|
|
1041
|
+
|
|
1042
|
+
export function rememberModeHintCapability(cacheKey: string, capability: boolean | null): void {
|
|
1043
|
+
modeHintCapabilityCache.delete(cacheKey);
|
|
1044
|
+
modeHintCapabilityCache.set(cacheKey, capability);
|
|
1045
|
+
while (modeHintCapabilityCache.size > MODE_HINT_CAPABILITY_CACHE_MAX_ENTRIES) {
|
|
1046
|
+
const oldest = modeHintCapabilityCache.keys().next().value;
|
|
1047
|
+
if (oldest === undefined) break;
|
|
1048
|
+
modeHintCapabilityCache.delete(oldest);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1040
1051
|
|
|
1041
1052
|
/**
|
|
1042
1053
|
* True when the installed Codex runtime binary contains the
|
|
@@ -1069,7 +1080,7 @@ export function probeCodexSupportsModeHint(): boolean | null {
|
|
|
1069
1080
|
if (!isNativeExecutable(buf)) continue;
|
|
1070
1081
|
sawBinary = true;
|
|
1071
1082
|
if (buf.includes(Buffer.from("multi_agent_mode_hint_text", "utf8"))) {
|
|
1072
|
-
|
|
1083
|
+
rememberModeHintCapability(cacheKey, true);
|
|
1073
1084
|
return true;
|
|
1074
1085
|
}
|
|
1075
1086
|
} catch {
|
|
@@ -1078,7 +1089,7 @@ export function probeCodexSupportsModeHint(): boolean | null {
|
|
|
1078
1089
|
}
|
|
1079
1090
|
// At least one real binary was inspected and none contained the key.
|
|
1080
1091
|
const result = sawBinary ? false : null;
|
|
1081
|
-
|
|
1092
|
+
rememberModeHintCapability(cacheKey, result);
|
|
1082
1093
|
return result;
|
|
1083
1094
|
} catch {
|
|
1084
1095
|
return null;
|
package/src/codex/model-cache.ts
CHANGED
|
@@ -47,7 +47,7 @@ export type ModelCacheClearReason = "authority" | "eviction";
|
|
|
47
47
|
|
|
48
48
|
const cache = new Map<string, CacheEntry>();
|
|
49
49
|
let globalCacheGeneration = 0;
|
|
50
|
-
const providerCacheGenerations = new Map<string, number>();
|
|
50
|
+
export const providerCacheGenerations = new Map<string, number>();
|
|
51
51
|
let cacheBytes = 0;
|
|
52
52
|
let oldestCachedProvider: string | undefined;
|
|
53
53
|
let oldestCachedAt: number | null = null;
|
|
@@ -235,9 +235,15 @@ export function reconcileModelCacheProviders(
|
|
|
235
235
|
...liveModelCounts.keys(),
|
|
236
236
|
...cache.keys(),
|
|
237
237
|
]);
|
|
238
|
+
let revokedRemovedProviderAuthority = false;
|
|
238
239
|
for (const provider of trackedProviders) {
|
|
239
240
|
if (validProviders.has(provider)) continue;
|
|
241
|
+
if (!revokedRemovedProviderAuthority) {
|
|
242
|
+
globalCacheGeneration += 1;
|
|
243
|
+
revokedRemovedProviderAuthority = true;
|
|
244
|
+
}
|
|
240
245
|
providerCacheGenerations.set(provider, (providerCacheGenerations.get(provider) ?? 0) + 1);
|
|
246
|
+
providerCacheGenerations.delete(provider);
|
|
241
247
|
deleteCachedProvider(provider);
|
|
242
248
|
failureAt.delete(provider);
|
|
243
249
|
discoveryStatus.delete(provider);
|
|
@@ -22,7 +22,18 @@ export interface NativeMainClaimOptions {
|
|
|
22
22
|
env?: NodeJS.ProcessEnv;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const
|
|
25
|
+
export const NATIVE_MAIN_HARDENED_IDENTITY_MAX_ENTRIES = 32;
|
|
26
|
+
export const hardenedIdentities = new Map<string, string>();
|
|
27
|
+
|
|
28
|
+
export function rememberHardenedIdentity(path: string, identity: string): void {
|
|
29
|
+
hardenedIdentities.delete(path);
|
|
30
|
+
hardenedIdentities.set(path, identity);
|
|
31
|
+
while (hardenedIdentities.size > NATIVE_MAIN_HARDENED_IDENTITY_MAX_ENTRIES) {
|
|
32
|
+
const oldest = hardenedIdentities.keys().next().value;
|
|
33
|
+
if (oldest === undefined) break;
|
|
34
|
+
hardenedIdentities.delete(oldest);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
26
37
|
|
|
27
38
|
export function nativeMainClaimPath(context: NativeProfileContext): string {
|
|
28
39
|
return join(context.codexHome, NATIVE_MAIN_CLAIM_DB);
|
|
@@ -86,7 +97,7 @@ async function openClaimDatabase(
|
|
|
86
97
|
// tests stayed green, because nearly every claim test injects `hardenPath`.
|
|
87
98
|
await (options.hardenPath ?? ((target: string) => hardenStableLockFile(target, platform)))(path);
|
|
88
99
|
assertStableLockFile(path, file);
|
|
89
|
-
|
|
100
|
+
rememberHardenedIdentity(path, identity);
|
|
90
101
|
}
|
|
91
102
|
database = new Database(path, { create: true });
|
|
92
103
|
// Journal-mode negotiation itself may need a database lock. Disable
|