@bitkyc08/opencodex 2.11.0 → 2.11.1
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-Bk-PN-70.css → index-BA1dgo4q.css} +1 -1
- package/gui/dist/assets/index-DDZpgzKk.js +70 -0
- package/gui/dist/index.html +2 -2
- package/package.json +6 -4
- package/src/adapters/anthropic.ts +10 -2
- package/src/adapters/cursor/native-exec-common.ts +6 -2
- package/src/adapters/google-antigravity-replay.ts +3 -2
- package/src/adapters/google-antigravity-wire.ts +38 -6
- package/src/adapters/google.ts +57 -10
- package/src/adapters/openai-chat.ts +19 -2
- package/src/claude/model-info.ts +1 -1
- package/src/cli/account-auth.ts +3 -1
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +15 -1
- package/src/cli/codex-shim-readiness.ts +69 -0
- package/src/cli/combo.ts +5 -0
- package/src/cli/config-command.ts +33 -4
- package/src/cli/doctor.ts +21 -0
- package/src/cli/export-command.ts +6 -6
- package/src/cli/help.ts +6 -6
- package/src/cli/index.ts +22 -5
- package/src/cli/models-runtime.ts +13 -1
- package/src/cli/provider.ts +7 -0
- package/src/clients/config-export.ts +67 -5
- package/src/codex/account-lifecycle.ts +99 -10
- package/src/codex/auth-api.ts +222 -34
- package/src/codex/catalog/account-models.ts +9 -4
- package/src/codex/catalog/aggregation.ts +41 -8
- package/src/codex/catalog/bundled.ts +54 -22
- package/src/codex/catalog/effort.ts +47 -20
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +70 -18
- package/src/codex/catalog/native-models.ts +7 -0
- package/src/codex/catalog/parsing.ts +29 -11
- package/src/codex/catalog/provider-fetch.ts +335 -50
- package/src/codex/catalog/sync.ts +588 -126
- package/src/codex/catalog-refresh-status.ts +87 -0
- package/src/codex/catalog-write-serialization.ts +2 -1
- package/src/codex/catalog.ts +4 -3
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +190 -52
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/features.ts +79 -1
- package/src/codex/history-job.ts +81 -1
- package/src/codex/history-lock.ts +2 -1
- package/src/codex/history-provider.ts +4 -3
- package/src/codex/inject.ts +56 -12
- package/src/codex/model-cache.ts +50 -10
- package/src/codex/transition-state.ts +10 -2
- package/src/codex/user-identity.ts +110 -2
- package/src/combos/index.ts +3 -0
- package/src/combos/types.ts +75 -9
- package/src/config.ts +26 -12
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +12 -10
- package/src/integrations/registry.ts +7 -0
- package/src/integrations/writer.ts +1 -1
- package/src/lib/pinned-http.ts +40 -9
- package/src/lib/process-control.ts +4 -1
- package/src/lib/provider-outbound.ts +42 -9
- package/src/oauth/cursor.ts +25 -5
- package/src/oauth/index.ts +59 -45
- package/src/providers/antigravity-models.ts +115 -3
- package/src/providers/context-cap.ts +13 -5
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +14 -15
- package/src/providers/provider-id-rewrite.ts +29 -0
- package/src/providers/quota.ts +93 -19
- package/src/providers/registry.ts +16 -5
- package/src/providers/slug-codec.ts +13 -6
- package/src/reasoning-effort.ts +22 -0
- package/src/router.ts +0 -20
- package/src/routing/history/indexer.ts +54 -39
- package/src/routing/quota.ts +77 -56
- package/src/server/index.ts +32 -7
- package/src/server/management/combo-routes.ts +43 -19
- package/src/server/management/config-routes.ts +115 -20
- package/src/server/management/model-routes.ts +10 -7
- package/src/server/management/model-rows.ts +6 -2
- package/src/server/management/oauth-account-routes.ts +12 -0
- package/src/server/management/provider-routes.ts +114 -40
- package/src/server/management/routing-profile-routes.ts +0 -22
- package/src/server/management-api.ts +7 -25
- package/src/server/responses/core.ts +3 -3
- package/src/server/responses/policy-fallback.ts +152 -0
- package/src/server/responses.ts +3 -2
- package/src/types.ts +15 -2
- package/src/usage/cost.ts +0 -0
- package/src/vision/describe.ts +3 -1
- package/src/vision/index.ts +17 -6
- package/src/vision/reasoning.ts +55 -0
- package/src/web-search/parse.ts +15 -3
- package/gui/dist/assets/index-BynIEIV-.js +0 -70
|
@@ -4,16 +4,17 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
4
4
|
import { delimiter, dirname, join, resolve } from "node:path";
|
|
5
5
|
import { expandUserPath, loadConfig, readConfigDiagnostics, websocketsEnabled } from "../../config";
|
|
6
6
|
import { shouldSyncCodexOnStart } from "../desired-state";
|
|
7
|
+
import { legacyCustomModelCatalogSlugs } from "../custom-model-catalog-migration";
|
|
7
8
|
import { CODEX_CONFIG_PATH, CODEX_MODELS_CACHE_PATH, DEFAULT_CATALOG_PATH, getCodexHome, readRootTomlString, resolveCodexConfigPath } from "../paths";
|
|
8
9
|
import { clearModelCache, DEFAULT_MODEL_CACHE_TTL_MS, getFreshCached, getStaleCached, isModelsFetchCoolingDown, markModelsFetchFailure, setCached } from "../model-cache";
|
|
9
10
|
import { buildModelsRequest, resolveModelsAuthToken } from "../../oauth";
|
|
10
11
|
import type { OcxConfig, OcxProviderConfig } from "../../types";
|
|
11
12
|
import { modelInList } from "../../types";
|
|
12
13
|
import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
|
|
13
|
-
import {
|
|
14
|
+
import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, resolveMetadataProvider } from "../../generated/model-metadata";
|
|
14
15
|
import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
|
|
15
16
|
import { applyProviderContextCap, providerContextCap } from "../../providers/context-cap";
|
|
16
|
-
import { routedSlug, slugEquals, slugsEquivalent } from "../../providers/slug-codec";
|
|
17
|
+
import { routedSlug, slugEquals, slugEquivalenceKey, slugsEquivalent } from "../../providers/slug-codec";
|
|
17
18
|
import { identifyRoutedModel } from "../../adapters/identity";
|
|
18
19
|
import { filterCursorConfiguredModelsByLiveDiscovery } from "../../adapters/cursor/discovery";
|
|
19
20
|
import { fetchCursorUsableModels } from "../../adapters/cursor/live-models";
|
|
@@ -30,9 +31,9 @@ import { redactSecretString } from "../../lib/redact";
|
|
|
30
31
|
import upstreamModelsSnapshot from "../data/upstream-models.json";
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
import { activeCodexModelsCachePath,
|
|
34
|
+
import { CODEX_CUSTOM_MODEL_CATALOG_KIND, CODEX_PROVIDER_MODEL_CATALOG_KIND, activeCodexModelsCachePath, applyCatalogMetadata, applyMultiAgentMode, applyNativeOpenAiContextOverride, catalogBackupPathFor, catalogHasRoutedEntries, catalogModelSlug, ensureStrictCatalogFields, findNativeTemplate, isDefaultCatalogPath, isRoutedModelCompatibilityExcluded, legacyCatalogBackupPath, normalizeRoutedCatalogEntry, normalizeServiceTiers, readCatalog, readCatalogBackup, readCodexCatalogPath, readNativeBaseline } from "./parsing";
|
|
34
35
|
import type { CatalogModel, MultiAgentMode, RawCatalog, RawEntry } from "./parsing";
|
|
35
|
-
import { applyNativeVisibility, disabledNativeSlugs,
|
|
36
|
+
import { applyNativeVisibility, CODEX_NATIVE_ALIAS_CATALOG_KIND, desktopAllowlistSuppressedNativeSlugs, disabledNativeSlugs, isNativeAliasCatalogEntry, isUnsupportedOpenAiNativeSlug, NATIVE_OPENAI_MODELS, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, shouldUpgradeToUpstreamEntry, SUPPORTED_NATIVE_OPENAI_SLUGS, upstreamNativeEntry } from "./metadata";
|
|
36
37
|
import {
|
|
37
38
|
bundledCatalogCacheState,
|
|
38
39
|
loadBundledCodexCatalog,
|
|
@@ -40,8 +41,14 @@ import {
|
|
|
40
41
|
} from "./bundled";
|
|
41
42
|
import { isMultiAgentV2Enabled } from "../features";
|
|
42
43
|
import { applyCatalogModelMetadata, applyReasoningLevels, catalogEntryEfforts, clampCatalogModelsToCodexSupport, ensureGpt56ReasoningLevels, ensureUltraReasoningLevel, isGpt56NativeSlug } from "./effort";
|
|
43
|
-
import {
|
|
44
|
-
|
|
44
|
+
import {
|
|
45
|
+
clearGatherRoutedModelsInflight,
|
|
46
|
+
filterCatalogVisibleModels,
|
|
47
|
+
gatherRoutedModels,
|
|
48
|
+
lastDropWarnSignature,
|
|
49
|
+
type CatalogGatherProviderModelOutcome,
|
|
50
|
+
} from "./provider-fetch";
|
|
51
|
+
import { accountSelectorShadowCollisionWarnings, clearLastComboCatalogOmissions, comboCatalogWarningSignatures, comboMasqueradeCollisionWarnings, comboUnrestorableShadowWarnings, exactComboCatalogSlugs, openAiApiCollisionWarnings, resolveSlugAliasCollisions, slugAliasCollisionWarnings, warnAccountSelectorShadowedProviderOnce, warnComboMasqueradeCollisionOnce, warnComboUnrestorableShadowOnce } from "./aggregation";
|
|
45
52
|
import type { ComboCatalogOmission } from "./aggregation";
|
|
46
53
|
import {
|
|
47
54
|
withCatalogWriteSerialization,
|
|
@@ -197,7 +204,16 @@ export function isExactComboCatalogModel(
|
|
|
197
204
|
model: CatalogModel | undefined,
|
|
198
205
|
exactComboSlugs: ReadonlySet<string>,
|
|
199
206
|
): boolean {
|
|
200
|
-
return model
|
|
207
|
+
return model?.provider === COMBO_NAMESPACE && exactComboSlugs.has(catalogModelSlug(model));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function isExactComboCatalogEntry(
|
|
211
|
+
entry: RawEntry,
|
|
212
|
+
exactComboSlugs: ReadonlySet<string>,
|
|
213
|
+
): boolean {
|
|
214
|
+
return entry.owned_by === COMBO_NAMESPACE
|
|
215
|
+
&& typeof entry.slug === "string"
|
|
216
|
+
&& exactComboSlugs.has(entry.slug);
|
|
201
217
|
}
|
|
202
218
|
|
|
203
219
|
/**
|
|
@@ -266,8 +282,9 @@ export function deriveEntry(
|
|
|
266
282
|
}
|
|
267
283
|
applyReasoningLevels(e, model?.reasoningEfforts, model?.defaultReasoningEffort, preserveExact);
|
|
268
284
|
normalizeRoutedCatalogEntry(e, model?.parallelToolCalls === true);
|
|
269
|
-
if (model)
|
|
285
|
+
if (model) applyCatalogMetadata(e, model.provider, model.id, model.contextCap);
|
|
270
286
|
applyCatalogModelMetadata(e, model);
|
|
287
|
+
if (model?.catalogKind) e.opencodex_catalog_kind = model.catalogKind;
|
|
271
288
|
} else {
|
|
272
289
|
applyNativeOpenAiContextOverride(e);
|
|
273
290
|
if (isGpt56NativeSlug(slug)) ensureGpt56ReasoningLevels(e);
|
|
@@ -303,8 +320,9 @@ export function deriveEntry(
|
|
|
303
320
|
applyReasoningLevels(entry, isGpt56NativeSlug(slug) ? undefined : ["low", "medium", "high", "xhigh"]);
|
|
304
321
|
if (isGpt56NativeSlug(slug)) ensureGpt56ReasoningLevels(entry);
|
|
305
322
|
}
|
|
306
|
-
if (model && isRouted)
|
|
323
|
+
if (model && isRouted) applyCatalogMetadata(entry, model.provider, model.id, model.contextCap);
|
|
307
324
|
applyCatalogModelMetadata(entry, model);
|
|
325
|
+
if (model?.catalogKind) entry.opencodex_catalog_kind = model.catalogKind;
|
|
308
326
|
if (!isRouted) applyNativeOpenAiContextOverride(entry);
|
|
309
327
|
return ensureStrictCatalogFields(normalizeServiceTiers(entry), {
|
|
310
328
|
preserveExactInputModalities: preserveExact,
|
|
@@ -312,6 +330,21 @@ export function deriveEntry(
|
|
|
312
330
|
});
|
|
313
331
|
}
|
|
314
332
|
|
|
333
|
+
export interface ObservedCatalogEntryBuildInput {
|
|
334
|
+
readonly template: RawEntry | null;
|
|
335
|
+
readonly gptSlugs: readonly string[];
|
|
336
|
+
readonly goModels: readonly CatalogModel[];
|
|
337
|
+
readonly featured?: readonly string[];
|
|
338
|
+
readonly wsEnabled: boolean;
|
|
339
|
+
readonly multiAgentMode: MultiAgentMode;
|
|
340
|
+
readonly exactComboSlugs: ReadonlySet<string>;
|
|
341
|
+
readonly accountSelectors: readonly string[];
|
|
342
|
+
readonly suppressedBareNativeSlugs: ReadonlySet<string>;
|
|
343
|
+
readonly disabledNativeAccountSlugs: ReadonlySet<string>;
|
|
344
|
+
readonly multiAgentV2Enabled: boolean;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** Build entries with the process-observed Codex feature state. */
|
|
315
348
|
export function buildCatalogEntries(
|
|
316
349
|
template: RawEntry | null,
|
|
317
350
|
gptSlugs: string[],
|
|
@@ -321,7 +354,38 @@ export function buildCatalogEntries(
|
|
|
321
354
|
multiAgentMode: MultiAgentMode = "default",
|
|
322
355
|
exactComboSlugs: ReadonlySet<string> = new Set(),
|
|
323
356
|
accountSelectors: readonly string[] = [],
|
|
357
|
+
suppressedBareNativeSlugs: ReadonlySet<string> = new Set(),
|
|
358
|
+
disabledNativeAccountSlugs: ReadonlySet<string> = new Set(),
|
|
324
359
|
): RawEntry[] {
|
|
360
|
+
return buildCatalogEntriesFromObservedState({
|
|
361
|
+
template,
|
|
362
|
+
gptSlugs,
|
|
363
|
+
goModels,
|
|
364
|
+
featured,
|
|
365
|
+
wsEnabled,
|
|
366
|
+
multiAgentMode,
|
|
367
|
+
exactComboSlugs,
|
|
368
|
+
accountSelectors,
|
|
369
|
+
suppressedBareNativeSlugs,
|
|
370
|
+
disabledNativeAccountSlugs,
|
|
371
|
+
multiAgentV2Enabled: isMultiAgentV2Enabled(),
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** Build entries solely from caller-observed inputs, with no feature-state filesystem read. */
|
|
376
|
+
export function buildCatalogEntriesFromObservedState({
|
|
377
|
+
template,
|
|
378
|
+
gptSlugs,
|
|
379
|
+
goModels,
|
|
380
|
+
featured,
|
|
381
|
+
wsEnabled,
|
|
382
|
+
multiAgentMode,
|
|
383
|
+
exactComboSlugs,
|
|
384
|
+
accountSelectors,
|
|
385
|
+
suppressedBareNativeSlugs,
|
|
386
|
+
disabledNativeAccountSlugs,
|
|
387
|
+
multiAgentV2Enabled,
|
|
388
|
+
}: ObservedCatalogEntryBuildInput): RawEntry[] {
|
|
325
389
|
// Codex's models-manager sorts by `priority` ASC and advertises the first 5 picker-visible
|
|
326
390
|
// models to spawn_agent (sort_by_key(priority) + MAX_MODEL_OVERRIDES_IN_SPAWN_AGENT=5). Catalog
|
|
327
391
|
// ARRAY order is discarded — so "featuring" a model = giving it the LOWEST priority (0..N-1) so
|
|
@@ -330,27 +394,70 @@ export function buildCatalogEntries(
|
|
|
330
394
|
const priorityStride = Math.max(accountSelectors.length, 1);
|
|
331
395
|
const out: RawEntry[] = [];
|
|
332
396
|
const nativeEntries: RawEntry[] = [];
|
|
333
|
-
const collisionSkipped = resolveSlugAliasCollisions(goModels);
|
|
397
|
+
const collisionSkipped = resolveSlugAliasCollisions([...goModels]);
|
|
398
|
+
const emittedNativeAliases = new Set<CatalogModel>();
|
|
399
|
+
const emittedNativeAliasSlugs = new Set<string>();
|
|
400
|
+
const nativeAliasesBySlug = new Map<string, CatalogModel>();
|
|
401
|
+
for (const model of goModels) {
|
|
402
|
+
if (model.provider !== COMBO_NAMESPACE
|
|
403
|
+
|| model.nativeAlias !== true
|
|
404
|
+
|| typeof model.alias !== "string"
|
|
405
|
+
|| model.alias.includes("/")) continue;
|
|
406
|
+
if (nativeAliasesBySlug.has(model.alias)) {
|
|
407
|
+
collisionSkipped.add(model);
|
|
408
|
+
if (!slugAliasCollisionWarnings.has(model.alias)) {
|
|
409
|
+
slugAliasCollisionWarnings.add(model.alias);
|
|
410
|
+
console.warn(
|
|
411
|
+
`[opencodex] native combo alias collision on "${model.alias}": keeping the first configured combo and omitting later duplicates from the catalog.`,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
nativeAliasesBySlug.set(model.alias, model);
|
|
417
|
+
}
|
|
334
418
|
const comboPublicSlugs = new Set(goModels
|
|
335
419
|
.filter(model => model.provider === COMBO_NAMESPACE)
|
|
336
420
|
.map(catalogModelSlug));
|
|
337
421
|
for (const slug of gptSlugs) {
|
|
338
|
-
const
|
|
339
|
-
if (rank.has(slug))
|
|
340
|
-
|
|
341
|
-
|
|
422
|
+
const native = deriveEntry(template, slug, "OpenAI native model (Codex OAuth passthrough).", 9);
|
|
423
|
+
if (rank.has(slug)) native.priority = rank.get(slug)!;
|
|
424
|
+
nativeEntries.push(native);
|
|
425
|
+
const nativeAlias = nativeAliasesBySlug.get(slug);
|
|
426
|
+
if (!nativeAlias || collisionSkipped.has(nativeAlias)) {
|
|
427
|
+
if (!suppressedBareNativeSlugs.has(slug)) out.push(native);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const routed = deriveEntry(
|
|
431
|
+
template,
|
|
432
|
+
slug,
|
|
433
|
+
`Routed via opencodex → ${nativeAlias.provider} (${nativeAlias.owned_by ?? nativeAlias.provider}).`,
|
|
434
|
+
5,
|
|
435
|
+
nativeAlias,
|
|
436
|
+
exactComboSlugs,
|
|
437
|
+
);
|
|
438
|
+
routed.opencodex_catalog_kind = CODEX_NATIVE_ALIAS_CATALOG_KIND;
|
|
439
|
+
const rankHit = rank.get(slug) ?? rank.get(`${nativeAlias.provider}/${nativeAlias.id}`);
|
|
440
|
+
if (rankHit !== undefined) routed.priority = rankHit * priorityStride;
|
|
441
|
+
else if (accountSelectors.length > 0) routed.priority = 1_000 + (typeof routed.priority === "number" ? routed.priority : 5);
|
|
442
|
+
out.push(routed);
|
|
443
|
+
emittedNativeAliases.add(nativeAlias);
|
|
444
|
+
emittedNativeAliasSlugs.add(slug);
|
|
342
445
|
}
|
|
343
446
|
for (const [selectorIndex, selector] of accountSelectors.entries()) {
|
|
344
447
|
for (const [nativeIndex, native] of nativeEntries.entries()) {
|
|
345
|
-
const e = JSON.parse(JSON.stringify(native)) as RawEntry;
|
|
346
448
|
const nativeSlug = String(native.slug);
|
|
449
|
+
if (disabledNativeAccountSlugs.has(nativeSlug)) continue;
|
|
450
|
+
const e = JSON.parse(JSON.stringify(native)) as RawEntry;
|
|
347
451
|
const catalogSlug = `${selector}/${nativeSlug}`;
|
|
348
452
|
e.slug = catalogSlug;
|
|
349
453
|
e.display_name = accountBoundNativeDisplayName(selector, native);
|
|
350
454
|
// Codex ignores this OpenCodex extension; preserve the native comp_hash unchanged.
|
|
351
455
|
e.opencodex_catalog_kind = CODEX_ACCOUNT_BOUND_CATALOG_KIND;
|
|
352
456
|
const exactRank = rank.get(catalogSlug);
|
|
353
|
-
|
|
457
|
+
// A bare featured id belongs to the compatibility combo once shadowed. Exact
|
|
458
|
+
// account-qualified picks still rank normally, but the account clone must not
|
|
459
|
+
// inherit the bare alias rank and consume another top spawn_agent slot.
|
|
460
|
+
const inheritedRank = emittedNativeAliasSlugs.has(nativeSlug) ? undefined : rank.get(nativeSlug);
|
|
354
461
|
const featuredRank = exactRank ?? inheritedRank;
|
|
355
462
|
e.priority = featuredRank !== undefined
|
|
356
463
|
? featuredRank * priorityStride + selectorIndex
|
|
@@ -360,7 +467,7 @@ export function buildCatalogEntries(
|
|
|
360
467
|
}
|
|
361
468
|
}
|
|
362
469
|
for (const m of goModels) {
|
|
363
|
-
if (collisionSkipped.has(m)) continue;
|
|
470
|
+
if (collisionSkipped.has(m) || emittedNativeAliases.has(m)) continue;
|
|
364
471
|
const slug = catalogModelSlug(m);
|
|
365
472
|
if (m.provider !== COMBO_NAMESPACE && comboPublicSlugs.has(slug)) {
|
|
366
473
|
warnComboMasqueradeCollisionOnce(slug);
|
|
@@ -376,6 +483,9 @@ export function buildCatalogEntries(
|
|
|
376
483
|
m,
|
|
377
484
|
exactComboSlugs,
|
|
378
485
|
);
|
|
486
|
+
if (m.provider === COMBO_NAMESPACE && m.nativeAlias === true && !slug.includes("/")) {
|
|
487
|
+
e.opencodex_catalog_kind = CODEX_NATIVE_ALIAS_CATALOG_KIND;
|
|
488
|
+
}
|
|
379
489
|
// Featured picks may be stored raw (legacy) or encoded — honor both.
|
|
380
490
|
const rankHit = rank.get(slug) ?? rank.get(`${m.provider}/${m.id}`);
|
|
381
491
|
if (rankHit !== undefined) e.priority = rankHit * priorityStride;
|
|
@@ -397,7 +507,7 @@ export function buildCatalogEntries(
|
|
|
397
507
|
delete entry.prefer_websockets;
|
|
398
508
|
}
|
|
399
509
|
}
|
|
400
|
-
return applyMultiAgentMode(out, multiAgentMode,
|
|
510
|
+
return applyMultiAgentMode(out, multiAgentMode, multiAgentV2Enabled);
|
|
401
511
|
}
|
|
402
512
|
|
|
403
513
|
export function resetCatalogRuntimeStateForTests(): void {
|
|
@@ -407,9 +517,10 @@ export function resetCatalogRuntimeStateForTests(): void {
|
|
|
407
517
|
comboCatalogWarningSignatures.clear();
|
|
408
518
|
slugAliasCollisionWarnings.clear();
|
|
409
519
|
comboMasqueradeCollisionWarnings.clear();
|
|
520
|
+
comboUnrestorableShadowWarnings.clear();
|
|
410
521
|
accountSelectorShadowCollisionWarnings.clear();
|
|
411
522
|
clearLastComboCatalogOmissions();
|
|
412
|
-
clearModelCache();
|
|
523
|
+
clearModelCache(undefined, "eviction");
|
|
413
524
|
clearGatherRoutedModelsInflight();
|
|
414
525
|
}
|
|
415
526
|
|
|
@@ -437,95 +548,284 @@ export function orderForSubagents(goModels: CatalogModel[], featured?: string[])
|
|
|
437
548
|
* row.
|
|
438
549
|
*/
|
|
439
550
|
function isOcxAuthoredRoutedEntry(entry: RawEntry): boolean {
|
|
551
|
+
if (isNativeAliasCatalogEntry(entry)) return true;
|
|
440
552
|
const desc = typeof entry.description === "string" ? entry.description : "";
|
|
441
553
|
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
442
554
|
return slug.includes("/") && desc.startsWith("Routed via opencodex → ");
|
|
443
555
|
}
|
|
444
556
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
})),
|
|
459
|
-
multiAgentMode: MultiAgentMode = "default",
|
|
460
|
-
exactComboSlugs: ReadonlySet<string> = new Set(),
|
|
461
|
-
hasPhysicalComboProvider = false,
|
|
462
|
-
includeNativeOpenAi = true,
|
|
463
|
-
accountBoundEntries: readonly RawEntry[] = [],
|
|
557
|
+
function recoverableNativeSlug(entry: RawEntry): string | null {
|
|
558
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
559
|
+
return SUPPORTED_NATIVE_OPENAI_SLUGS.has(slug)
|
|
560
|
+
&& !isNativeAliasCatalogEntry(entry)
|
|
561
|
+
&& entry.owned_by !== COMBO_NAMESPACE
|
|
562
|
+
? slug
|
|
563
|
+
: null;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/** Append missing supported native rows from trusted catalog sources only. */
|
|
567
|
+
export function mergeCatalogModelsWithNativeRecovery(
|
|
568
|
+
primaryCatalogModels: readonly RawEntry[],
|
|
569
|
+
nativeRecoverySources: readonly (readonly RawEntry[])[],
|
|
464
570
|
): RawEntry[] {
|
|
571
|
+
const merged = [...primaryCatalogModels];
|
|
572
|
+
const recoveredNativeSlugs = new Set(primaryCatalogModels.flatMap(entry => {
|
|
573
|
+
const slug = recoverableNativeSlug(entry);
|
|
574
|
+
return slug === null ? [] : [slug];
|
|
575
|
+
}));
|
|
576
|
+
for (const source of nativeRecoverySources) {
|
|
577
|
+
for (const entry of source) {
|
|
578
|
+
const slug = recoverableNativeSlug(entry);
|
|
579
|
+
if (slug === null || recoveredNativeSlugs.has(slug)) continue;
|
|
580
|
+
merged.push(structuredClone(entry) as RawEntry);
|
|
581
|
+
recoveredNativeSlugs.add(slug);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return merged;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
export interface ObservedCatalogMergePolicy {
|
|
588
|
+
/** Required observed/fixed set; the core merge never consults ambient catalog state. */
|
|
589
|
+
readonly nativeBackfillSlugs: readonly string[];
|
|
590
|
+
/** Whether unsupported OpenAI-family bare rows survive the merge. */
|
|
591
|
+
readonly unsupportedNativeEntries: "preserve" | "drop";
|
|
592
|
+
/** Whether merge-policy collision/preservation warnings belong to this caller's flow. */
|
|
593
|
+
readonly warningPolicy: "emit" | "suppress";
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** Content policy shared by every writer of the canonical Codex model catalog. */
|
|
597
|
+
export const CANONICAL_NATIVE_CATALOG_CONTENT_POLICY: Readonly<
|
|
598
|
+
Pick<ObservedCatalogMergePolicy, "nativeBackfillSlugs" | "unsupportedNativeEntries">
|
|
599
|
+
> = Object.freeze({
|
|
600
|
+
nativeBackfillSlugs: Object.freeze([...NATIVE_OPENAI_MODELS]),
|
|
601
|
+
unsupportedNativeEntries: "drop",
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
export interface ObservedCatalogMergeInput {
|
|
605
|
+
readonly catalogModels: readonly RawEntry[];
|
|
606
|
+
readonly baselineCatalogModels: readonly RawEntry[];
|
|
607
|
+
readonly routedEntries: readonly RawEntry[];
|
|
608
|
+
readonly baseline: ReadonlyMap<string, number>;
|
|
609
|
+
readonly featured: readonly string[];
|
|
610
|
+
readonly wsEnabled: boolean;
|
|
611
|
+
readonly template: RawEntry | null;
|
|
612
|
+
readonly disabledModels: ReadonlySet<string>;
|
|
613
|
+
readonly selectedModelsByProvider: ReadonlyMap<string, ReadonlySet<string>>;
|
|
614
|
+
readonly gatheredProviderNames: ReadonlySet<string>;
|
|
615
|
+
readonly degradedProviderNames: ReadonlySet<string>;
|
|
616
|
+
readonly legacyCustomModelSlugs: ReadonlySet<string>;
|
|
617
|
+
readonly multiAgentMode: MultiAgentMode;
|
|
618
|
+
readonly multiAgentV2Enabled: boolean;
|
|
619
|
+
readonly exactComboSlugs: ReadonlySet<string>;
|
|
620
|
+
readonly hasPhysicalComboProvider: boolean;
|
|
621
|
+
readonly includeNativeOpenAi: boolean;
|
|
622
|
+
readonly accountBoundEntries: readonly RawEntry[];
|
|
623
|
+
readonly suppressedBareNativeSlugs?: ReadonlySet<string>;
|
|
624
|
+
readonly policy: ObservedCatalogMergePolicy;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Deterministically merge one fully observed catalog state.
|
|
629
|
+
*
|
|
630
|
+
* Every non-catalog input is explicit so evidence-bound convergence cannot
|
|
631
|
+
* accidentally fall back to process-ambient catalog discovery or merge-policy warnings.
|
|
632
|
+
*/
|
|
633
|
+
export function mergeCatalogEntriesFromObservedState({
|
|
634
|
+
catalogModels,
|
|
635
|
+
baselineCatalogModels,
|
|
636
|
+
routedEntries,
|
|
637
|
+
baseline,
|
|
638
|
+
featured,
|
|
639
|
+
wsEnabled,
|
|
640
|
+
template,
|
|
641
|
+
disabledModels,
|
|
642
|
+
selectedModelsByProvider,
|
|
643
|
+
gatheredProviderNames,
|
|
644
|
+
degradedProviderNames,
|
|
645
|
+
legacyCustomModelSlugs,
|
|
646
|
+
multiAgentMode,
|
|
647
|
+
multiAgentV2Enabled,
|
|
648
|
+
exactComboSlugs,
|
|
649
|
+
hasPhysicalComboProvider,
|
|
650
|
+
includeNativeOpenAi,
|
|
651
|
+
accountBoundEntries,
|
|
652
|
+
suppressedBareNativeSlugs = new Set(),
|
|
653
|
+
policy,
|
|
654
|
+
}: ObservedCatalogMergeInput): RawEntry[] {
|
|
655
|
+
// Raw catalog rows contain nested arrays/objects that normalization mutates. Detach every row at
|
|
656
|
+
// the observed-core boundary so callers can safely retain evidence objects or repeat the merge.
|
|
657
|
+
const detachedCatalogModels = catalogModels.map(entry => structuredClone(entry) as RawEntry);
|
|
658
|
+
const detachedBaselineCatalogModels = baselineCatalogModels
|
|
659
|
+
.map(entry => structuredClone(entry) as RawEntry);
|
|
660
|
+
const detachedRoutedEntries = routedEntries.map(entry => structuredClone(entry) as RawEntry);
|
|
661
|
+
const detachedAccountBoundEntries = accountBoundEntries
|
|
662
|
+
.map(entry => structuredClone(entry) as RawEntry);
|
|
663
|
+
const disabledModelKeys = new Set([...disabledModels].map(slugEquivalenceKey));
|
|
664
|
+
const legacyCustomModelKeys = new Set(
|
|
665
|
+
[...legacyCustomModelSlugs].map(slugEquivalenceKey),
|
|
666
|
+
);
|
|
667
|
+
const selectedModelKeysByProvider = new Map([...selectedModelsByProvider].map(([provider, models]) => (
|
|
668
|
+
[provider, new Set([...models].map(model => slugEquivalenceKey(routedSlug(provider, model))))] as const
|
|
669
|
+
)));
|
|
670
|
+
const freshAccountKeys = new Set(detachedAccountBoundEntries.flatMap(entry => (
|
|
671
|
+
typeof entry.slug === "string" ? [slugEquivalenceKey(entry.slug)] : []
|
|
672
|
+
)));
|
|
673
|
+
const wouldSurviveUnreplaced = (entry: RawEntry): boolean => {
|
|
674
|
+
if (entry.owned_by === COMBO_NAMESPACE
|
|
675
|
+
|| trustedAccountBoundNativeCatalogSlug(entry) !== undefined
|
|
676
|
+
|| entry.opencodex_catalog_kind === CODEX_CUSTOM_MODEL_CATALOG_KIND
|
|
677
|
+
|| isOcxAuthoredRoutedEntry(entry)
|
|
678
|
+
|| typeof entry.slug !== "string") return false;
|
|
679
|
+
const slug = entry.slug;
|
|
680
|
+
if (!slug.includes("/")) {
|
|
681
|
+
if (!includeNativeOpenAi || policy.nativeBackfillSlugs.includes(slug)) return false;
|
|
682
|
+
return policy.unsupportedNativeEntries === "preserve" || !isUnsupportedOpenAiNativeSlug(slug);
|
|
683
|
+
}
|
|
684
|
+
if (isRoutedModelCompatibilityExcluded(slug)) return false;
|
|
685
|
+
if (!hasPhysicalComboProvider && slug.startsWith(`${COMBO_NAMESPACE}/`)) return false;
|
|
686
|
+
const key = slugEquivalenceKey(slug);
|
|
687
|
+
if (freshAccountKeys.has(key)) return false;
|
|
688
|
+
if (disabledModelKeys.has(key)) return false;
|
|
689
|
+
const slash = slug.indexOf("/");
|
|
690
|
+
const provider = slug.slice(0, slash);
|
|
691
|
+
const selected = selectedModelKeysByProvider.get(provider);
|
|
692
|
+
if (selected !== undefined && !selected.has(key)) return false;
|
|
693
|
+
return !gatheredProviderNames.has(provider) || degradedProviderNames.has(provider);
|
|
694
|
+
};
|
|
695
|
+
const validRoutedEntries = detachedRoutedEntries.filter(entry => {
|
|
696
|
+
return !isExactComboCatalogEntry(entry, exactComboSlugs)
|
|
697
|
+
|| (Array.isArray(entry.input_modalities) && entry.input_modalities.length > 0);
|
|
698
|
+
});
|
|
699
|
+
const restorableCatalogKeys = new Set(detachedBaselineCatalogModels.flatMap(entry => (
|
|
700
|
+
wouldSurviveUnreplaced(entry) && typeof entry.slug === "string"
|
|
701
|
+
? [slugEquivalenceKey(entry.slug)]
|
|
702
|
+
: []
|
|
703
|
+
)));
|
|
704
|
+
const unrestorableCatalogKeys = new Set(detachedCatalogModels.flatMap(entry => {
|
|
705
|
+
if (!wouldSurviveUnreplaced(entry) || typeof entry.slug !== "string") return [];
|
|
706
|
+
const key = slugEquivalenceKey(entry.slug);
|
|
707
|
+
return restorableCatalogKeys.has(key) ? [] : [key];
|
|
708
|
+
}));
|
|
709
|
+
const admittedRoutedEntries = validRoutedEntries.filter(entry => {
|
|
710
|
+
if (!isExactComboCatalogEntry(entry, exactComboSlugs)) return true;
|
|
711
|
+
const slug = entry.slug as string;
|
|
712
|
+
const key = slugEquivalenceKey(slug);
|
|
713
|
+
if (!unrestorableCatalogKeys.has(key)) return true;
|
|
714
|
+
if (policy.warningPolicy === "emit") warnComboUnrestorableShadowOnce(slug);
|
|
715
|
+
return false;
|
|
716
|
+
});
|
|
717
|
+
// A fresh non-custom row authoritatively resolves a historically ambiguous slug as a normal
|
|
718
|
+
// provider model. Persist that classification so the durable deletion evidence cannot remove
|
|
719
|
+
// the legitimate row during a later degraded refresh.
|
|
720
|
+
for (const entry of admittedRoutedEntries) {
|
|
721
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
722
|
+
if (!slug
|
|
723
|
+
|| entry.opencodex_catalog_kind !== undefined
|
|
724
|
+
|| entry.owned_by === COMBO_NAMESPACE
|
|
725
|
+
|| !isOcxAuthoredRoutedEntry(entry)
|
|
726
|
+
|| !legacyCustomModelKeys.has(slugEquivalenceKey(slug))) continue;
|
|
727
|
+
entry.opencodex_catalog_kind = CODEX_PROVIDER_MODEL_CATALOG_KIND;
|
|
728
|
+
}
|
|
729
|
+
const freshExactComboEntries = new Set(admittedRoutedEntries.filter(entry => (
|
|
730
|
+
isExactComboCatalogEntry(entry, exactComboSlugs)
|
|
731
|
+
&& typeof entry.description === "string"
|
|
732
|
+
&& entry.description.startsWith(`Routed via opencodex → ${COMBO_NAMESPACE} (`)
|
|
733
|
+
)));
|
|
465
734
|
const rank = new Map(featured.map((slug, i) => [slug, i] as const));
|
|
466
|
-
const
|
|
467
|
-
?
|
|
735
|
+
const freshEquivalentKeys = new Set(admittedRoutedEntries.flatMap(entry => (
|
|
736
|
+
typeof entry.slug === "string" ? [slugEquivalenceKey(entry.slug)] : []
|
|
737
|
+
)));
|
|
738
|
+
const freshEquivalent = (slug: string): boolean => (
|
|
739
|
+
freshEquivalentKeys.has(slugEquivalenceKey(slug))
|
|
740
|
+
);
|
|
741
|
+
const freshBareComboAliases = new Set(admittedRoutedEntries.flatMap(entry => (
|
|
742
|
+
typeof entry.slug === "string"
|
|
743
|
+
&& !entry.slug.includes("/")
|
|
744
|
+
&& entry.owned_by === COMBO_NAMESPACE
|
|
745
|
+
? [entry.slug]
|
|
746
|
+
: []
|
|
747
|
+
)));
|
|
748
|
+
const staleComboKeys = new Set(detachedCatalogModels.flatMap(entry => (
|
|
749
|
+
typeof entry.slug === "string"
|
|
750
|
+
&& entry.owned_by === COMBO_NAMESPACE
|
|
751
|
+
&& !freshEquivalent(entry.slug)
|
|
752
|
+
? [slugEquivalenceKey(entry.slug)]
|
|
753
|
+
: []
|
|
754
|
+
)));
|
|
755
|
+
const currentNonComboKeys = new Set(detachedCatalogModels.flatMap(entry => (
|
|
756
|
+
entry.owned_by !== COMBO_NAMESPACE && typeof entry.slug === "string"
|
|
757
|
+
? [slugEquivalenceKey(entry.slug)]
|
|
758
|
+
: []
|
|
759
|
+
)));
|
|
760
|
+
const restoredComboShadows = detachedBaselineCatalogModels.filter(entry => {
|
|
761
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
762
|
+
if (!slug || entry.owned_by === COMBO_NAMESPACE) return false;
|
|
763
|
+
const key = slugEquivalenceKey(slug);
|
|
764
|
+
return staleComboKeys.has(key) && !currentNonComboKeys.has(key);
|
|
765
|
+
});
|
|
766
|
+
const catalogModelsForMerge = [...detachedCatalogModels, ...restoredComboShadows];
|
|
767
|
+
const nativePriority = (slug: string, fallback: unknown): number => {
|
|
768
|
+
const base = baseline.get(slug)
|
|
769
|
+
?? (typeof fallback === "number" ? fallback : 9);
|
|
770
|
+
if (rank.has(slug)) return rank.get(slug)!;
|
|
771
|
+
return featured.length > 0 ? Math.max(base, featured.length + 100) : base;
|
|
772
|
+
};
|
|
773
|
+
const nativeSourceEntries = includeNativeOpenAi
|
|
774
|
+
? catalogModelsForMerge
|
|
468
775
|
.filter(m => typeof m.slug === "string"
|
|
469
776
|
&& !(m.slug as string).includes("/")
|
|
470
777
|
&& m.owned_by !== COMBO_NAMESPACE
|
|
471
|
-
&&
|
|
472
|
-
|
|
778
|
+
&& (policy.unsupportedNativeEntries === "preserve"
|
|
779
|
+
|| !isUnsupportedOpenAiNativeSlug(m.slug as string)))
|
|
473
780
|
.map(m => {
|
|
474
781
|
const slug = m.slug as string;
|
|
475
|
-
// Featured models rank first (rank order); non-featured natives are pushed below the featured
|
|
476
|
-
// block when any model is featured, else keep their pristine baseline priority.
|
|
477
|
-
const baselinePriority = baseline.get(slug) ?? (m.priority as number);
|
|
478
|
-
const priority = rank.has(slug)
|
|
479
|
-
? rank.get(slug)!
|
|
480
|
-
: featured.length > 0
|
|
481
|
-
? Math.max(typeof baselinePriority === "number" ? baselinePriority : 9, featured.length + 100)
|
|
482
|
-
: baselinePriority;
|
|
483
782
|
// Fallback-quality entries (ocx synthesis / codex-rs model_info fallback: display_name
|
|
484
783
|
// stamped with the bare slug) are upgraded to the pinned upstream snapshot entry so a
|
|
485
784
|
// previously synthesized ladder (e.g. luna advertising ultra) self-heals on sync. A
|
|
486
785
|
// genuine catalog entry (real display name) is preserved untouched.
|
|
487
786
|
if (shouldUpgradeToUpstreamEntry(m)) {
|
|
488
787
|
const upstream = upstreamNativeEntry(slug)!;
|
|
489
|
-
const upgradePriority = rank.has(slug)
|
|
490
|
-
? rank.get(slug)!
|
|
491
|
-
: featured.length > 0
|
|
492
|
-
? Math.max(typeof upstream.priority === "number" ? upstream.priority : 9, featured.length + 100)
|
|
493
|
-
: typeof upstream.priority === "number" ? upstream.priority : priority;
|
|
494
788
|
const finished = finishUpstreamNativeEntry(upstream, 9);
|
|
495
|
-
finished.priority =
|
|
789
|
+
finished.priority = nativePriority(slug, upstream.priority);
|
|
496
790
|
return finished;
|
|
497
791
|
}
|
|
498
|
-
const preserved = normalizeServiceTiers({ ...m, priority });
|
|
792
|
+
const preserved = normalizeServiceTiers({ ...m, priority: nativePriority(slug, m.priority) });
|
|
499
793
|
// Older natives kept from disk still need the mock top tiers (max + ultra always
|
|
500
794
|
// for subagent max spawns; wire-clamped to the model's real top rung).
|
|
501
795
|
if (!isGpt56NativeSlug(slug)) ensureUltraReasoningLevel(preserved);
|
|
502
796
|
return preserved;
|
|
503
797
|
})
|
|
504
798
|
: [];
|
|
799
|
+
const native = nativeSourceEntries.filter(entry =>
|
|
800
|
+
typeof entry.slug !== "string"
|
|
801
|
+
|| (!freshBareComboAliases.has(entry.slug) && !suppressedBareNativeSlugs.has(entry.slug))
|
|
802
|
+
);
|
|
505
803
|
|
|
506
804
|
// Backfill any native OpenAI slug that the on-disk catalog is missing (e.g. gpt-5.5), so a
|
|
507
805
|
// routed provider exposing the same id can never delete the native OpenAI/Codex base row.
|
|
508
806
|
// Skip when no enabled canonical openai provider exists (#636) — bare gpt-* would 404.
|
|
509
807
|
const nativeSlugs = new Set(native.flatMap(m => typeof m.slug === "string" ? [m.slug] : []));
|
|
510
808
|
if (includeNativeOpenAi) {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
809
|
+
for (const slug of policy.nativeBackfillSlugs) {
|
|
810
|
+
if (nativeSlugs.has(slug) || freshBareComboAliases.has(slug) || suppressedBareNativeSlugs.has(slug)) continue;
|
|
811
|
+
nativeSlugs.add(slug);
|
|
812
|
+
const entry = deriveEntry(
|
|
813
|
+
template ? JSON.parse(JSON.stringify(template)) : null,
|
|
814
|
+
slug,
|
|
815
|
+
"OpenAI native model (Codex OAuth passthrough).",
|
|
816
|
+
nativePriority(slug, upstreamNativeEntry(slug)?.priority),
|
|
817
|
+
);
|
|
818
|
+
entry.priority = nativePriority(slug, upstreamNativeEntry(slug)?.priority);
|
|
819
|
+
native.push(entry);
|
|
820
|
+
}
|
|
521
821
|
}
|
|
522
822
|
|
|
523
|
-
const
|
|
823
|
+
const nativeSourceBySlug = new Map([...nativeSourceEntries, ...native].flatMap(entry =>
|
|
524
824
|
typeof entry.slug === "string" ? [[entry.slug, entry] as const] : []
|
|
525
825
|
));
|
|
526
|
-
const alignedAccountBoundEntries =
|
|
826
|
+
const alignedAccountBoundEntries = detachedAccountBoundEntries.map(entry => {
|
|
527
827
|
const nativeSlug = trustedAccountBoundNativeCatalogSlug(entry);
|
|
528
|
-
const source = nativeSlug === undefined ? undefined :
|
|
828
|
+
const source = nativeSlug === undefined ? undefined : nativeSourceBySlug.get(nativeSlug);
|
|
529
829
|
if (!source) return entry;
|
|
530
830
|
const aligned = JSON.parse(JSON.stringify(source)) as RawEntry;
|
|
531
831
|
aligned.slug = entry.slug;
|
|
@@ -537,46 +837,64 @@ export function mergeCatalogEntriesForSync(
|
|
|
537
837
|
});
|
|
538
838
|
|
|
539
839
|
const freshSlugs = new Set(
|
|
540
|
-
|
|
840
|
+
admittedRoutedEntries.flatMap(entry => typeof entry.slug === "string" ? [entry.slug] : []),
|
|
541
841
|
);
|
|
542
|
-
|
|
543
|
-
const existingRoutedEntries = catalogModels.filter(m =>
|
|
842
|
+
const existingRoutedEntries = catalogModelsForMerge.filter(m =>
|
|
544
843
|
typeof m.slug === "string"
|
|
545
|
-
&& m.slug.includes("/")
|
|
844
|
+
&& (m.slug.includes("/") || isNativeAliasCatalogEntry(m))
|
|
546
845
|
&& trustedAccountBoundNativeCatalogSlug(m) === undefined
|
|
547
846
|
);
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
//
|
|
565
|
-
//
|
|
566
|
-
return
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
|
|
847
|
+
const preservedRoutedEntries = existingRoutedEntries.filter(entry => {
|
|
848
|
+
const slug = entry.slug as string;
|
|
849
|
+
if (freshEquivalent(slug)) return false;
|
|
850
|
+
if (isNativeAliasCatalogEntry(entry)) return exactComboSlugs.has(slug);
|
|
851
|
+
// Current custom rows are always regenerated from config, even while provider discovery is
|
|
852
|
+
// degraded. A marked row absent from the fresh projection is therefore an intentional delete.
|
|
853
|
+
if (entry.opencodex_catalog_kind === CODEX_CUSTOM_MODEL_CATALOG_KIND) return false;
|
|
854
|
+
// Before custom rows had a marker, a config deletion could otherwise be mistaken for a
|
|
855
|
+
// provider outage. Only explicit save-boundary evidence may classify an unmarked OpenCodex
|
|
856
|
+
// row; foreign and future-marked rows fail closed and remain preserved.
|
|
857
|
+
if (entry.opencodex_catalog_kind === undefined
|
|
858
|
+
&& entry.owned_by !== COMBO_NAMESPACE
|
|
859
|
+
&& isOcxAuthoredRoutedEntry(entry)
|
|
860
|
+
&& legacyCustomModelKeys.has(slugEquivalenceKey(slug))) return false;
|
|
861
|
+
const provider = slug.slice(0, slug.indexOf("/"));
|
|
862
|
+
if (gatheredProviderNames.has(provider)) {
|
|
863
|
+
// A provider-local degraded observation preserves only that namespace. Authoritative empty
|
|
864
|
+
// catalogs and successful removals still delete stale rows even when another provider fails.
|
|
865
|
+
return degradedProviderNames.has(provider);
|
|
866
|
+
}
|
|
867
|
+
// Deleted/disabled providers cannot retain OpenCodex-authored ghosts. Foreign catalog rows
|
|
868
|
+
// remain outside provider ownership and survive unless a fresh row replaces their exact slug.
|
|
869
|
+
return !isOcxAuthoredRoutedEntry(entry);
|
|
870
|
+
});
|
|
871
|
+
let finalRoutedEntries = [...admittedRoutedEntries, ...preservedRoutedEntries];
|
|
872
|
+
finalRoutedEntries = finalRoutedEntries.filter(entry => {
|
|
873
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
874
|
+
if (!slug.includes("/")) return true;
|
|
875
|
+
if (disabledModelKeys.has(slugEquivalenceKey(slug))) return false;
|
|
876
|
+
// Provider allowlists own provider rows, not a current combo's public alias. Exempt only an
|
|
877
|
+
// identity from this gather's generated combo projection: provider discovery may supply a
|
|
878
|
+
// spoofed `owned_by`, and persisted combo-shaped rows are not fresh authority.
|
|
879
|
+
if (freshExactComboEntries.has(entry)) return true;
|
|
880
|
+
const slash = slug.indexOf("/");
|
|
881
|
+
const provider = slug.slice(0, slash);
|
|
882
|
+
const selected = selectedModelKeysByProvider.get(provider);
|
|
883
|
+
return selected === undefined || selected.has(slugEquivalenceKey(slug));
|
|
884
|
+
});
|
|
570
885
|
if (!hasPhysicalComboProvider) {
|
|
571
886
|
finalRoutedEntries = finalRoutedEntries.filter(entry => {
|
|
572
887
|
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
573
888
|
const comboOwned = slug.startsWith(`${COMBO_NAMESPACE}/`) || entry.owned_by === COMBO_NAMESPACE;
|
|
574
|
-
|
|
889
|
+
const retainedNativeAlias = isNativeAliasCatalogEntry(entry) && exactComboSlugs.has(slug);
|
|
890
|
+
return !comboOwned || freshSlugs.has(slug) || retainedNativeAlias;
|
|
575
891
|
});
|
|
576
892
|
}
|
|
577
893
|
finalRoutedEntries = finalRoutedEntries.filter(entry => {
|
|
578
894
|
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
579
|
-
|
|
895
|
+
const retainedNativeAlias = isNativeAliasCatalogEntry(entry) && exactComboSlugs.has(slug);
|
|
896
|
+
return retainedNativeAlias
|
|
897
|
+
|| !isExactComboCatalogEntry(entry, exactComboSlugs)
|
|
580
898
|
|| (Array.isArray(entry.input_modalities) && entry.input_modalities.length > 0);
|
|
581
899
|
});
|
|
582
900
|
// Reapply final catalog policy to rows preserved from disk. Those rows bypass
|
|
@@ -589,21 +907,30 @@ export function mergeCatalogEntriesForSync(
|
|
|
589
907
|
));
|
|
590
908
|
finalRoutedEntries = finalRoutedEntries.filter(entry => {
|
|
591
909
|
if (typeof entry.slug !== "string" || !accountBoundSlugs.has(entry.slug)) return true;
|
|
592
|
-
if (freshSlugs.has(entry.slug)
|
|
910
|
+
if (freshSlugs.has(entry.slug) && policy.warningPolicy === "emit") {
|
|
911
|
+
warnAccountSelectorShadowedProviderOnce(entry.slug);
|
|
912
|
+
}
|
|
593
913
|
return false;
|
|
594
914
|
});
|
|
595
|
-
|
|
596
|
-
|
|
915
|
+
const finalRoutedEntrySet = new Set(finalRoutedEntries);
|
|
916
|
+
const degradedPreservedCount = preservedRoutedEntries.filter(entry => {
|
|
917
|
+
if (!finalRoutedEntrySet.has(entry)) return false;
|
|
918
|
+
const slug = entry.slug as string;
|
|
919
|
+
const provider = slug.slice(0, slug.indexOf("/"));
|
|
920
|
+
return gatheredProviderNames.has(provider) && degradedProviderNames.has(provider);
|
|
921
|
+
}).length;
|
|
922
|
+
if (degradedPreservedCount > 0 && policy.warningPolicy === "emit") {
|
|
923
|
+
console.warn(`[opencodex] catalog sync: provider discovery degraded; preserving ${degradedPreservedCount} existing routed entr${degradedPreservedCount === 1 ? "y" : "ies"} on disk.`);
|
|
597
924
|
}
|
|
598
925
|
|
|
599
926
|
const managedEntries = [...finalRoutedEntries, ...alignedAccountBoundEntries];
|
|
600
927
|
const mergedEntries = [...native, ...managedEntries].map(m => {
|
|
601
928
|
const normalized = normalizeServiceTiers(m);
|
|
602
|
-
applyNativeOpenAiContextOverride(normalized);
|
|
603
|
-
const exactCombo =
|
|
929
|
+
if (!isNativeAliasCatalogEntry(normalized)) applyNativeOpenAiContextOverride(normalized);
|
|
930
|
+
const exactCombo = isExactComboCatalogEntry(m, exactComboSlugs);
|
|
604
931
|
const e = ensureStrictCatalogFields(normalized, {
|
|
605
932
|
preserveExactInputModalities: exactCombo,
|
|
606
|
-
isRouted:
|
|
933
|
+
isRouted: finalRoutedEntrySet.has(m),
|
|
607
934
|
});
|
|
608
935
|
// Mock-max universality (260709): preserved routed entries from disk may predate
|
|
609
936
|
// the max rung — ensure it here so subagent max spawns validate on every
|
|
@@ -629,11 +956,85 @@ export function mergeCatalogEntriesForSync(
|
|
|
629
956
|
// Native enable/disable runs as the LAST pass so the upstream-upgrade branch above can never
|
|
630
957
|
// clobber a hide flag back to list. Bare ids disable every account clone; qualified ids disable
|
|
631
958
|
// only their generated account row.
|
|
632
|
-
|
|
959
|
+
const versionedEntries = applyMultiAgentMode(
|
|
633
960
|
applyNativeVisibility(mergedEntries, disabledModels, alignedAccountBoundEntries.length > 0),
|
|
634
961
|
multiAgentMode,
|
|
635
|
-
|
|
962
|
+
multiAgentV2Enabled,
|
|
963
|
+
);
|
|
964
|
+
for (const entry of versionedEntries) {
|
|
965
|
+
const kind = entry.opencodex_catalog_kind;
|
|
966
|
+
if (trustedAccountBoundNativeCatalogSlug(entry) === undefined
|
|
967
|
+
&& kind !== CODEX_CUSTOM_MODEL_CATALOG_KIND
|
|
968
|
+
&& kind !== CODEX_PROVIDER_MODEL_CATALOG_KIND) continue;
|
|
969
|
+
// Canonicalize extension-field order after every normalizer. This keeps an unchanged catalog
|
|
970
|
+
// byte-idempotent whether an owned row was freshly built or retained from the prior pass.
|
|
971
|
+
delete entry.opencodex_catalog_kind;
|
|
972
|
+
entry.opencodex_catalog_kind = kind;
|
|
973
|
+
}
|
|
974
|
+
return versionedEntries;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/** Merge retained-sync rows using the process-observed Codex feature state. */
|
|
978
|
+
export function mergeCatalogEntriesForSync(
|
|
979
|
+
catalogModels: RawEntry[],
|
|
980
|
+
routedEntries: RawEntry[],
|
|
981
|
+
baseline: Map<string, number>,
|
|
982
|
+
featured: string[],
|
|
983
|
+
wsEnabled: boolean,
|
|
984
|
+
_goIds: Set<string> = new Set(),
|
|
985
|
+
template: RawEntry | null = null,
|
|
986
|
+
disabledModels: ReadonlySet<string> = new Set(),
|
|
987
|
+
gatheredProviderNames?: Set<string>,
|
|
988
|
+
multiAgentMode: MultiAgentMode = "default",
|
|
989
|
+
exactComboSlugs: ReadonlySet<string> = new Set(),
|
|
990
|
+
hasPhysicalComboProvider = false,
|
|
991
|
+
includeNativeOpenAi = true,
|
|
992
|
+
accountBoundEntries: readonly RawEntry[] = [],
|
|
993
|
+
legacyCustomModelSlugs: ReadonlySet<string> = new Set(),
|
|
994
|
+
suppressedBareNativeSlugs: ReadonlySet<string> = new Set(
|
|
995
|
+
routedEntries.flatMap(entry => (
|
|
996
|
+
isNativeAliasCatalogEntry(entry) && typeof entry.slug === "string" ? [entry.slug] : []
|
|
997
|
+
)),
|
|
998
|
+
),
|
|
999
|
+
): RawEntry[] {
|
|
1000
|
+
// Retained for source compatibility with the original helper contract. Raw provider ids must
|
|
1001
|
+
// not suppress same-named native rows; actual admitted combo entries own that decision now.
|
|
1002
|
+
void _goIds;
|
|
1003
|
+
const effectiveGatheredProviderNames = gatheredProviderNames ?? new Set(
|
|
1004
|
+
routedEntries.flatMap(entry => {
|
|
1005
|
+
// A slashed combo alias is not evidence that its public prefix is an authoritative provider
|
|
1006
|
+
// namespace. Treating it as one would let the combo replace an unrestorable foreign row.
|
|
1007
|
+
if (isExactComboCatalogEntry(entry, exactComboSlugs)) return [];
|
|
1008
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
1009
|
+
const slash = slug.indexOf("/");
|
|
1010
|
+
return slash > 0 ? [slug.slice(0, slash)] : [];
|
|
1011
|
+
}),
|
|
636
1012
|
);
|
|
1013
|
+
return mergeCatalogEntriesFromObservedState({
|
|
1014
|
+
catalogModels,
|
|
1015
|
+
baselineCatalogModels: [],
|
|
1016
|
+
routedEntries,
|
|
1017
|
+
baseline,
|
|
1018
|
+
featured,
|
|
1019
|
+
wsEnabled,
|
|
1020
|
+
template,
|
|
1021
|
+
disabledModels,
|
|
1022
|
+
selectedModelsByProvider: new Map(),
|
|
1023
|
+
gatheredProviderNames: effectiveGatheredProviderNames,
|
|
1024
|
+
degradedProviderNames: new Set(),
|
|
1025
|
+
legacyCustomModelSlugs,
|
|
1026
|
+
multiAgentMode,
|
|
1027
|
+
multiAgentV2Enabled: isMultiAgentV2Enabled(),
|
|
1028
|
+
exactComboSlugs,
|
|
1029
|
+
hasPhysicalComboProvider,
|
|
1030
|
+
includeNativeOpenAi,
|
|
1031
|
+
accountBoundEntries,
|
|
1032
|
+
suppressedBareNativeSlugs,
|
|
1033
|
+
policy: {
|
|
1034
|
+
...CANONICAL_NATIVE_CATALOG_CONTENT_POLICY,
|
|
1035
|
+
warningPolicy: "emit",
|
|
1036
|
+
},
|
|
1037
|
+
});
|
|
637
1038
|
}
|
|
638
1039
|
|
|
639
1040
|
interface RetainedCatalogSyncRead {
|
|
@@ -660,6 +1061,7 @@ interface RetainedCatalogSyncResult {
|
|
|
660
1061
|
interface RetainedCatalogSyncWrite {
|
|
661
1062
|
readonly config: OcxConfig;
|
|
662
1063
|
readonly goModels: CatalogModel[];
|
|
1064
|
+
readonly providerModelOutcomes: readonly CatalogGatherProviderModelOutcome[];
|
|
663
1065
|
readonly comboOmissions: ComboCatalogOmission[];
|
|
664
1066
|
readonly read: RetainedCatalogSyncRead;
|
|
665
1067
|
readonly permit: CatalogWritePermit;
|
|
@@ -679,7 +1081,10 @@ function loadCatalogForRetainedSync(path: string): RawCatalog | null {
|
|
|
679
1081
|
const bundled = isDefaultCatalogPath(path) ? loadBundledCodexCatalog() : null;
|
|
680
1082
|
if (bundled) return JSON.parse(JSON.stringify(bundled)) as RawCatalog;
|
|
681
1083
|
const active = readCatalog(path);
|
|
682
|
-
|
|
1084
|
+
// A valid configured custom file remains the content authority even when it has no bare native
|
|
1085
|
+
// template. The null-template builder is deliberate; a stale backup must not replace active
|
|
1086
|
+
// custom root metadata merely because the current file contains only routed rows.
|
|
1087
|
+
if (active && (!isDefaultCatalogPath(path) || findNativeTemplate(active))) return active;
|
|
683
1088
|
return readCatalog(catalogBackupPathFor(path))
|
|
684
1089
|
?? (isDefaultCatalogPath(path) ? readCatalog(legacyCatalogBackupPath()) : null)
|
|
685
1090
|
?? readCatalog(activeCodexModelsCachePath())
|
|
@@ -780,16 +1185,37 @@ function pristineCatalogBytes(read: RetainedCatalogSyncRead): string | null {
|
|
|
780
1185
|
: `${JSON.stringify(read.catalog, null, 2)}\n`;
|
|
781
1186
|
}
|
|
782
1187
|
|
|
1188
|
+
function catalogModelsForMergeWithNativeRecovery(
|
|
1189
|
+
catalogPath: string,
|
|
1190
|
+
catalog: RawCatalog,
|
|
1191
|
+
onDiskCatalog: RawCatalog | null,
|
|
1192
|
+
): RawEntry[] {
|
|
1193
|
+
const primaryCatalogModels = onDiskCatalog?.models ?? catalog.models ?? [];
|
|
1194
|
+
// Native-alias compatibility can omit disabled native rows from the effective catalog because
|
|
1195
|
+
// Desktop's remote allowlist ignores `visibility: "hide"`. Keep current/pristine native recovery
|
|
1196
|
+
// sources beside the on-disk rows so re-enabling a model restores its real metadata. Routed and
|
|
1197
|
+
// user-authored rows still come only from the on-disk catalog.
|
|
1198
|
+
return mergeCatalogModelsWithNativeRecovery(primaryCatalogModels, [
|
|
1199
|
+
catalog.models ?? [],
|
|
1200
|
+
readCatalogBackup(catalogPath)?.models ?? [],
|
|
1201
|
+
]);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
783
1204
|
function writeRetainedCatalogSync({
|
|
784
1205
|
config,
|
|
785
1206
|
goModels,
|
|
1207
|
+
providerModelOutcomes,
|
|
786
1208
|
comboOmissions,
|
|
787
1209
|
read,
|
|
788
1210
|
permit,
|
|
789
1211
|
owningCodexHome,
|
|
790
1212
|
}: RetainedCatalogSyncWrite): RetainedCatalogSyncResult {
|
|
791
1213
|
const { catalogPath, catalog, onDiskCatalog } = read;
|
|
792
|
-
const catalogModelsForMerge =
|
|
1214
|
+
const catalogModelsForMerge = catalogModelsForMergeWithNativeRecovery(
|
|
1215
|
+
catalogPath,
|
|
1216
|
+
catalog,
|
|
1217
|
+
onDiskCatalog,
|
|
1218
|
+
);
|
|
793
1219
|
const template = findNativeTemplate(catalog);
|
|
794
1220
|
|
|
795
1221
|
try {
|
|
@@ -817,6 +1243,7 @@ function writeRetainedCatalogSync({
|
|
|
817
1243
|
const orderedGoModels = orderForSubagents(enabledGo, featured); // stable tie-break among equal priorities
|
|
818
1244
|
const multiAgentMode: MultiAgentMode = config.multiAgentMode === "v1" || config.multiAgentMode === "v2" ? config.multiAgentMode : "default";
|
|
819
1245
|
const exactComboSlugs = exactComboCatalogSlugs(config);
|
|
1246
|
+
const suppressedBareNativeSlugs = desktopAllowlistSuppressedNativeSlugs(config);
|
|
820
1247
|
const hasPhysicalComboProvider = Object.hasOwn(config.providers, COMBO_NAMESPACE);
|
|
821
1248
|
const includeNativeOpenAi = shouldIncludeNativeOpenAi(config);
|
|
822
1249
|
const includeAccountBoundNativeOpenAi = shouldIncludeAccountBoundNativeOpenAi(config);
|
|
@@ -824,26 +1251,44 @@ function writeRetainedCatalogSync({
|
|
|
824
1251
|
? visibleCodexAccountSelectors(config)
|
|
825
1252
|
: [];
|
|
826
1253
|
const wsEnabled = websocketsEnabled(config);
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
1254
|
+
const multiAgentV2Enabled = isMultiAgentV2Enabled();
|
|
1255
|
+
const goEntries = buildCatalogEntriesFromObservedState({
|
|
1256
|
+
template: template ? JSON.parse(JSON.stringify(template)) : null,
|
|
1257
|
+
gptSlugs: [],
|
|
1258
|
+
goModels: orderedGoModels,
|
|
831
1259
|
featured,
|
|
832
1260
|
wsEnabled,
|
|
833
1261
|
multiAgentMode,
|
|
834
1262
|
exactComboSlugs,
|
|
835
1263
|
accountSelectors,
|
|
836
|
-
|
|
1264
|
+
suppressedBareNativeSlugs,
|
|
1265
|
+
disabledNativeAccountSlugs: new Set(),
|
|
1266
|
+
multiAgentV2Enabled,
|
|
1267
|
+
});
|
|
837
1268
|
// Keep genuine native entries (gpt-*, codex-*) with their real per-model fields and append
|
|
838
1269
|
// routed providers as namespaced slugs. Cursor and other adopted providers can expose model ids
|
|
839
1270
|
// like `gpt-5.5`; those must not delete the native OpenAI/Codex base row.
|
|
1271
|
+
const baselineCatalog = readCatalogBackup(catalogPath);
|
|
840
1272
|
const baseline = readNativeBaseline(catalogPath);
|
|
841
|
-
const goIds = new Set(enabledGo.map(m => m.id));
|
|
842
1273
|
const gatheredProviderNames = new Set(
|
|
843
1274
|
Object.entries(config.providers ?? {})
|
|
844
1275
|
.filter(([, prov]) => prov.disabled !== true)
|
|
845
1276
|
.map(([name]) => name),
|
|
846
1277
|
);
|
|
1278
|
+
const degradedProviderNames = new Set(
|
|
1279
|
+
providerModelOutcomes
|
|
1280
|
+
.filter(outcome => outcome.state === "degraded")
|
|
1281
|
+
.map(outcome => outcome.provider),
|
|
1282
|
+
);
|
|
1283
|
+
const selectedModelsByProvider = new Map<string, ReadonlySet<string>>(
|
|
1284
|
+
Object.entries(config.providers ?? {}).flatMap(([name, provider]) => (
|
|
1285
|
+
provider.disabled !== true
|
|
1286
|
+
&& Array.isArray(provider.selectedModels)
|
|
1287
|
+
&& provider.selectedModels.length > 0
|
|
1288
|
+
? [[name, new Set(provider.selectedModels)] as const]
|
|
1289
|
+
: []
|
|
1290
|
+
)),
|
|
1291
|
+
);
|
|
847
1292
|
// Central WS capability override on the FINAL on-disk catalog (the file Codex reads). Applies to
|
|
848
1293
|
// native AND routed so the advertised flag matches the implemented endpoint (phase 120.4) and a
|
|
849
1294
|
// native template can never leak supports_websockets while the flag is off.
|
|
@@ -851,33 +1296,45 @@ function writeRetainedCatalogSync({
|
|
|
851
1296
|
// bare gpt-* rows that hard-404 via NoEnabledOpenAiProviderError. Keep natives when no
|
|
852
1297
|
// providers are configured yet (fresh install / catalog bootstrap tests).
|
|
853
1298
|
const accountBoundEntries = includeAccountBoundNativeOpenAi && accountSelectors.length > 0
|
|
854
|
-
?
|
|
855
|
-
template ? JSON.parse(JSON.stringify(template)) : null,
|
|
856
|
-
NATIVE_OPENAI_MODELS,
|
|
857
|
-
[],
|
|
1299
|
+
? buildCatalogEntriesFromObservedState({
|
|
1300
|
+
template: template ? JSON.parse(JSON.stringify(template)) : null,
|
|
1301
|
+
gptSlugs: NATIVE_OPENAI_MODELS,
|
|
1302
|
+
goModels: [],
|
|
858
1303
|
featured,
|
|
859
1304
|
wsEnabled,
|
|
860
1305
|
multiAgentMode,
|
|
861
1306
|
exactComboSlugs,
|
|
862
1307
|
accountSelectors,
|
|
863
|
-
|
|
1308
|
+
suppressedBareNativeSlugs,
|
|
1309
|
+
disabledNativeAccountSlugs: new Set([...disabledNativeSlugs(config)].filter(slug => suppressedBareNativeSlugs.has(slug))),
|
|
1310
|
+
multiAgentV2Enabled,
|
|
1311
|
+
}).filter(entry => trustedAccountBoundNativeCatalogSlug(entry) !== undefined)
|
|
864
1312
|
: [];
|
|
865
|
-
catalog.models =
|
|
866
|
-
catalogModelsForMerge,
|
|
867
|
-
|
|
1313
|
+
catalog.models = mergeCatalogEntriesFromObservedState({
|
|
1314
|
+
catalogModels: catalogModelsForMerge,
|
|
1315
|
+
baselineCatalogModels: baselineCatalog?.models ?? [],
|
|
1316
|
+
routedEntries: goEntries,
|
|
868
1317
|
baseline,
|
|
869
1318
|
featured,
|
|
870
1319
|
wsEnabled,
|
|
871
|
-
goIds,
|
|
872
1320
|
template,
|
|
873
|
-
new Set(config.disabledModels ?? []),
|
|
1321
|
+
disabledModels: new Set(config.disabledModels ?? []),
|
|
1322
|
+
selectedModelsByProvider,
|
|
874
1323
|
gatheredProviderNames,
|
|
1324
|
+
degradedProviderNames,
|
|
1325
|
+
legacyCustomModelSlugs: legacyCustomModelCatalogSlugs(config),
|
|
875
1326
|
multiAgentMode,
|
|
1327
|
+
multiAgentV2Enabled,
|
|
876
1328
|
exactComboSlugs,
|
|
877
1329
|
hasPhysicalComboProvider,
|
|
878
1330
|
includeNativeOpenAi,
|
|
879
1331
|
accountBoundEntries,
|
|
880
|
-
|
|
1332
|
+
suppressedBareNativeSlugs,
|
|
1333
|
+
policy: {
|
|
1334
|
+
...CANONICAL_NATIVE_CATALOG_CONTENT_POLICY,
|
|
1335
|
+
warningPolicy: "emit",
|
|
1336
|
+
},
|
|
1337
|
+
});
|
|
881
1338
|
clampCatalogModelsToCodexSupport(catalog.models);
|
|
882
1339
|
|
|
883
1340
|
replaceActiveCodexCatalog(permit, owningCodexHome, {
|
|
@@ -953,6 +1410,7 @@ export async function syncCatalogModels(config: OcxConfig): Promise<RetainedCata
|
|
|
953
1410
|
}
|
|
954
1411
|
|
|
955
1412
|
const comboOmissions: ComboCatalogOmission[] = [];
|
|
1413
|
+
const providerModelOutcomes: CatalogGatherProviderModelOutcome[] = [];
|
|
956
1414
|
// Settle the bundled template, then baseline, and only then await. Reading it
|
|
957
1415
|
// here makes the memo ours before anyone else can move it, so a bundled swap
|
|
958
1416
|
// during the await is an outside change rather than our own side effect.
|
|
@@ -966,7 +1424,10 @@ export async function syncCatalogModels(config: OcxConfig): Promise<RetainedCata
|
|
|
966
1424
|
evidence: retainedCatalogSyncEvidence(config, preflightRead.catalogPath, preflightRead.catalog),
|
|
967
1425
|
processEvidence: retainedCatalogProcessEvidence(),
|
|
968
1426
|
};
|
|
969
|
-
const goModels = await gatherRoutedModels(config, {
|
|
1427
|
+
const goModels = await gatherRoutedModels(config, {
|
|
1428
|
+
comboOmissions,
|
|
1429
|
+
providerModelOutcomes,
|
|
1430
|
+
});
|
|
970
1431
|
const committed = withCatalogWriteSerialization(owningCodexHome, permit => {
|
|
971
1432
|
// Desired state can flip OFF during the provider await above. The catalog
|
|
972
1433
|
// evidence revalidation below cannot see that — intent lives in our config,
|
|
@@ -987,6 +1448,7 @@ export async function syncCatalogModels(config: OcxConfig): Promise<RetainedCata
|
|
|
987
1448
|
return writeRetainedCatalogSync({
|
|
988
1449
|
config,
|
|
989
1450
|
goModels,
|
|
1451
|
+
providerModelOutcomes,
|
|
990
1452
|
comboOmissions,
|
|
991
1453
|
read: current,
|
|
992
1454
|
permit,
|