@bitkyc08/opencodex 2.11.1 → 2.12.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/README.md +1 -1
- package/bin/ocx.mjs +4 -0
- package/gui/dist/assets/index-BNVYzdn0.css +1 -0
- package/gui/dist/assets/index-Cw1_mxO8.js +70 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/command-code.ts +94 -5
- package/src/adapters/cursor/live-transport.ts +14 -1
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google.ts +24 -7
- package/src/adapters/openai-chat.ts +233 -305
- package/src/adapters/openai-responses.ts +62 -0
- package/src/adapters/tool-catalog-nudge.ts +2 -11
- package/src/claude/outbound.ts +42 -14
- package/src/cli/claude.ts +61 -9
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +8 -1
- package/src/cli/config-command.ts +4 -1
- package/src/cli/doctor.ts +10 -0
- package/src/cli/help.ts +15 -0
- package/src/cli/index.ts +27 -11
- package/src/cli/lab.ts +367 -0
- package/src/cli/provider.ts +9 -1
- package/src/clients/config-export.ts +100 -3
- package/src/codex/app-server-processes.ts +11 -8
- package/src/codex/auth-api.ts +92 -28
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/catalog/metadata.ts +7 -3
- package/src/codex/catalog/provider-fetch.ts +191 -47
- package/src/codex/catalog.ts +2 -2
- package/src/codex/history-job.ts +83 -18
- package/src/codex/history-migration-guardian.ts +9 -16
- package/src/codex/history-provider.ts +178 -1
- package/src/codex/history-transition.ts +5 -5
- package/src/codex/history-worker.ts +22 -2
- package/src/codex/native-profile-store.ts +1 -1
- package/src/codex/plan.ts +15 -0
- package/src/codex/quota.ts +6 -6
- package/src/codex/routing.ts +3 -3
- package/src/codex/subagent-model-fallback.ts +246 -22
- package/src/codex/user-identity.ts +63 -12
- package/src/config.ts +265 -32
- package/src/images/loop.ts +3 -1
- package/src/images/plan.ts +23 -13
- package/src/integrations/omp-yaml-source.ts +225 -0
- package/src/integrations/ownership.ts +5 -5
- package/src/integrations/state.ts +45 -6
- package/src/integrations/writer.ts +38 -3
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +24 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +160 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +57 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +191 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +228 -0
- package/src/lab/events/validate.ts +780 -0
- package/src/lab/index.ts +36 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +241 -0
- package/src/lab/ledger/store.ts +263 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +311 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +116 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +344 -0
- package/src/lab/observe/from-live.ts +103 -0
- package/src/lab/paths.ts +65 -0
- package/src/lab/projection/rebuild.ts +477 -0
- package/src/lab/projection/schema.ts +122 -0
- package/src/lab/projection/verdicts.ts +438 -0
- package/src/lab/projection/verification.ts +332 -0
- package/src/lab/query/catalog.ts +72 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/index.ts +34 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +81 -0
- package/src/lab/subject/route-subject.ts +73 -0
- package/src/lib/bounded-body.ts +104 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +45 -0
- package/src/lib/pinned-http.ts +36 -7
- package/src/oauth/index.ts +20 -1
- package/src/oauth/local-token-detect.ts +11 -2
- package/src/oauth/login-cli.ts +38 -4
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/codex-capacity.ts +7 -4
- package/src/providers/command-code-efforts.ts +5 -2
- package/src/providers/derive.ts +105 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/openai-tiers.ts +59 -3
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/quota.ts +67 -21
- package/src/providers/registry.ts +118 -5
- package/src/router.ts +15 -1
- package/src/routing/analytics.ts +8 -7
- package/src/routing/evaluator.ts +51 -5
- package/src/routing/profile.ts +10 -1
- package/src/routing/trace.ts +42 -6
- package/src/server/auth-cors.ts +15 -0
- package/src/server/background-lifecycle.ts +169 -0
- package/src/server/chat-completions.ts +18 -3
- package/src/server/claude-messages.ts +1 -0
- package/src/server/images.ts +78 -11
- package/src/server/index.ts +72 -18
- package/src/server/lifecycle.ts +6 -0
- package/src/server/live.ts +5 -1
- package/src/server/management/agent-settings-routes.ts +41 -24
- package/src/server/management/body.ts +6 -0
- package/src/server/management/config-routes.ts +51 -8
- package/src/server/management/lab-routes.ts +388 -0
- package/src/server/management/logs-usage-routes.ts +19 -1
- package/src/server/management/model-rows.ts +25 -11
- package/src/server/management/native-integration-routes.ts +3 -3
- package/src/server/management/provider-routes.ts +5 -0
- package/src/server/management/shared.ts +8 -1
- package/src/server/management/usage-summary-cache.ts +2 -0
- package/src/server/management/vision-sidecar-options.ts +125 -0
- package/src/server/management-api.ts +2 -0
- package/src/server/request-decompress.ts +106 -5
- package/src/server/responses/collaboration.ts +4 -1
- package/src/server/responses/core.ts +117 -31
- package/src/server/responses-json-events.ts +50 -12
- package/src/server/search.ts +23 -7
- package/src/server/ws-bridge.ts +3 -1
- package/src/service.ts +40 -20
- package/src/types.ts +74 -0
- package/src/update/job.ts +10 -2
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +9 -0
- package/src/usage/log.ts +9 -9
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/describe.ts +2 -2
- package/src/vision/eligibility.ts +229 -0
- package/src/vision/index.ts +20 -2
- package/src/web-search/executor.ts +2 -2
- package/src/web-search/index.ts +4 -2
- package/src/web-search/loop.ts +3 -1
- package/src/web-search/parse.ts +72 -3
- package/gui/dist/assets/index-BA1dgo4q.css +0 -1
- package/gui/dist/assets/index-DDZpgzKk.js +0 -70
package/src/codex/auth-api.ts
CHANGED
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
parseAccountPriority,
|
|
59
59
|
} from "./pool-rotation";
|
|
60
60
|
import { checkAccountIdCollision, getMainChatgptAccountId, readCodexTokens, readCodexTokensResult } from "./auth-collision";
|
|
61
|
+
import { codexPlanValue, isThirtyDayOnlyCodexPlan } from "./plan";
|
|
61
62
|
export { checkAccountIdCollision, getMainChatgptAccountId } from "./auth-collision";
|
|
62
63
|
export { clearAccountNeedsReauth, isAccountNeedsReauth, markAccountNeedsReauth } from "./account-runtime-state";
|
|
63
64
|
import { clearAccountNeedsReauth, isAccountNeedsReauth, markAccountNeedsReauth } from "./account-runtime-state";
|
|
@@ -103,7 +104,8 @@ import type { CodexAccount, CodexAccountCredentials, OcxConfig } from "../types"
|
|
|
103
104
|
import type { CatalogDisposition } from "./convergence-types";
|
|
104
105
|
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
|
|
105
106
|
import { providerCodexAccountMode } from "../providers/registry";
|
|
106
|
-
import { readBoundedResponseBody } from "../lib/bounded-body";
|
|
107
|
+
import { BOUNDED_BODY_MAX_BYTES, readBoundedResponseBody } from "../lib/bounded-body";
|
|
108
|
+
import { cancelBodyOnAbort, signalWithTimeout } from "../lib/abort";
|
|
107
109
|
import {
|
|
108
110
|
oauthAccountHealthFields,
|
|
109
111
|
projectCodexAccountHealth,
|
|
@@ -208,16 +210,11 @@ function codexAccountPersistenceConflict(
|
|
|
208
210
|
: undefined;
|
|
209
211
|
}
|
|
210
212
|
|
|
211
|
-
function isThirtyDayOnlyPlan(plan: string | null | undefined): boolean {
|
|
212
|
-
const normalized = plan?.trim().toLowerCase();
|
|
213
|
-
return normalized === "go" || normalized === "free";
|
|
214
|
-
}
|
|
215
|
-
|
|
216
213
|
function quotaForPlan<T extends Omit<StoredAccountQuota, "updatedAt"> | StoredAccountQuota | null>(
|
|
217
214
|
quota: T,
|
|
218
|
-
plan:
|
|
215
|
+
plan: unknown,
|
|
219
216
|
): T {
|
|
220
|
-
if (!quota || !
|
|
217
|
+
if (!quota || !isThirtyDayOnlyCodexPlan(plan)) return quota;
|
|
221
218
|
return {
|
|
222
219
|
...(quota.monthlyPercent !== undefined ? { monthlyPercent: quota.monthlyPercent } : {}),
|
|
223
220
|
...(quota.monthlyResetAt !== undefined ? { monthlyResetAt: quota.monthlyResetAt } : {}),
|
|
@@ -233,14 +230,15 @@ function poolAccountDto(
|
|
|
233
230
|
paused: boolean,
|
|
234
231
|
priority: number,
|
|
235
232
|
): CodexAuthAccountDto {
|
|
236
|
-
const
|
|
233
|
+
const plan = codexPlanValue(account.plan);
|
|
234
|
+
const quota = quotaForPlan(quotaResult.quota, plan);
|
|
237
235
|
const needsReauth = !hasCredential || quotaResult.needsReauth || isAccountNeedsReauth(account.id);
|
|
238
236
|
const health = projectCodexAccountHealth({ accountId: account.id, needsReauth });
|
|
239
237
|
return {
|
|
240
238
|
id: account.id,
|
|
241
239
|
email: maskEmail(account.email) ?? account.email,
|
|
242
240
|
...(account.alias !== undefined ? { alias: account.alias } : {}),
|
|
243
|
-
...(
|
|
241
|
+
...(plan !== undefined ? { plan } : {}),
|
|
244
242
|
...(account.logLabel !== undefined ? { logLabel: account.logLabel } : {}),
|
|
245
243
|
isMain: false,
|
|
246
244
|
paused,
|
|
@@ -340,6 +338,43 @@ function safeResetCreditConsumeDto(input: unknown): { code: string } {
|
|
|
340
338
|
return { code: typeof obj.code === "string" ? obj.code : "unknown" };
|
|
341
339
|
}
|
|
342
340
|
|
|
341
|
+
type ResetCreditJsonRead =
|
|
342
|
+
| { ok: true; value: unknown }
|
|
343
|
+
| { ok: false };
|
|
344
|
+
|
|
345
|
+
function cancelResponseBodyWithoutWaiting(body: ReadableStream<Uint8Array> | null): void {
|
|
346
|
+
if (!body) return;
|
|
347
|
+
try {
|
|
348
|
+
void body.cancel().catch(() => undefined);
|
|
349
|
+
} catch {
|
|
350
|
+
// Some stream implementations throw synchronously from cancel().
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function readResetCreditJson(
|
|
355
|
+
response: Response,
|
|
356
|
+
signal: AbortSignal,
|
|
357
|
+
): Promise<ResetCreditJsonRead> {
|
|
358
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
359
|
+
if (Number.isSafeInteger(declaredLength)
|
|
360
|
+
&& declaredLength >= 0
|
|
361
|
+
&& declaredLength > BOUNDED_BODY_MAX_BYTES) {
|
|
362
|
+
cancelResponseBodyWithoutWaiting(response.body);
|
|
363
|
+
return { ok: false };
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
const body = await readBoundedResponseBody(response, {
|
|
367
|
+
signal,
|
|
368
|
+
maxBytes: BOUNDED_BODY_MAX_BYTES,
|
|
369
|
+
fatalUtf8: true,
|
|
370
|
+
});
|
|
371
|
+
if (!body.displaySafe || body.truncated || !body.text.trim()) return { ok: false };
|
|
372
|
+
return { ok: true, value: JSON.parse(body.text) as unknown };
|
|
373
|
+
} catch {
|
|
374
|
+
return { ok: false };
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
343
378
|
export function isUnverifiedCodexImportEnabled(): boolean {
|
|
344
379
|
return process.env[MANUAL_IMPORT_ENV] === "1";
|
|
345
380
|
}
|
|
@@ -398,7 +433,7 @@ const POOL_CACHE_TTL = 5 * 60_000;
|
|
|
398
433
|
const POOL_QUOTA_REFRESH_CONCURRENCY = 4;
|
|
399
434
|
|
|
400
435
|
function nonEmptyPlan(value: unknown): string | null {
|
|
401
|
-
return
|
|
436
|
+
return codexPlanValue(value) ?? null;
|
|
402
437
|
}
|
|
403
438
|
|
|
404
439
|
function isRuntimeConfig(config: OcxConfig): boolean {
|
|
@@ -1334,7 +1369,7 @@ export async function handleCodexAuthAPI(
|
|
|
1334
1369
|
if (url.pathname === "/api/codex-auth/accounts" && req.method === "POST") {
|
|
1335
1370
|
if (!isUnverifiedCodexImportEnabled()) return manualImportDisabledResponse();
|
|
1336
1371
|
|
|
1337
|
-
let body: { id: string; email: string; plan?:
|
|
1372
|
+
let body: { id: string; email: string; plan?: unknown; accessToken: string; refreshToken: string; chatgptAccountId: string };
|
|
1338
1373
|
try { body = (await req.json()) as typeof body; } catch { return jsonResponse({ error: "Invalid JSON" }, 400); }
|
|
1339
1374
|
if (!body.id || !body.email || !body.accessToken || !body.refreshToken || !body.chatgptAccountId) {
|
|
1340
1375
|
return jsonResponse({ error: "Missing required fields" }, 400);
|
|
@@ -1349,8 +1384,9 @@ export async function handleCodexAuthAPI(
|
|
|
1349
1384
|
const preflightConflict = codexAccountPersistenceConflict(runtimeConfig, body.id, "create");
|
|
1350
1385
|
if (preflightConflict) return jsonResponse({ error: preflightConflict }, 400);
|
|
1351
1386
|
// 1.1: Duplicate check is scoped by personal vs workspace plan bucket.
|
|
1387
|
+
const plan = codexPlanValue(body.plan);
|
|
1352
1388
|
const derivedAccountId = extractAccountId(undefined, body.accessToken) ?? body.chatgptAccountId;
|
|
1353
|
-
const collision = checkAccountIdCollision(derivedAccountId, body.email,
|
|
1389
|
+
const collision = checkAccountIdCollision(derivedAccountId, body.email, plan);
|
|
1354
1390
|
if (collision.collision) {
|
|
1355
1391
|
return jsonResponse({ error: collision.reason }, 400);
|
|
1356
1392
|
}
|
|
@@ -1363,7 +1399,12 @@ export async function handleCodexAuthAPI(
|
|
|
1363
1399
|
const commitConflict = codexAccountPersistenceConflict(latestConfig, body.id, "create");
|
|
1364
1400
|
if (commitConflict) return jsonResponse({ error: commitConflict }, 400);
|
|
1365
1401
|
const addedAccount = withCodexAccountLogLabel(
|
|
1366
|
-
{
|
|
1402
|
+
{
|
|
1403
|
+
id: body.id,
|
|
1404
|
+
email: body.email,
|
|
1405
|
+
...(plan !== undefined ? { plan } : {}),
|
|
1406
|
+
isMain: false,
|
|
1407
|
+
},
|
|
1367
1408
|
latestConfig.codexAccounts ?? [],
|
|
1368
1409
|
);
|
|
1369
1410
|
const persistence = persistNewCodexAccount(
|
|
@@ -1679,21 +1720,44 @@ export async function handleCodexAuthAPI(
|
|
|
1679
1720
|
|
|
1680
1721
|
try {
|
|
1681
1722
|
const result = await withResetCreditAuth(getRuntimeConfig(config), accountId, async auth => {
|
|
1682
|
-
const
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1723
|
+
const linkedSignal = signalWithTimeout(8000, req.signal);
|
|
1724
|
+
let detachBodyAbort = () => {};
|
|
1725
|
+
try {
|
|
1726
|
+
let resp: Response;
|
|
1727
|
+
try {
|
|
1728
|
+
resp = await fetch(
|
|
1729
|
+
"https://chatgpt.com/backend-api/wham/rate-limit-reset-credits",
|
|
1730
|
+
{
|
|
1731
|
+
headers: {
|
|
1732
|
+
Authorization: `Bearer ${auth.accessToken}`,
|
|
1733
|
+
"ChatGPT-Account-Id": auth.chatgptAccountId,
|
|
1734
|
+
},
|
|
1735
|
+
signal: linkedSignal.signal,
|
|
1736
|
+
},
|
|
1737
|
+
);
|
|
1738
|
+
} catch (error) {
|
|
1739
|
+
if (linkedSignal.signal.aborted) {
|
|
1740
|
+
return jsonResponse({ error: "Invalid upstream reset-credit response" }, 502);
|
|
1741
|
+
}
|
|
1742
|
+
throw error;
|
|
1743
|
+
}
|
|
1744
|
+
// Own the response body before the bounded reader attaches. If the client
|
|
1745
|
+
// disconnects in that narrow window, Bun otherwise tears down the native
|
|
1746
|
+
// body off the awaited path and can report an unhandled rejection.
|
|
1747
|
+
detachBodyAbort = cancelBodyOnAbort(resp.body, linkedSignal.signal);
|
|
1748
|
+
if (!resp.ok) {
|
|
1749
|
+
await resp.body?.cancel().catch(() => {});
|
|
1750
|
+
return jsonResponse({ error: `Upstream error ${resp.status}` }, resp.status);
|
|
1751
|
+
}
|
|
1752
|
+
const parsed = await readResetCreditJson(resp, linkedSignal.signal);
|
|
1753
|
+
if (!parsed.ok) {
|
|
1754
|
+
return jsonResponse({ error: "Invalid upstream reset-credit response" }, 502);
|
|
1755
|
+
}
|
|
1756
|
+
return jsonResponse(safeResetCreditsDto(parsed.value));
|
|
1757
|
+
} finally {
|
|
1758
|
+
detachBodyAbort();
|
|
1759
|
+
linkedSignal.cleanup();
|
|
1695
1760
|
}
|
|
1696
|
-
return jsonResponse(safeResetCreditsDto(await resp.json()));
|
|
1697
1761
|
});
|
|
1698
1762
|
return result.ok ? result.value : result.response;
|
|
1699
1763
|
} catch (e) {
|
|
@@ -5,6 +5,7 @@ import { loadConfig } from "../config";
|
|
|
5
5
|
import { resolveCodexHomeDir } from "./home";
|
|
6
6
|
import { extractAccountId } from "../oauth/chatgpt";
|
|
7
7
|
import { isSelectableCodexPoolAccount } from "./account-id";
|
|
8
|
+
import { codexPlanKey } from "./plan";
|
|
8
9
|
|
|
9
10
|
export interface CodexTokens {
|
|
10
11
|
access_token: string;
|
|
@@ -78,8 +79,9 @@ function normalizedEmail(email: string | undefined | null): string | null {
|
|
|
78
79
|
return trimmed || null;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
function isWorkspacePlan(plan:
|
|
82
|
-
|
|
82
|
+
function isWorkspacePlan(plan: unknown): boolean {
|
|
83
|
+
const key = codexPlanKey(plan);
|
|
84
|
+
return !!key && /team|business|enterprise|workspace|edu/.test(key);
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
// Main login and managed pool accounts are separate duplicate buckets.
|
|
@@ -88,7 +90,7 @@ function isWorkspacePlan(plan: string | undefined | null): boolean {
|
|
|
88
90
|
export function checkAccountIdCollision(
|
|
89
91
|
chatgptAccountId: string,
|
|
90
92
|
email?: string | null,
|
|
91
|
-
plan?:
|
|
93
|
+
plan?: unknown,
|
|
92
94
|
excludeAccountId?: string | null,
|
|
93
95
|
): { collision: true; reason: string } | { collision: false } {
|
|
94
96
|
const candidateEmail = normalizedEmail(email);
|
|
@@ -253,11 +253,11 @@ export function shouldUpgradeToUpstreamEntry(entry: RawEntry): boolean {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
export function nativeOpenAiSlugs(): string[] {
|
|
256
|
-
const live =
|
|
256
|
+
const live = catalogNativeSlugs();
|
|
257
257
|
return live.length > 0 ? unique([...live, ...DOCUMENTED_NATIVE_OPENAI_ADDITIONS]) : NATIVE_OPENAI_MODELS;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
|
|
260
|
+
function catalogNativeSlugs(): string[] {
|
|
261
261
|
const cat = readCurrentCatalogOrCache();
|
|
262
262
|
const models = cat?.models ?? [];
|
|
263
263
|
const live = models.flatMap(entry => {
|
|
@@ -271,7 +271,11 @@ export function listCatalogNativeSlugs(): string[] {
|
|
|
271
271
|
// Deliberately ignore `visibility`: it is a rendered projection of disabledModels and account
|
|
272
272
|
// selectors, so treating it as fresh availability would shrink the supported set between syncs.
|
|
273
273
|
// visibleNativeSlugs applies the current disabledModels source of truth for public consumers.
|
|
274
|
+
return unique([...live, ...accountBound]);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function listCatalogNativeSlugs(): string[] {
|
|
274
278
|
// Ensure documented additions (e.g. gpt-5.3-codex-spark) appear even when the bundled catalog
|
|
275
279
|
// predates the slug — mirrors nativeOpenAiSlugs() which already merges them for /v1/models.
|
|
276
|
-
return unique([...
|
|
280
|
+
return unique([...catalogNativeSlugs(), ...DOCUMENTED_NATIVE_OPENAI_ADDITIONS]);
|
|
277
281
|
}
|
|
@@ -151,6 +151,13 @@ interface CapturedProviderGather {
|
|
|
151
151
|
readonly policy: CatalogProviderDiscoveryPolicySnapshot;
|
|
152
152
|
readonly request: CapturedModelsRequest;
|
|
153
153
|
readonly observedAuth?: ModelsAuthResolution;
|
|
154
|
+
/**
|
|
155
|
+
* Configured model ids this provider must keep even when live discovery omits
|
|
156
|
+
* them — combo targets that are also listed in providers.*.models (OCX-111).
|
|
157
|
+
* Combo-only ids (not in models[]) stay out of the public catalog and are
|
|
158
|
+
* synthesized for combo derivation instead (#1305).
|
|
159
|
+
*/
|
|
160
|
+
readonly retainConfiguredModelIds?: ReadonlySet<string>;
|
|
154
161
|
}
|
|
155
162
|
|
|
156
163
|
interface GatherFlightCapture {
|
|
@@ -381,6 +388,7 @@ function captureProviderGather(
|
|
|
381
388
|
name: string,
|
|
382
389
|
configured: OcxProviderConfig,
|
|
383
390
|
authResolver: ModelsAuthResolver,
|
|
391
|
+
retainConfiguredModelIds?: ReadonlySet<string>,
|
|
384
392
|
): CapturedProviderGather {
|
|
385
393
|
const enriched = detachedClone(configured);
|
|
386
394
|
enrichProviderFromRegistry(name, enriched);
|
|
@@ -422,18 +430,47 @@ function captureProviderGather(
|
|
|
422
430
|
policy,
|
|
423
431
|
request,
|
|
424
432
|
...(observedAuth ? { observedAuth: Object.freeze({ ...observedAuth }) } : {}),
|
|
433
|
+
...(retainConfiguredModelIds && retainConfiguredModelIds.size > 0
|
|
434
|
+
? { retainConfiguredModelIds }
|
|
435
|
+
: {}),
|
|
425
436
|
});
|
|
426
437
|
}
|
|
427
438
|
|
|
439
|
+
/** Model ids each provider must retain for combo catalog derivation (OCX-111). */
|
|
440
|
+
export function configuredComboTargetModelsByProvider(
|
|
441
|
+
config: Pick<OcxConfig, "combos">,
|
|
442
|
+
): Map<string, ReadonlySet<string>> {
|
|
443
|
+
const byProvider = new Map<string, Set<string>>();
|
|
444
|
+
for (const id of listComboIds(config)) {
|
|
445
|
+
const combo = getCombo(config, id);
|
|
446
|
+
if (!combo) continue;
|
|
447
|
+
for (const target of combo.targets) {
|
|
448
|
+
let models = byProvider.get(target.provider);
|
|
449
|
+
if (!models) {
|
|
450
|
+
models = new Set();
|
|
451
|
+
byProvider.set(target.provider, models);
|
|
452
|
+
}
|
|
453
|
+
models.add(target.model);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return byProvider;
|
|
457
|
+
}
|
|
458
|
+
|
|
428
459
|
function captureGatherFlight(
|
|
429
460
|
config: OcxConfig,
|
|
430
461
|
createAuthResolver: ModelsAuthResolverFactory,
|
|
431
462
|
): GatherFlightCapture {
|
|
432
463
|
const providerAuthOutcomes: CatalogGatherProviderAuthOutcome[] = [];
|
|
433
464
|
const authResolver = createAuthResolver(providerAuthOutcomes);
|
|
465
|
+
const comboTargetsByProvider = configuredComboTargetModelsByProvider(config);
|
|
434
466
|
const providers = Object.entries(config.providers)
|
|
435
467
|
.filter(([, provider]) => provider.disabled !== true)
|
|
436
|
-
.map(([name, provider]) => captureProviderGather(
|
|
468
|
+
.map(([name, provider]) => captureProviderGather(
|
|
469
|
+
name,
|
|
470
|
+
provider,
|
|
471
|
+
authResolver,
|
|
472
|
+
comboTargetsByProvider.get(name),
|
|
473
|
+
));
|
|
437
474
|
const discoveryPolicySnapshots = Object.freeze(providers.map(provider => provider.policy));
|
|
438
475
|
return Object.freeze({
|
|
439
476
|
discoveryPolicyIdentity: keyedGatherIdentity("catalog-discovery-policy-v1", discoveryPolicySnapshots),
|
|
@@ -460,6 +497,9 @@ function captureGatherFlight(
|
|
|
460
497
|
// It is the one member of a provider row that is legitimately a function,
|
|
461
498
|
// so it is dropped here rather than allowed to break every encode.
|
|
462
499
|
provider: omitProviderTransportExecutor(provider.provider),
|
|
500
|
+
// Combo retention is capture-time state, not a provider-row field. Two
|
|
501
|
+
// gathers that share providers but differ in combo targets must not join.
|
|
502
|
+
retainConfiguredModelIds: [...(provider.retainConfiguredModelIds ?? [])].sort(),
|
|
463
503
|
}))),
|
|
464
504
|
discoveryPolicySnapshots,
|
|
465
505
|
providers: Object.freeze(providers),
|
|
@@ -846,7 +886,9 @@ function normalizedStringList(value: unknown, maxItems = 32, maxLength = 64): st
|
|
|
846
886
|
function modelCapabilities(item: ProviderModelsApiItem): string[] | undefined {
|
|
847
887
|
const metadata = plainRecord(item.metadata);
|
|
848
888
|
const metadataCapabilities = metadata?.capabilities;
|
|
849
|
-
const capabilityRecord = plainRecord(metadataCapabilities)
|
|
889
|
+
const capabilityRecord = plainRecord(metadataCapabilities)
|
|
890
|
+
?? plainRecord(item.capabilities)
|
|
891
|
+
?? plainRecord(item.features);
|
|
850
892
|
const out = new Set<string>();
|
|
851
893
|
for (const list of [item.capabilities, item.features, item.supported_features, metadataCapabilities]) {
|
|
852
894
|
for (const capability of normalizedStringList(list) ?? []) out.add(capability);
|
|
@@ -876,7 +918,9 @@ function modelInputModalities(
|
|
|
876
918
|
capabilities: readonly string[] | undefined,
|
|
877
919
|
): string[] | undefined {
|
|
878
920
|
const metadata = plainRecord(item.metadata);
|
|
879
|
-
const capabilityRecord = plainRecord(metadata?.capabilities)
|
|
921
|
+
const capabilityRecord = plainRecord(metadata?.capabilities)
|
|
922
|
+
?? plainRecord(item.capabilities)
|
|
923
|
+
?? plainRecord(item.features);
|
|
880
924
|
const explicit = normalizedStringList(
|
|
881
925
|
item.input_modalities
|
|
882
926
|
?? item.modalities
|
|
@@ -903,7 +947,9 @@ function modelInputModalities(
|
|
|
903
947
|
if (inferred.length > 0) return [...new Set(inferred)];
|
|
904
948
|
}
|
|
905
949
|
if (capabilityRecord?.vision === false) return ["text"];
|
|
906
|
-
if (capabilityRecord?.vision === true || capabilities?.some(value =>
|
|
950
|
+
if (capabilityRecord?.vision === true || capabilities?.some(value => (
|
|
951
|
+
value === "vision" || value === "image-input" || value === "image_input"
|
|
952
|
+
))) {
|
|
907
953
|
return ["text", "image"];
|
|
908
954
|
}
|
|
909
955
|
return undefined;
|
|
@@ -923,7 +969,16 @@ export function catalogHintsFromModelsApiItem(providerName: string, item: Provid
|
|
|
923
969
|
item.max_context_length,
|
|
924
970
|
);
|
|
925
971
|
const maxInputTokens = positiveSafeInteger(limits?.max_input_tokens, item.max_input_tokens);
|
|
926
|
-
|
|
972
|
+
// Some OpenAI-compatible catalogs expose the selectable ladder under
|
|
973
|
+
// `reasoning_parameters.efforts` instead of the older `reasoning_efforts` key.
|
|
974
|
+
// Treat both as model metadata: otherwise a valid upstream capability disappears
|
|
975
|
+
// before client exporters (including omp) can advertise it.
|
|
976
|
+
const reasoningParameters = plainRecord(item.reasoning_parameters)
|
|
977
|
+
?? plainRecord(metadata?.reasoning_parameters)
|
|
978
|
+
?? plainRecord(capabilityRecord?.reasoning_parameters);
|
|
979
|
+
const rawReasoningEfforts = capabilityRecord?.reasoning_effort
|
|
980
|
+
?? item.reasoning_efforts
|
|
981
|
+
?? reasoningParameters?.efforts;
|
|
927
982
|
const listedReasoningEfforts = normalizedStringList(rawReasoningEfforts, 8, 24);
|
|
928
983
|
const reasoningEfforts = listedReasoningEfforts
|
|
929
984
|
? sanitizeCodexReasoningEfforts(listedReasoningEfforts)
|
|
@@ -1004,6 +1059,30 @@ async function fetchProviderModelsWithAuth(
|
|
|
1004
1059
|
provider: name,
|
|
1005
1060
|
...catalogHintsFromProviderConfig(name, prov, id, contextCap),
|
|
1006
1061
|
}));
|
|
1062
|
+
const withConfiguredRetention = (
|
|
1063
|
+
models: CatalogModel[],
|
|
1064
|
+
options?: { retainComboTargets?: boolean; warnDrops?: boolean },
|
|
1065
|
+
): CatalogModel[] => {
|
|
1066
|
+
const { models: merged, droppedConfiguredIds } = mergeConfiguredModelsIntoLiveCatalog({
|
|
1067
|
+
name,
|
|
1068
|
+
provider: prov,
|
|
1069
|
+
models,
|
|
1070
|
+
configured,
|
|
1071
|
+
retainConfiguredModelIds: captured.retainConfiguredModelIds,
|
|
1072
|
+
contextCap,
|
|
1073
|
+
seedVertexDefault,
|
|
1074
|
+
retainComboTargets: options?.retainComboTargets,
|
|
1075
|
+
});
|
|
1076
|
+
if (
|
|
1077
|
+
options?.warnDrops === true
|
|
1078
|
+
&& droppedConfiguredIds.length > 0
|
|
1079
|
+
&& name !== OPENAI_API_PROVIDER_ID
|
|
1080
|
+
&& !QUIET_AUTHORITATIVE_CATALOG_PROVIDERS.has(name)
|
|
1081
|
+
) {
|
|
1082
|
+
warnDroppedConfiguredIdsOnce(name, droppedConfiguredIds);
|
|
1083
|
+
}
|
|
1084
|
+
return merged;
|
|
1085
|
+
};
|
|
1007
1086
|
// Static catalogs never need an OAuth refresh or an upstream model request. Clear any
|
|
1008
1087
|
// discovery failure left by an older live configuration even when the account is logged out.
|
|
1009
1088
|
if (prov.liveModels === false) {
|
|
@@ -1047,12 +1126,17 @@ async function fetchProviderModelsWithAuth(
|
|
|
1047
1126
|
// plan (e.g. claude-fable-5) drop out instead of failing ERROR_BAD_MODEL_NAME. Fall back to the seed.
|
|
1048
1127
|
const cachedCursor = getFreshCached(name, ttlMs);
|
|
1049
1128
|
if (cachedCursor) {
|
|
1050
|
-
return observed(
|
|
1129
|
+
return observed(
|
|
1130
|
+
withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedCursor)),
|
|
1131
|
+
"authoritative",
|
|
1132
|
+
);
|
|
1051
1133
|
}
|
|
1052
1134
|
if (isModelsFetchCoolingDown(name)) {
|
|
1053
1135
|
const cooling = getStaleCached(name);
|
|
1054
1136
|
return observed(
|
|
1055
|
-
|
|
1137
|
+
withConfiguredRetention(
|
|
1138
|
+
cooling ? applyConfigHintsToCachedModels(name, prov, cooling) : configured,
|
|
1139
|
+
),
|
|
1056
1140
|
"degraded",
|
|
1057
1141
|
);
|
|
1058
1142
|
}
|
|
@@ -1060,10 +1144,14 @@ async function fetchProviderModelsWithAuth(
|
|
|
1060
1144
|
if (liveResult.ok) {
|
|
1061
1145
|
const available = filterCursorConfiguredModelsByLiveDiscovery(configured, liveResult.models);
|
|
1062
1146
|
const result = available.length > 0 ? available : configured;
|
|
1063
|
-
//
|
|
1064
|
-
|
|
1147
|
+
// Cache the discovery-filtered roster without combo retention so a later
|
|
1148
|
+
// gather can re-apply the current capture's retain set on read.
|
|
1149
|
+
const forCache = withConfiguredRetention(result, { retainComboTargets: false });
|
|
1150
|
+
if (!setCached(name, forCache, Date.now(), cacheGeneration)) {
|
|
1151
|
+
return observed(withConfiguredRetention(configured), "degraded");
|
|
1152
|
+
}
|
|
1065
1153
|
markProviderDiscoveryOk(name, liveResult.models.length);
|
|
1066
|
-
return observed(
|
|
1154
|
+
return observed(withConfiguredRetention(forCache, { warnDrops: true }), "authoritative");
|
|
1067
1155
|
}
|
|
1068
1156
|
if (isCurrentCacheGeneration()) {
|
|
1069
1157
|
markModelsFetchFailure(name);
|
|
@@ -1074,7 +1162,9 @@ async function fetchProviderModelsWithAuth(
|
|
|
1074
1162
|
}
|
|
1075
1163
|
const staleCursor = getStaleCached(name);
|
|
1076
1164
|
return observed(
|
|
1077
|
-
|
|
1165
|
+
withConfiguredRetention(
|
|
1166
|
+
staleCursor ? applyConfigHintsToCachedModels(name, prov, staleCursor) : configured,
|
|
1167
|
+
),
|
|
1078
1168
|
"degraded",
|
|
1079
1169
|
);
|
|
1080
1170
|
}
|
|
@@ -1090,7 +1180,9 @@ async function fetchProviderModelsWithAuth(
|
|
|
1090
1180
|
const fresh = getFreshCached(name, ttlMs);
|
|
1091
1181
|
if (fresh) {
|
|
1092
1182
|
return observed(
|
|
1093
|
-
|
|
1183
|
+
withConfiguredRetention(
|
|
1184
|
+
withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, fresh, contextCap)),
|
|
1185
|
+
),
|
|
1094
1186
|
"authoritative",
|
|
1095
1187
|
); // dedups Codex's frequent /v1/models polling within the TTL
|
|
1096
1188
|
}
|
|
@@ -1099,9 +1191,11 @@ async function fetchProviderModelsWithAuth(
|
|
|
1099
1191
|
// fetch timeout on every catalog poll — the dashboard polls this path per page load.
|
|
1100
1192
|
const stale = getStaleCached(name);
|
|
1101
1193
|
return observed(
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1194
|
+
withConfiguredRetention(
|
|
1195
|
+
stale
|
|
1196
|
+
? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap))
|
|
1197
|
+
: failedDiscoveryConfigured,
|
|
1198
|
+
),
|
|
1105
1199
|
"degraded",
|
|
1106
1200
|
);
|
|
1107
1201
|
}
|
|
@@ -1114,7 +1208,11 @@ async function fetchProviderModelsWithAuth(
|
|
|
1114
1208
|
failure: ProviderModelDiscoveryFailure,
|
|
1115
1209
|
): { models: CatalogModel[]; fallback: "stale" | "configured"; shouldLog: boolean } => {
|
|
1116
1210
|
if (!isCurrentCacheGeneration()) {
|
|
1117
|
-
return {
|
|
1211
|
+
return {
|
|
1212
|
+
models: withConfiguredRetention(failedDiscoveryConfigured),
|
|
1213
|
+
fallback: "configured",
|
|
1214
|
+
shouldLog: false,
|
|
1215
|
+
};
|
|
1118
1216
|
}
|
|
1119
1217
|
// Decide logging BEFORE recording the new status, so we can compare against the prior one and
|
|
1120
1218
|
// suppress an identical repeated failure (#395 log flood). The failure stays observable via the
|
|
@@ -1124,9 +1222,11 @@ async function fetchProviderModelsWithAuth(
|
|
|
1124
1222
|
markProviderDiscoveryFailed(name, failure);
|
|
1125
1223
|
const stale = getStaleCached(name);
|
|
1126
1224
|
return {
|
|
1127
|
-
models:
|
|
1128
|
-
|
|
1129
|
-
|
|
1225
|
+
models: withConfiguredRetention(
|
|
1226
|
+
stale
|
|
1227
|
+
? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap))
|
|
1228
|
+
: failedDiscoveryConfigured,
|
|
1229
|
+
),
|
|
1130
1230
|
fallback: stale ? "stale" : "configured",
|
|
1131
1231
|
shouldLog,
|
|
1132
1232
|
};
|
|
@@ -1202,9 +1302,12 @@ async function fetchProviderModelsWithAuth(
|
|
|
1202
1302
|
...(model.contextWindow ? { contextWindow: model.contextWindow } : {}),
|
|
1203
1303
|
...(model.inputModalities ? { inputModalities: model.inputModalities } : {}),
|
|
1204
1304
|
}, contextCap));
|
|
1205
|
-
|
|
1305
|
+
const forCache = withConfiguredRetention(live, { retainComboTargets: false });
|
|
1306
|
+
if (!setCached(name, forCache, Date.now(), cacheGeneration)) {
|
|
1307
|
+
return observed(withConfiguredRetention(configured), "degraded");
|
|
1308
|
+
}
|
|
1206
1309
|
markProviderDiscoveryOk(name, live.length);
|
|
1207
|
-
return observed(
|
|
1310
|
+
return observed(withConfiguredRetention(forCache, { warnDrops: true }), "authoritative");
|
|
1208
1311
|
}
|
|
1209
1312
|
const extracted = extractProviderModelItems(bounded.value, discovery);
|
|
1210
1313
|
if (!extracted.ok) {
|
|
@@ -1236,37 +1339,24 @@ async function fetchProviderModelsWithAuth(
|
|
|
1236
1339
|
// Capture the count BEFORE the alias/configured augmentation below pushes extra rows into
|
|
1237
1340
|
// `live`; otherwise configured entries would be reported as discovered ones.
|
|
1238
1341
|
const liveModelCount = live.length;
|
|
1239
|
-
|
|
1240
|
-
//
|
|
1241
|
-
//
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
const dated = live.find(l => isDatedVariantId(l.id, m.id));
|
|
1249
|
-
if (dated) {
|
|
1250
|
-
// Reapply config hints so alias-keyed overrides (modelContextWindows etc.) win.
|
|
1251
|
-
live.push(applyProviderConfigHints(name, prov, { ...dated, id: m.id }, contextCap));
|
|
1252
|
-
} else if (seedVertexDefault || shouldRetainConfiguredProviderModel(name, m.id)) {
|
|
1253
|
-
live.push(m);
|
|
1254
|
-
} else {
|
|
1255
|
-
droppedConfiguredIds.push(m.id);
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
if (live.length === 0 && name !== OPENAI_API_PROVIDER_ID) {
|
|
1342
|
+
// Dated-release aliases + configured retention (compat allow-list, combo targets,
|
|
1343
|
+
// Vertex default). Cache without combo retention so a later gather re-applies the
|
|
1344
|
+
// current capture's retain set on read (warm-cache OCX-111 / #1308).
|
|
1345
|
+
const forCache = withConfiguredRetention(live, { retainComboTargets: false });
|
|
1346
|
+
const returned = withConfiguredRetention(forCache, { warnDrops: true });
|
|
1347
|
+
const droppedConfiguredIds = configured
|
|
1348
|
+
.map(model => model.id)
|
|
1349
|
+
.filter(id => !returned.some(model => model.id === id));
|
|
1350
|
+
if (returned.length === 0 && name !== OPENAI_API_PROVIDER_ID) {
|
|
1259
1351
|
console.warn(
|
|
1260
1352
|
`[opencodex] Provider model discovery for "${name}" returned an authoritative empty catalog; ${droppedConfiguredIds.length > 0 ? `dropping configured model ids: ${droppedConfiguredIds.join(", ")}` : "no models will be exposed"}.`,
|
|
1261
1353
|
);
|
|
1262
|
-
} else if (droppedConfiguredIds.length > 0
|
|
1263
|
-
&& name !== OPENAI_API_PROVIDER_ID
|
|
1264
|
-
&& !QUIET_AUTHORITATIVE_CATALOG_PROVIDERS.has(name)) {
|
|
1265
|
-
warnDroppedConfiguredIdsOnce(name, droppedConfiguredIds);
|
|
1266
1354
|
}
|
|
1267
|
-
if (!setCached(name,
|
|
1355
|
+
if (!setCached(name, forCache, Date.now(), cacheGeneration)) {
|
|
1356
|
+
return observed(withConfiguredRetention(configured), "degraded");
|
|
1357
|
+
}
|
|
1268
1358
|
markProviderDiscoveryOk(name, liveModelCount);
|
|
1269
|
-
return observed(
|
|
1359
|
+
return observed(returned, "authoritative");
|
|
1270
1360
|
} catch (error) {
|
|
1271
1361
|
if (error instanceof ProviderOutboundPolicyError) {
|
|
1272
1362
|
const { models, fallback, shouldLog } = failedDiscoveryFallback({ reason: "blocked" });
|
|
@@ -1313,6 +1403,60 @@ export function shouldRetainConfiguredProviderModel(providerName: string, modelI
|
|
|
1313
1403
|
return false;
|
|
1314
1404
|
}
|
|
1315
1405
|
|
|
1406
|
+
/**
|
|
1407
|
+
* Fold dated-release aliases and retain configured rows that must survive an
|
|
1408
|
+
* authoritative live roster (compatibility allow-list, combo targets, Vertex
|
|
1409
|
+
* default). Used on every discovery return — live, fresh cache, stale, and
|
|
1410
|
+
* failure fallback — so a warm cache captured before a combo existed still
|
|
1411
|
+
* surfaces the configured target (OCX-111 / #1308).
|
|
1412
|
+
*
|
|
1413
|
+
* Cache writes should pass `retainComboTargets: false` so combo retention is
|
|
1414
|
+
* re-applied on read against the current capture, not frozen into the TTL entry.
|
|
1415
|
+
*/
|
|
1416
|
+
export function mergeConfiguredModelsIntoLiveCatalog(opts: {
|
|
1417
|
+
name: string;
|
|
1418
|
+
provider: OcxProviderConfig;
|
|
1419
|
+
models: readonly CatalogModel[];
|
|
1420
|
+
configured: readonly CatalogModel[];
|
|
1421
|
+
retainConfiguredModelIds?: ReadonlySet<string>;
|
|
1422
|
+
contextCap?: number;
|
|
1423
|
+
seedVertexDefault?: boolean;
|
|
1424
|
+
retainComboTargets?: boolean;
|
|
1425
|
+
}): { models: CatalogModel[]; droppedConfiguredIds: string[] } {
|
|
1426
|
+
const {
|
|
1427
|
+
name,
|
|
1428
|
+
provider: prov,
|
|
1429
|
+
configured,
|
|
1430
|
+
retainConfiguredModelIds,
|
|
1431
|
+
contextCap,
|
|
1432
|
+
seedVertexDefault,
|
|
1433
|
+
retainComboTargets = true,
|
|
1434
|
+
} = opts;
|
|
1435
|
+
const out = [...opts.models];
|
|
1436
|
+
const present = new Set(out.map(model => model.id));
|
|
1437
|
+
const droppedConfiguredIds: string[] = [];
|
|
1438
|
+
for (const candidate of configured) {
|
|
1439
|
+
if (present.has(candidate.id)) continue;
|
|
1440
|
+
const dated = out.find(live => isDatedVariantId(live.id, candidate.id));
|
|
1441
|
+
if (dated) {
|
|
1442
|
+
out.push(applyProviderConfigHints(name, prov, { ...dated, id: candidate.id }, contextCap));
|
|
1443
|
+
present.add(candidate.id);
|
|
1444
|
+
continue;
|
|
1445
|
+
}
|
|
1446
|
+
if (
|
|
1447
|
+
seedVertexDefault === true
|
|
1448
|
+
|| shouldRetainConfiguredProviderModel(name, candidate.id)
|
|
1449
|
+
|| (retainComboTargets && retainConfiguredModelIds?.has(candidate.id) === true)
|
|
1450
|
+
) {
|
|
1451
|
+
out.push(candidate);
|
|
1452
|
+
present.add(candidate.id);
|
|
1453
|
+
continue;
|
|
1454
|
+
}
|
|
1455
|
+
droppedConfiguredIds.push(candidate.id);
|
|
1456
|
+
}
|
|
1457
|
+
return { models: out, droppedConfiguredIds };
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1316
1460
|
export function filterCatalogVisibleModels(
|
|
1317
1461
|
models: CatalogModel[],
|
|
1318
1462
|
config: Pick<OcxConfig, "disabledModels" | "providers">,
|
package/src/codex/catalog.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
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 { CODEX_NATIVE_ALIAS_CATALOG_KIND, NATIVE_OPENAI_MODELS, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, isNativeAliasCatalogEntry, nativeOpenAiContextWindow, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs, nativeReasoningEfforts, nativeDefaultReasoningEffort, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi } from "./catalog/metadata";
|
|
5
|
+
export { CODEX_NATIVE_ALIAS_CATALOG_KIND, NATIVE_OPENAI_MODELS, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, isNativeAliasCatalogEntry, nativeOpenAiContextWindow, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, 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
|
-
export { applyProviderConfigHints, isDatedVariantId, filterCatalogVisibleModels, gatherRoutedModels, clearGatherRoutedModelsInflight, augmentRoutedModelsWithRegistryOpenAiApiRows, augmentRoutedModelsWithMetadata, resolveComboCatalogMember } from "./catalog/provider-fetch";
|
|
8
|
+
export { applyProviderConfigHints, isDatedVariantId, filterCatalogVisibleModels, gatherRoutedModels, clearGatherRoutedModelsInflight, augmentRoutedModelsWithRegistryOpenAiApiRows, augmentRoutedModelsWithMetadata, resolveComboCatalogMember, configuredComboTargetModelsByProvider } from "./catalog/provider-fetch";
|
|
9
9
|
export { deriveComboCatalogModel, exactComboCatalogSlugs, getLastComboCatalogOmissions, resetOpenAiApiCatalogWarningStateForTests, uniqueCatalogModelsForPublicList, uniqueCatalogModelsForRawPublicList, buildComboCatalogOmission, comboCatalogOmissionReason, summarizeComboCatalogOmissions } from "./catalog/aggregation";
|
|
10
10
|
export type { ComboCatalogOmission, ComboCatalogOmissionReason } from "./catalog/aggregation";
|
|
11
11
|
export { MAX_SPAWN_AGENT_MODEL_OVERRIDES, CANONICAL_NATIVE_CATALOG_CONTENT_POLICY, effectiveSubagentRoster, buildCatalogEntries, mergeCatalogEntriesFromObservedState, resetCatalogRuntimeStateForTests, orderForSubagents, mergeCatalogEntriesForSync, syncCatalogModels, restoreCodexCatalog, invalidateCodexModelsCache } from "./catalog/sync";
|