@bitkyc08/opencodex 2.55.0 → 2.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/{index-VuoiWj9J.js → index-D4zuyIxQ.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +2 -1
- package/src/adapters/base.ts +21 -0
- package/src/adapters/cursor/transport-retry.ts +46 -1
- package/src/adapters/cursor.ts +4 -0
- package/src/adapters/kiro/adapter.ts +42 -1
- package/src/adapters/kiro-retry.ts +23 -4
- package/src/adapters/openai-chat/errors.ts +116 -0
- package/src/adapters/openai-chat/messages.ts +346 -0
- package/src/adapters/openai-chat/passthrough.ts +146 -0
- package/src/adapters/openai-chat/response-events.ts +117 -0
- package/src/adapters/openai-chat/tool-call-validation.ts +200 -0
- package/src/adapters/openai-chat/tool-schema.ts +477 -0
- package/src/adapters/openai-chat/wire.ts +50 -0
- package/src/adapters/openai-chat.ts +33 -1445
- package/src/adapters/openai-responses/canonical-forward.ts +202 -0
- package/src/adapters/openai-responses/image-gen.ts +406 -0
- package/src/adapters/openai-responses/internal.ts +3 -0
- package/src/adapters/openai-responses/passthrough.ts +611 -0
- package/src/adapters/openai-responses/prompt-cache.ts +83 -0
- package/src/adapters/openai-responses/reasoning.ts +220 -0
- package/src/adapters/openai-responses/request-strips.ts +185 -0
- package/src/adapters/openai-responses/tool-output-recovery.ts +509 -0
- package/src/adapters/openai-responses/tool-schema.ts +293 -0
- package/src/adapters/openai-responses/web-search.ts +156 -0
- package/src/adapters/openai-responses.ts +4 -2625
- package/src/bridge/errors.ts +34 -0
- package/src/bridge/internal.ts +174 -0
- package/src/bridge/response-json.ts +624 -0
- package/src/bridge/sse.ts +1444 -0
- package/src/bridge.ts +5 -2204
- package/src/chat/inbound.ts +12 -1
- package/src/codex/account-lifecycle.ts +3 -0
- package/src/codex/account-store.ts +71 -9
- package/src/codex/auth-api/account-list.ts +507 -0
- package/src/codex/auth-api/http.ts +32 -0
- package/src/codex/auth-api/login-flow.ts +554 -0
- package/src/codex/auth-api/login-state.ts +64 -0
- package/src/codex/auth-api/main-account-probe.ts +331 -0
- package/src/codex/auth-api/pool-mode-gate.ts +274 -0
- package/src/codex/auth-api/pool-quota-probe.ts +512 -0
- package/src/codex/auth-api/reset-credit-service.ts +422 -0
- package/src/codex/auth-api/routes.ts +425 -0
- package/src/codex/auth-api/runtime-config.ts +48 -0
- package/src/codex/auth-api.ts +27 -3118
- package/src/codex/auth-context.ts +95 -28
- package/src/codex/catalog/auto-review.ts +507 -0
- package/src/codex/catalog/build-entries.ts +981 -0
- package/src/codex/catalog/combo-member.ts +375 -0
- package/src/codex/catalog/derive-entry.ts +229 -0
- package/src/codex/catalog/effort.ts +0 -1
- package/src/codex/catalog/gated-native-warn.ts +63 -0
- package/src/codex/catalog/gather-capture.ts +533 -0
- package/src/codex/catalog/model-hints.ts +691 -0
- package/src/codex/catalog/model-visibility.ts +304 -0
- package/src/codex/catalog/provider-fetch.ts +52 -2942
- package/src/codex/catalog/provider-models.ts +685 -0
- package/src/codex/catalog/restore.ts +132 -0
- package/src/codex/catalog/retained-sync.ts +706 -0
- package/src/codex/catalog/routed-gather.ts +858 -0
- package/src/codex/catalog/subagent-roster.ts +176 -0
- package/src/codex/catalog/sync.ts +52 -2698
- package/src/codex/inject/config-toml.ts +563 -0
- package/src/codex/inject/remove.ts +192 -0
- package/src/codex/inject/restore.ts +540 -0
- package/src/codex/inject/routing-classify.ts +109 -0
- package/src/codex/inject/routing-target.ts +125 -0
- package/src/codex/inject.ts +81 -1436
- package/src/codex/lineage.ts +458 -0
- package/src/codex/pool-refresh-backoff.ts +152 -0
- package/src/codex/routing/active-account.ts +194 -0
- package/src/codex/routing/cooldown-math.ts +275 -0
- package/src/codex/routing/health-store.ts +402 -0
- package/src/codex/routing/probe-lease.ts +358 -0
- package/src/codex/routing/selection.ts +703 -0
- package/src/codex/routing/thread-affinity.ts +538 -0
- package/src/codex/routing.ts +353 -2234
- package/src/codex/shim-fingerprint.ts +223 -0
- package/src/codex/shim-inspect.ts +175 -0
- package/src/codex/shim-probe.ts +367 -0
- package/src/codex/shim-restore-lock.ts +169 -0
- package/src/codex/shim-state-file.ts +151 -0
- package/src/codex/shim-templates.ts +265 -0
- package/src/codex/shim.ts +48 -1268
- package/src/config/diagnostics.ts +705 -0
- package/src/config/feature-flags.ts +55 -0
- package/src/config/live-reconcile.ts +403 -0
- package/src/config/load-degrade.ts +880 -0
- package/src/config/mutation-lock.ts +244 -0
- package/src/config/openai-tier-backup.ts +268 -0
- package/src/config/persist-unlocked.ts +92 -0
- package/src/config/proxy-env.ts +188 -0
- package/src/config/salvage.ts +244 -0
- package/src/config/schema/config-schema.ts +640 -0
- package/src/config/schema/leaf-validators.ts +855 -0
- package/src/config/warn-memo.ts +28 -0
- package/src/config.ts +234 -4481
- package/src/generated/compatibility-version.json +539 -39
- package/src/lib/request-execution-budget.ts +69 -20
- package/src/lib/spend-reservation-ledger.ts +940 -0
- package/src/lib/upstream-retry.ts +55 -11
- package/src/lib/workflow-budget.ts +553 -30
- package/src/providers/quota/account-cache.ts +441 -0
- package/src/providers/quota/antigravity.ts +295 -0
- package/src/providers/quota/report-cache.ts +320 -0
- package/src/providers/quota/vendor-probes-key.ts +1243 -0
- package/src/providers/quota/vendor-probes-oauth.ts +590 -0
- package/src/providers/quota.ts +324 -3079
- package/src/providers/registry/entries-core.ts +1221 -0
- package/src/providers/registry/entries-extended.ts +1204 -0
- package/src/providers/registry/model-seeds.ts +908 -0
- package/src/providers/registry/types.ts +352 -0
- package/src/providers/registry.ts +24 -3536
- package/src/responses/continuation-ownership.ts +29 -0
- package/src/responses/state/replay-fingerprint.ts +80 -0
- package/src/responses/state/snapshot-codec.ts +104 -0
- package/src/responses/state/spill-failure.ts +118 -0
- package/src/responses/state/spill-queue.ts +665 -0
- package/src/responses/state/temp-recovery.ts +257 -0
- package/src/responses/state.ts +82 -1143
- package/src/routing/identity-domains.ts +449 -0
- package/src/routing/probe-lease.ts +511 -0
- package/src/server/index/bounded-request.ts +88 -0
- package/src/server/index/live-sideband.ts +565 -0
- package/src/server/index/serve-options.ts +1766 -0
- package/src/server/index/startup-warnings.ts +213 -0
- package/src/server/index/websocket-handler.ts +335 -0
- package/src/server/index.ts +40 -2547
- package/src/server/management/route-registry.ts +26 -23
- package/src/server/management/shared.ts +8 -5
- package/src/server/management/workflow-budget-routes.ts +133 -0
- package/src/server/management-api.ts +12 -0
- package/src/server/request-log-conversation.ts +9 -7
- package/src/server/request-log.ts +245 -1
- package/src/server/responses/account-change-state.ts +233 -0
- package/src/server/responses/adapter-continuation.ts +514 -0
- package/src/server/responses/adapter-delivery.ts +214 -0
- package/src/server/responses/adapter-dispatch.ts +971 -0
- package/src/server/responses/compact.ts +59 -4
- package/src/server/responses/completion-policy.ts +33 -0
- package/src/server/responses/core-auth.ts +527 -0
- package/src/server/responses/core-codex-account.ts +859 -0
- package/src/server/responses/core-combo-failure.ts +210 -0
- package/src/server/responses/core-combo.ts +707 -0
- package/src/server/responses/core-errors.ts +152 -0
- package/src/server/responses/core-lifetime.ts +95 -0
- package/src/server/responses/core-normalize.ts +350 -0
- package/src/server/responses/core-opaque-recovery.ts +380 -0
- package/src/server/responses/core-options.ts +159 -0
- package/src/server/responses/core-replay.ts +225 -0
- package/src/server/responses/core.ts +192 -8893
- package/src/server/responses/passthrough-delivery.ts +856 -0
- package/src/server/responses/passthrough-dispatch.ts +1476 -0
- package/src/server/responses/passthrough-execution.ts +54 -0
- package/src/server/responses/request-prepare.ts +970 -0
- package/src/server/responses/request-send-budget.ts +164 -0
- package/src/server/responses/request-sidecar-auth.ts +149 -0
- package/src/server/responses/request-transport.ts +744 -0
- package/src/server/responses/response-effects.ts +157 -0
- package/src/server/responses/run-turn-execution.ts +448 -0
- package/src/server/responses/sidecar-execution.ts +469 -0
- package/src/server/responses-image-gen-repair.ts +1 -1
- package/src/server/workflow-refusal.ts +84 -0
- package/src/types/config.ts +30 -0
- package/src/usage/log.ts +146 -0
- package/src/usage/summary.ts +171 -21
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
import { CODEX_REASONING_LEVELS } from "../../reasoning-effort";
|
|
2
|
+
import { clearModelCache } from "../model-cache";
|
|
3
|
+
import { routedSlug, slugEquivalenceKey } from "../../providers/slug-codec";
|
|
4
|
+
import { COMBO_NAMESPACE } from "../../combos";
|
|
5
|
+
import {
|
|
6
|
+
CODEX_CUSTOM_MODEL_CATALOG_KIND,
|
|
7
|
+
CODEX_PROVIDER_MODEL_CATALOG_KIND,
|
|
8
|
+
applyMultiAgentMode,
|
|
9
|
+
applyNativeOpenAiContextOverride,
|
|
10
|
+
catalogModelSlug,
|
|
11
|
+
ensureStrictCatalogFields,
|
|
12
|
+
isRoutedModelCompatibilityExcluded,
|
|
13
|
+
normalizeServiceTiers,
|
|
14
|
+
} from "./parsing";
|
|
15
|
+
import type { CatalogModel, MultiAgentMode, RawEntry } from "./parsing";
|
|
16
|
+
import {
|
|
17
|
+
CODEX_NATIVE_ALIAS_CATALOG_KIND,
|
|
18
|
+
NATIVE_OPENAI_MODELS,
|
|
19
|
+
SUPPORTED_NATIVE_OPENAI_SLUGS,
|
|
20
|
+
applyNativeVisibility,
|
|
21
|
+
isNativeAliasCatalogEntry,
|
|
22
|
+
isUnsupportedOpenAiNativeSlug,
|
|
23
|
+
shouldUpgradeToUpstreamEntry,
|
|
24
|
+
upstreamNativeEntry,
|
|
25
|
+
type NativeContextLimitsInput,
|
|
26
|
+
} from "./metadata";
|
|
27
|
+
import { resetBundledCatalogCacheForTests } from "./bundled";
|
|
28
|
+
import { isMultiAgentV2Enabled } from "../features";
|
|
29
|
+
import { ensureUltraReasoningLevel, isGpt56NativeSlug } from "./effort";
|
|
30
|
+
import { clearGatherRoutedModelsInflight, lastDropWarnSignature } from "./provider-fetch";
|
|
31
|
+
import {
|
|
32
|
+
accountSelectorShadowCollisionWarnings,
|
|
33
|
+
clearLastComboCatalogOmissions,
|
|
34
|
+
comboCatalogWarningSignatures,
|
|
35
|
+
comboMasqueradeCollisionWarnings,
|
|
36
|
+
comboUnrestorableShadowWarnings,
|
|
37
|
+
openAiApiCollisionWarnings,
|
|
38
|
+
resolveSlugAliasCollisions,
|
|
39
|
+
slugAliasCollisionWarnings,
|
|
40
|
+
warnAccountSelectorShadowedProviderOnce,
|
|
41
|
+
warnComboMasqueradeCollisionOnce,
|
|
42
|
+
warnComboUnrestorableShadowOnce,
|
|
43
|
+
} from "./aggregation";
|
|
44
|
+
import { accountBoundNativeDisplayName, CODEX_ACCOUNT_BOUND_CATALOG_KIND, trustedAccountBoundNativeCatalogSlug } from "./account-models";
|
|
45
|
+
import { NATIVE_RESERVE_MODEL } from "./native-models";
|
|
46
|
+
import { isReserveCatalogProjection, type ReserveCatalogProjection } from "./reserve";
|
|
47
|
+
import { deriveEntry, finishUpstreamNativeEntry, isExactComboCatalogEntry } from "./derive-entry";
|
|
48
|
+
import { PICKER_ORDER_PRIORITY_BASE, SPAWN_PRIORITY_FIELD } from "./subagent-roster";
|
|
49
|
+
|
|
50
|
+
export interface ObservedCatalogEntryBuildInput {
|
|
51
|
+
readonly template: RawEntry | null;
|
|
52
|
+
readonly gptSlugs: readonly string[];
|
|
53
|
+
readonly goModels: readonly CatalogModel[];
|
|
54
|
+
readonly featured?: readonly string[];
|
|
55
|
+
/** Optional full picker ordering (config.modelPickerOrder); orders non-featured rows. */
|
|
56
|
+
readonly modelPickerOrder?: readonly string[];
|
|
57
|
+
readonly wsEnabled: boolean;
|
|
58
|
+
readonly multiAgentMode: MultiAgentMode;
|
|
59
|
+
readonly exactComboSlugs: ReadonlySet<string>;
|
|
60
|
+
readonly accountSelectors: readonly string[];
|
|
61
|
+
readonly suppressedBareNativeSlugs: ReadonlySet<string>;
|
|
62
|
+
readonly disabledNativeAccountSlugs: ReadonlySet<string>;
|
|
63
|
+
readonly multiAgentV2Enabled: boolean;
|
|
64
|
+
readonly keepNativeChatGptOnV1?: boolean;
|
|
65
|
+
readonly openaiContextCap?: NativeContextLimitsInput;
|
|
66
|
+
/** Additional native ids to clone under account selectors, without creating bare rows. */
|
|
67
|
+
readonly accountNativeSlugs?: readonly string[];
|
|
68
|
+
/** Per-selector account ids; unknown observations must not be copied to unrelated accounts. */
|
|
69
|
+
readonly accountNativeSlugsBySelector?: ReadonlyMap<string, readonly string[]>;
|
|
70
|
+
/** Codex-only manual selector metadata; deliberately independent of live permission. */
|
|
71
|
+
readonly reserve?: ReserveCatalogProjection;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Build entries with the process-observed Codex feature state. */
|
|
75
|
+
export function buildCatalogEntries(
|
|
76
|
+
template: RawEntry | null,
|
|
77
|
+
gptSlugs: string[],
|
|
78
|
+
goModels: CatalogModel[],
|
|
79
|
+
featured?: string[],
|
|
80
|
+
wsEnabled = false,
|
|
81
|
+
multiAgentMode: MultiAgentMode = "default",
|
|
82
|
+
exactComboSlugs: ReadonlySet<string> = new Set(),
|
|
83
|
+
accountSelectors: readonly string[] = [],
|
|
84
|
+
suppressedBareNativeSlugs: ReadonlySet<string> = new Set(),
|
|
85
|
+
disabledNativeAccountSlugs: ReadonlySet<string> = new Set(),
|
|
86
|
+
contextCap?: NativeContextLimitsInput,
|
|
87
|
+
accountNativeSlugs?: readonly string[],
|
|
88
|
+
accountNativeSlugsBySelector?: ReadonlyMap<string, readonly string[]>,
|
|
89
|
+
keepNativeChatGptOnV1 = false,
|
|
90
|
+
modelPickerOrder: readonly string[] = [],
|
|
91
|
+
): RawEntry[] {
|
|
92
|
+
const entries = buildCatalogEntriesFromObservedState({
|
|
93
|
+
template,
|
|
94
|
+
gptSlugs,
|
|
95
|
+
goModels,
|
|
96
|
+
featured,
|
|
97
|
+
modelPickerOrder,
|
|
98
|
+
wsEnabled,
|
|
99
|
+
multiAgentMode,
|
|
100
|
+
exactComboSlugs,
|
|
101
|
+
accountSelectors,
|
|
102
|
+
suppressedBareNativeSlugs,
|
|
103
|
+
disabledNativeAccountSlugs,
|
|
104
|
+
multiAgentV2Enabled: isMultiAgentV2Enabled(),
|
|
105
|
+
keepNativeChatGptOnV1,
|
|
106
|
+
openaiContextCap: contextCap,
|
|
107
|
+
accountNativeSlugs,
|
|
108
|
+
accountNativeSlugsBySelector,
|
|
109
|
+
});
|
|
110
|
+
applyFullModelPickerOrder(entries, modelPickerOrder);
|
|
111
|
+
return entries;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Build entries solely from caller-observed inputs, with no feature-state filesystem read. */
|
|
115
|
+
export function buildCatalogEntriesFromObservedState({
|
|
116
|
+
template,
|
|
117
|
+
gptSlugs,
|
|
118
|
+
goModels,
|
|
119
|
+
featured,
|
|
120
|
+
modelPickerOrder,
|
|
121
|
+
wsEnabled,
|
|
122
|
+
multiAgentMode,
|
|
123
|
+
exactComboSlugs,
|
|
124
|
+
accountSelectors,
|
|
125
|
+
suppressedBareNativeSlugs,
|
|
126
|
+
disabledNativeAccountSlugs,
|
|
127
|
+
multiAgentV2Enabled,
|
|
128
|
+
keepNativeChatGptOnV1,
|
|
129
|
+
openaiContextCap,
|
|
130
|
+
accountNativeSlugs,
|
|
131
|
+
accountNativeSlugsBySelector,
|
|
132
|
+
reserve,
|
|
133
|
+
}: ObservedCatalogEntryBuildInput): RawEntry[] {
|
|
134
|
+
// Codex's models-manager sorts by `priority` ASC and advertises the first 5 picker-visible
|
|
135
|
+
// models to spawn_agent (sort_by_key(priority) + MAX_MODEL_OVERRIDES_IN_SPAWN_AGENT=5). Catalog
|
|
136
|
+
// ARRAY order is discarded — so "featuring" a model = giving it the LOWEST priority (0..N-1) so
|
|
137
|
+
// it sorts to the front. This works for native gpt slugs AND routed slugs alike.
|
|
138
|
+
const rank = new Map((featured ?? []).map((slug, i) => [slug, i] as const));
|
|
139
|
+
const priorityStride = Math.max(accountSelectors.length, 1);
|
|
140
|
+
// Optional full picker order (#1649). Independent of the 5-slot spawn_agent cap: it only
|
|
141
|
+
// rewrites the Codex-visible display `priority` of listed non-featured routed rows so a >5
|
|
142
|
+
// catalog stays put across rebuilds. Featured rows keep their existing 0..N-1 band; when
|
|
143
|
+
// modelPickerOrder is unset the helper is a no-op and every priority below is byte-identical to
|
|
144
|
+
// before. The spawn_agent candidate window is derived separately from SPAWN_PRIORITY_FIELD, so
|
|
145
|
+
// this display reorder does not change OpenCodex's guidance candidate calculation.
|
|
146
|
+
const pickerOrder = normalizeModelPickerOrder(modelPickerOrder);
|
|
147
|
+
const pickerOrderRank = new Map(pickerOrder.map((slug, i) => [slug, i] as const));
|
|
148
|
+
const pickerOrderActive = pickerOrder.length > 0;
|
|
149
|
+
// The display band reuses the existing high priority tier (>= PICKER_ORDER_PRIORITY_BASE, the
|
|
150
|
+
// same 1_000+ neighborhood account rows occupy), keeping listed rows visually after the featured
|
|
151
|
+
// band. OpenCodex guidance membership does not depend on this — see SPAWN_PRIORITY_FIELD.
|
|
152
|
+
/**
|
|
153
|
+
* Priority for a non-featured routed row that is explicitly LISTED in modelPickerOrder. Listed
|
|
154
|
+
* slugs sort in declared order within the high picker-order display tier
|
|
155
|
+
* (>= PICKER_ORDER_PRIORITY_BASE). This sets the Codex-visible `priority` only; the caller records
|
|
156
|
+
* the row's natural priority in SPAWN_PRIORITY_FIELD for OpenCodex's unchanged guidance window.
|
|
157
|
+
* Returns undefined when the feature is off or the row is not listed, so those rows
|
|
158
|
+
* keep their original assignment (default 5 / account 1_000+) untouched.
|
|
159
|
+
*
|
|
160
|
+
* Scope: only the generic routed `<provider>/<model>` rows call this (see the goModels loop
|
|
161
|
+
* below). Native passthrough rows and account-qualified native rows keep their own priority
|
|
162
|
+
* logic and are intentionally not reordered in this legacy builder pass. The final merge can
|
|
163
|
+
* apply complete ordering when the configured list includes a bare id.
|
|
164
|
+
*/
|
|
165
|
+
const pickerOrderPriority = (slug: string, altSlug?: string): number | undefined => {
|
|
166
|
+
if (!pickerOrderActive) return undefined;
|
|
167
|
+
const hit = pickerOrderRank.get(slug) ?? (altSlug !== undefined ? pickerOrderRank.get(altSlug) : undefined);
|
|
168
|
+
if (hit === undefined) return undefined;
|
|
169
|
+
return PICKER_ORDER_PRIORITY_BASE + hit * priorityStride;
|
|
170
|
+
};
|
|
171
|
+
const out: RawEntry[] = [];
|
|
172
|
+
const nativeEntries: RawEntry[] = [];
|
|
173
|
+
const collisionSkipped = resolveSlugAliasCollisions([...goModels]);
|
|
174
|
+
const emittedNativeAliases = new Set<CatalogModel>();
|
|
175
|
+
const emittedNativeAliasSlugs = new Set<string>();
|
|
176
|
+
const nativeAliasesBySlug = new Map<string, CatalogModel>();
|
|
177
|
+
for (const model of goModels) {
|
|
178
|
+
if (model.provider !== COMBO_NAMESPACE
|
|
179
|
+
|| model.nativeAlias !== true
|
|
180
|
+
|| typeof model.alias !== "string"
|
|
181
|
+
|| model.alias.includes("/")) continue;
|
|
182
|
+
if (nativeAliasesBySlug.has(model.alias)) {
|
|
183
|
+
collisionSkipped.add(model);
|
|
184
|
+
if (!slugAliasCollisionWarnings.has(model.alias)) {
|
|
185
|
+
slugAliasCollisionWarnings.add(model.alias);
|
|
186
|
+
console.warn(
|
|
187
|
+
`[opencodex] native combo alias collision on "${model.alias}": keeping the first configured combo and omitting later duplicates from the catalog.`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
nativeAliasesBySlug.set(model.alias, model);
|
|
193
|
+
}
|
|
194
|
+
const comboPublicSlugs = new Set(goModels
|
|
195
|
+
.filter(model => model.provider === COMBO_NAMESPACE)
|
|
196
|
+
.map(catalogModelSlug));
|
|
197
|
+
for (const slug of gptSlugs) {
|
|
198
|
+
const native = deriveEntry(template, slug, "OpenAI native model (Codex OAuth passthrough).", 9, undefined, new Set(), openaiContextCap);
|
|
199
|
+
if (rank.has(slug)) native.priority = rank.get(slug)!;
|
|
200
|
+
nativeEntries.push(native);
|
|
201
|
+
const nativeAlias = nativeAliasesBySlug.get(slug);
|
|
202
|
+
if (!nativeAlias || collisionSkipped.has(nativeAlias)) {
|
|
203
|
+
if (!suppressedBareNativeSlugs.has(slug)) out.push(native);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
const routed = deriveEntry(
|
|
207
|
+
template,
|
|
208
|
+
slug,
|
|
209
|
+
`Routed via opencodex → ${nativeAlias.provider} (${nativeAlias.owned_by ?? nativeAlias.provider}).`,
|
|
210
|
+
5,
|
|
211
|
+
nativeAlias,
|
|
212
|
+
exactComboSlugs,
|
|
213
|
+
);
|
|
214
|
+
routed.opencodex_catalog_kind = CODEX_NATIVE_ALIAS_CATALOG_KIND;
|
|
215
|
+
const rankHit = rank.get(slug) ?? rank.get(`${nativeAlias.provider}/${nativeAlias.id}`);
|
|
216
|
+
if (rankHit !== undefined) routed.priority = rankHit * priorityStride;
|
|
217
|
+
else if (accountSelectors.length > 0) routed.priority = 1_000 + (typeof routed.priority === "number" ? routed.priority : 5);
|
|
218
|
+
out.push(routed);
|
|
219
|
+
emittedNativeAliases.add(nativeAlias);
|
|
220
|
+
emittedNativeAliasSlugs.add(slug);
|
|
221
|
+
}
|
|
222
|
+
const nativeEntriesBySlug = new Map(nativeEntries.map(entry => [String(entry.slug), entry] as const));
|
|
223
|
+
for (const [selectorIndex, selector] of accountSelectors.entries()) {
|
|
224
|
+
const selectorNativeSlugs = accountNativeSlugsBySelector?.get(selector)
|
|
225
|
+
?? accountNativeSlugs
|
|
226
|
+
?? gptSlugs;
|
|
227
|
+
const accountNativeEntries = selectorNativeSlugs.filter(slug => slug !== NATIVE_RESERVE_MODEL).map(slug => (
|
|
228
|
+
nativeEntriesBySlug.get(slug)
|
|
229
|
+
?? deriveEntry(template, slug, "OpenAI native model (Codex OAuth passthrough).", 9, undefined, new Set(), openaiContextCap)
|
|
230
|
+
));
|
|
231
|
+
if (reserve?.mainSelectors.includes(selector)) accountNativeEntries.push(reserve.source);
|
|
232
|
+
for (const [nativeIndex, native] of accountNativeEntries.entries()) {
|
|
233
|
+
const nativeSlug = String(native.slug);
|
|
234
|
+
if (disabledNativeAccountSlugs.has(nativeSlug)) continue;
|
|
235
|
+
const e = JSON.parse(JSON.stringify(native)) as RawEntry;
|
|
236
|
+
const catalogSlug = `${selector}/${nativeSlug}`;
|
|
237
|
+
if (nativeSlug === NATIVE_RESERVE_MODEL && disabledNativeAccountSlugs.has(catalogSlug)) continue;
|
|
238
|
+
e.slug = catalogSlug;
|
|
239
|
+
e.display_name = accountBoundNativeDisplayName(selector, native);
|
|
240
|
+
// Codex ignores this OpenCodex extension; preserve the native comp_hash unchanged.
|
|
241
|
+
e.opencodex_catalog_kind = CODEX_ACCOUNT_BOUND_CATALOG_KIND;
|
|
242
|
+
const exactRank = rank.get(catalogSlug);
|
|
243
|
+
// A bare featured id belongs to the compatibility combo once shadowed. Exact
|
|
244
|
+
// account-qualified picks still rank normally, but the account clone must not
|
|
245
|
+
// inherit the bare alias rank and consume another top spawn_agent slot.
|
|
246
|
+
const inheritedRank = emittedNativeAliasSlugs.has(nativeSlug) ? undefined : rank.get(nativeSlug);
|
|
247
|
+
const featuredRank = exactRank ?? inheritedRank;
|
|
248
|
+
e.priority = featuredRank !== undefined
|
|
249
|
+
? featuredRank * priorityStride + selectorIndex
|
|
250
|
+
: ((featured?.length ?? 0) + nativeIndex) * accountSelectors.length + selectorIndex;
|
|
251
|
+
e.visibility = "list";
|
|
252
|
+
out.push(e);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
for (const m of goModels) {
|
|
256
|
+
if (collisionSkipped.has(m) || emittedNativeAliases.has(m)) continue;
|
|
257
|
+
const slug = catalogModelSlug(m);
|
|
258
|
+
if (m.provider !== COMBO_NAMESPACE && comboPublicSlugs.has(slug)) {
|
|
259
|
+
warnComboMasqueradeCollisionOnce(slug);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
// Provider rows use the one-slash slug codec; combo aliases intentionally override that
|
|
263
|
+
// public slug and may be bare.
|
|
264
|
+
const e = deriveEntry(
|
|
265
|
+
template,
|
|
266
|
+
slug,
|
|
267
|
+
`Routed via opencodex → ${m.provider} (${m.owned_by ?? m.provider}).`,
|
|
268
|
+
5,
|
|
269
|
+
m,
|
|
270
|
+
exactComboSlugs,
|
|
271
|
+
);
|
|
272
|
+
if (m.provider === COMBO_NAMESPACE && m.nativeAlias === true && !slug.includes("/")) {
|
|
273
|
+
e.opencodex_catalog_kind = CODEX_NATIVE_ALIAS_CATALOG_KIND;
|
|
274
|
+
}
|
|
275
|
+
// Featured picks may be stored raw (legacy) or encoded — honor both.
|
|
276
|
+
const rankHit = rank.get(slug) ?? rank.get(`${m.provider}/${m.id}`);
|
|
277
|
+
// Natural priority: what the row would get WITHOUT modelPickerOrder. This is the value the
|
|
278
|
+
// spawn_agent candidate window is derived from (see effectiveSubagentRoster), so it must never
|
|
279
|
+
// move when modelPickerOrder reorders the picker.
|
|
280
|
+
if (rankHit !== undefined) e.priority = rankHit * priorityStride;
|
|
281
|
+
else if (accountSelectors.length > 0) {
|
|
282
|
+
// Keep the generated account rows together in Codex's priority-sorted flat picker.
|
|
283
|
+
e.priority = 1_000 + (typeof e.priority === "number" ? e.priority : 5);
|
|
284
|
+
}
|
|
285
|
+
// The legacy routed-only builder pass keeps featured ranks and records natural priority
|
|
286
|
+
// before changing non-featured display priority. The final complete-order pass may move
|
|
287
|
+
// featured display rows too; OpenCodex guidance continues to use their natural ranks.
|
|
288
|
+
if (rankHit === undefined) {
|
|
289
|
+
const pickerPriority = pickerOrderPriority(slug, `${m.provider}/${m.id}`);
|
|
290
|
+
if (pickerPriority !== undefined) {
|
|
291
|
+
e[SPAWN_PRIORITY_FIELD] = typeof e.priority === "number" ? e.priority : 5;
|
|
292
|
+
e.priority = pickerPriority;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
out.push(e);
|
|
296
|
+
}
|
|
297
|
+
// Central capability override (phase 120.4): the advertised flag must match the implemented WS
|
|
298
|
+
// endpoint. Overrides both the routed strip (normalizeRoutedCatalogEntry) and any native template
|
|
299
|
+
// leak (deriveEntry clones the template as-is for native slugs).
|
|
300
|
+
for (const entry of out) {
|
|
301
|
+
if (wsEnabled) entry.supports_websockets = true;
|
|
302
|
+
else {
|
|
303
|
+
delete entry.supports_websockets;
|
|
304
|
+
// Snapshot-backed native entries carry prefer_websockets: never advertise a preference
|
|
305
|
+
// for an endpoint ocx has disabled.
|
|
306
|
+
delete entry.prefer_websockets;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return applyMultiAgentMode(out, multiAgentMode, multiAgentV2Enabled, {
|
|
310
|
+
keepNativeChatGptOnV1,
|
|
311
|
+
preserveDefaultMultiAgentVersion: isReserveCatalogProjection,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function resetCatalogRuntimeStateForTests(): void {
|
|
316
|
+
resetBundledCatalogCacheForTests();
|
|
317
|
+
lastDropWarnSignature.clear();
|
|
318
|
+
openAiApiCollisionWarnings.clear();
|
|
319
|
+
comboCatalogWarningSignatures.clear();
|
|
320
|
+
slugAliasCollisionWarnings.clear();
|
|
321
|
+
comboMasqueradeCollisionWarnings.clear();
|
|
322
|
+
comboUnrestorableShadowWarnings.clear();
|
|
323
|
+
accountSelectorShadowCollisionWarnings.clear();
|
|
324
|
+
clearLastComboCatalogOmissions();
|
|
325
|
+
clearModelCache(undefined, "eviction");
|
|
326
|
+
clearGatherRoutedModelsInflight();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function orderForSubagents(goModels: CatalogModel[], featured?: string[]): CatalogModel[] {
|
|
330
|
+
if (!featured || featured.length === 0) return goModels;
|
|
331
|
+
const rank = new Map(featured.map((id, i) => [id, i]));
|
|
332
|
+
// Featured picks may be stored raw (legacy) or encoded — match both forms.
|
|
333
|
+
const rankOf = (m: CatalogModel) =>
|
|
334
|
+
(m.alias ? rank.get(m.alias) : undefined)
|
|
335
|
+
?? rank.get(`${m.provider}/${m.id}`)
|
|
336
|
+
?? rank.get(routedSlug(m.provider, m.id))
|
|
337
|
+
?? Number.MAX_SAFE_INTEGER;
|
|
338
|
+
return [...goModels].sort((a, b) => {
|
|
339
|
+
return rankOf(a) - rankOf(b);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Routed discovery projection; native groups and alias ownership belong to the caller. */
|
|
344
|
+
export function orderForModelPicker(
|
|
345
|
+
models: readonly CatalogModel[],
|
|
346
|
+
order: readonly string[] = [],
|
|
347
|
+
featured: readonly string[] = [],
|
|
348
|
+
): CatalogModel[] {
|
|
349
|
+
const pickerOrder = normalizeModelPickerOrder(order);
|
|
350
|
+
if (pickerOrder.length === 0) return [...models];
|
|
351
|
+
const pickerRank = modelPickerRank(pickerOrder);
|
|
352
|
+
const featuredRank = modelPickerRank(featured);
|
|
353
|
+
const complete = pickerOrder.some(slug => !slug.includes("/"));
|
|
354
|
+
const rank = (model: CatalogModel): number => {
|
|
355
|
+
const slug = catalogModelSlug(model);
|
|
356
|
+
const featuredIndex = featuredRank(slug) ?? featuredRank(`${model.provider}/${model.id}`);
|
|
357
|
+
const natural = featuredIndex ?? 5;
|
|
358
|
+
const index = pickerRank(slug) ?? pickerRank(`${model.provider}/${model.id}`);
|
|
359
|
+
if (complete) return index ?? pickerOrder.length + natural;
|
|
360
|
+
// Preserve the legacy featured/alias bands, including unlisted rows before listed rows.
|
|
361
|
+
if (featuredIndex !== undefined || model.nativeAlias === true) return natural;
|
|
362
|
+
return index === undefined ? natural : PICKER_ORDER_PRIORITY_BASE + index;
|
|
363
|
+
};
|
|
364
|
+
return [...models].sort((a, b) => rank(a) - rank(b));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* True when an existing catalog row was authored by OpenCodex routing (#855).
|
|
369
|
+
* Every generated routed row — current full-slug form, the June–July 2026
|
|
370
|
+
* provider-name form, and legacy combo aliases — carries the stable
|
|
371
|
+
* description prefix `Routed via opencodex → `; foreign rows from Cursor or
|
|
372
|
+
* user tooling do not. `owned_by` cannot serve as the signal (upstream
|
|
373
|
+
* ownership), and `comp_hash` defaults to "opencodex" for every normalized
|
|
374
|
+
* row.
|
|
375
|
+
*/
|
|
376
|
+
function isOcxAuthoredRoutedEntry(entry: RawEntry): boolean {
|
|
377
|
+
if (isNativeAliasCatalogEntry(entry)) return true;
|
|
378
|
+
const desc = typeof entry.description === "string" ? entry.description : "";
|
|
379
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
380
|
+
return slug.includes("/") && desc.startsWith("Routed via opencodex → ");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function recoverableNativeSlug(entry: RawEntry): string | null {
|
|
384
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
385
|
+
return SUPPORTED_NATIVE_OPENAI_SLUGS.has(slug)
|
|
386
|
+
&& !isNativeAliasCatalogEntry(entry)
|
|
387
|
+
&& entry.owned_by !== COMBO_NAMESPACE
|
|
388
|
+
? slug
|
|
389
|
+
: null;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Undo our display overlay before native metadata normalization and template reuse. */
|
|
393
|
+
function restoreNativeDisplayName(entry: RawEntry): RawEntry {
|
|
394
|
+
const saved = entry.opencodex_native_display_name;
|
|
395
|
+
delete entry.opencodex_native_display_name;
|
|
396
|
+
if (saved && typeof saved === "object" && !Array.isArray(saved)) {
|
|
397
|
+
const label = saved as Record<string, unknown>;
|
|
398
|
+
if (recoverableNativeSlug(entry) === label.slug
|
|
399
|
+
&& typeof label.original === "string" && entry.display_name === label.applied) {
|
|
400
|
+
entry.display_name = label.original;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return entry;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** Append missing supported native rows from trusted catalog sources only. */
|
|
407
|
+
export function mergeCatalogModelsWithNativeRecovery(
|
|
408
|
+
primaryCatalogModels: readonly RawEntry[],
|
|
409
|
+
nativeRecoverySources: readonly (readonly RawEntry[])[],
|
|
410
|
+
): RawEntry[] {
|
|
411
|
+
const merged = [...primaryCatalogModels];
|
|
412
|
+
const recoveredNativeSlugs = new Set(primaryCatalogModels.flatMap(entry => {
|
|
413
|
+
const slug = recoverableNativeSlug(entry);
|
|
414
|
+
return slug === null ? [] : [slug];
|
|
415
|
+
}));
|
|
416
|
+
for (const source of nativeRecoverySources) {
|
|
417
|
+
for (const entry of source) {
|
|
418
|
+
const slug = recoverableNativeSlug(entry);
|
|
419
|
+
if (slug === null || recoveredNativeSlugs.has(slug)) continue;
|
|
420
|
+
merged.push(structuredClone(entry) as RawEntry);
|
|
421
|
+
recoveredNativeSlugs.add(slug);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return merged;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export interface ObservedCatalogMergePolicy {
|
|
428
|
+
/** Required observed/fixed set; the core merge never consults ambient catalog state. */
|
|
429
|
+
readonly nativeBackfillSlugs: readonly string[];
|
|
430
|
+
/** Whether unsupported OpenAI-family bare rows survive the merge. */
|
|
431
|
+
readonly unsupportedNativeEntries: "preserve" | "drop";
|
|
432
|
+
/** Whether merge-policy collision/preservation warnings belong to this caller's flow. */
|
|
433
|
+
readonly warningPolicy: "emit" | "suppress";
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** Content policy shared by every writer of the canonical Codex model catalog. */
|
|
437
|
+
export const CANONICAL_NATIVE_CATALOG_CONTENT_POLICY: Readonly<
|
|
438
|
+
Pick<ObservedCatalogMergePolicy, "nativeBackfillSlugs" | "unsupportedNativeEntries">
|
|
439
|
+
> = Object.freeze({
|
|
440
|
+
nativeBackfillSlugs: Object.freeze([...NATIVE_OPENAI_MODELS]),
|
|
441
|
+
unsupportedNativeEntries: "drop",
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
function normalizeModelPickerOrder(order: unknown): string[] {
|
|
445
|
+
return Array.isArray(order)
|
|
446
|
+
? order.filter((id): id is string => typeof id === "string" && id.trim().length > 0)
|
|
447
|
+
: [];
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** Preserve exact-id precedence while accepting the existing raw/encoded slug spellings. */
|
|
451
|
+
function modelPickerRank(order: readonly string[]): (slug: string) => number | undefined {
|
|
452
|
+
const exact = new Map(order.map((slug, index) => [slug, index]));
|
|
453
|
+
const equivalent = new Map(order.map((slug, index) => [slugEquivalenceKey(slug), index]));
|
|
454
|
+
return slug => exact.get(slug) ?? equivalent.get(slugEquivalenceKey(slug));
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/** Complete display ordering retains natural ranks for OpenCodex's separate guidance projection. */
|
|
458
|
+
export function applyFullModelPickerOrder(entries: RawEntry[], order: readonly string[]): void {
|
|
459
|
+
const pickerOrder = normalizeModelPickerOrder(order);
|
|
460
|
+
if (!pickerOrder.some(slug => !slug.includes("/"))) return;
|
|
461
|
+
const rankOf = modelPickerRank(pickerOrder);
|
|
462
|
+
for (const entry of entries) {
|
|
463
|
+
const natural = entry[SPAWN_PRIORITY_FIELD] ?? entry.priority ?? 9;
|
|
464
|
+
entry[SPAWN_PRIORITY_FIELD] = natural;
|
|
465
|
+
entry.priority = rankOf(String(entry.slug)) ?? pickerOrder.length + Number(natural);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export interface ObservedCatalogMergeInput {
|
|
470
|
+
readonly catalogModels: readonly RawEntry[];
|
|
471
|
+
readonly baselineCatalogModels: readonly RawEntry[];
|
|
472
|
+
readonly routedEntries: readonly RawEntry[];
|
|
473
|
+
readonly baseline: ReadonlyMap<string, number>;
|
|
474
|
+
readonly featured: readonly string[];
|
|
475
|
+
readonly modelPickerOrder?: readonly string[];
|
|
476
|
+
readonly accountSelectors?: readonly string[];
|
|
477
|
+
readonly wsEnabled: boolean;
|
|
478
|
+
readonly template: RawEntry | null;
|
|
479
|
+
readonly disabledModels: ReadonlySet<string>;
|
|
480
|
+
readonly selectedModelsByProvider: ReadonlyMap<string, ReadonlySet<string>>;
|
|
481
|
+
readonly gatheredProviderNames: ReadonlySet<string>;
|
|
482
|
+
readonly pendingProviderNames?: ReadonlySet<string>;
|
|
483
|
+
readonly degradedProviderNames: ReadonlySet<string>;
|
|
484
|
+
readonly legacyCustomModelSlugs: ReadonlySet<string>;
|
|
485
|
+
readonly multiAgentMode: MultiAgentMode;
|
|
486
|
+
readonly multiAgentV2Enabled: boolean;
|
|
487
|
+
readonly keepNativeChatGptOnV1?: boolean;
|
|
488
|
+
readonly exactComboSlugs: ReadonlySet<string>;
|
|
489
|
+
readonly hasPhysicalComboProvider: boolean;
|
|
490
|
+
readonly includeNativeOpenAi: boolean;
|
|
491
|
+
readonly accountBoundEntries: readonly RawEntry[];
|
|
492
|
+
readonly suppressedBareNativeSlugs?: ReadonlySet<string>;
|
|
493
|
+
readonly policy: ObservedCatalogMergePolicy;
|
|
494
|
+
readonly openaiContextCap?: NativeContextLimitsInput;
|
|
495
|
+
/** Exact display-only labels for bare native OpenAI models. */
|
|
496
|
+
readonly nativeDisplayNames?: Readonly<Record<string, string>>;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Deterministically merge one fully observed catalog state.
|
|
501
|
+
*
|
|
502
|
+
* Every non-catalog input is explicit so evidence-bound convergence cannot
|
|
503
|
+
* accidentally fall back to process-ambient catalog discovery or merge-policy warnings.
|
|
504
|
+
*/
|
|
505
|
+
export function mergeCatalogEntriesFromObservedState({
|
|
506
|
+
catalogModels,
|
|
507
|
+
baselineCatalogModels,
|
|
508
|
+
routedEntries,
|
|
509
|
+
baseline,
|
|
510
|
+
featured,
|
|
511
|
+
modelPickerOrder = [],
|
|
512
|
+
accountSelectors = [],
|
|
513
|
+
wsEnabled,
|
|
514
|
+
template,
|
|
515
|
+
disabledModels,
|
|
516
|
+
selectedModelsByProvider,
|
|
517
|
+
gatheredProviderNames,
|
|
518
|
+
pendingProviderNames = new Set(),
|
|
519
|
+
degradedProviderNames,
|
|
520
|
+
legacyCustomModelSlugs,
|
|
521
|
+
multiAgentMode,
|
|
522
|
+
multiAgentV2Enabled,
|
|
523
|
+
keepNativeChatGptOnV1,
|
|
524
|
+
exactComboSlugs,
|
|
525
|
+
hasPhysicalComboProvider,
|
|
526
|
+
includeNativeOpenAi,
|
|
527
|
+
accountBoundEntries,
|
|
528
|
+
suppressedBareNativeSlugs = new Set(),
|
|
529
|
+
policy,
|
|
530
|
+
openaiContextCap,
|
|
531
|
+
nativeDisplayNames,
|
|
532
|
+
}: ObservedCatalogMergeInput): RawEntry[] {
|
|
533
|
+
// Raw catalog rows contain nested arrays/objects that normalization mutates. Detach every row at
|
|
534
|
+
// the observed-core boundary so callers can safely retain evidence objects or repeat the merge.
|
|
535
|
+
const detachedCatalogModels = catalogModels
|
|
536
|
+
.map(entry => restoreNativeDisplayName(structuredClone(entry) as RawEntry));
|
|
537
|
+
const detachedBaselineCatalogModels = baselineCatalogModels
|
|
538
|
+
.map(entry => restoreNativeDisplayName(structuredClone(entry) as RawEntry));
|
|
539
|
+
const detachedRoutedEntries = routedEntries.map(entry => structuredClone(entry) as RawEntry);
|
|
540
|
+
// Track this invocation's generated custom rows, not ownership markers read from disk.
|
|
541
|
+
// Their builder already finalized exact native ladders and ordinary routed mock tiers.
|
|
542
|
+
const freshCustomEntries = new Set(detachedRoutedEntries.filter(entry =>
|
|
543
|
+
entry.opencodex_catalog_kind === CODEX_CUSTOM_MODEL_CATALOG_KIND));
|
|
544
|
+
const detachedAccountBoundEntries = accountBoundEntries
|
|
545
|
+
.map(entry => structuredClone(entry) as RawEntry);
|
|
546
|
+
const disabledModelKeys = new Set([...disabledModels].map(slugEquivalenceKey));
|
|
547
|
+
const legacyCustomModelKeys = new Set(
|
|
548
|
+
[...legacyCustomModelSlugs].map(slugEquivalenceKey),
|
|
549
|
+
);
|
|
550
|
+
const selectedModelKeysByProvider = new Map([...selectedModelsByProvider].map(([provider, models]) => (
|
|
551
|
+
[provider, new Set([...models].map(model => slugEquivalenceKey(routedSlug(provider, model))))] as const
|
|
552
|
+
)));
|
|
553
|
+
const freshAccountKeys = new Set(detachedAccountBoundEntries.flatMap(entry => (
|
|
554
|
+
typeof entry.slug === "string" ? [slugEquivalenceKey(entry.slug)] : []
|
|
555
|
+
)));
|
|
556
|
+
const wouldSurviveUnreplaced = (entry: RawEntry): boolean => {
|
|
557
|
+
if (entry.owned_by === COMBO_NAMESPACE
|
|
558
|
+
|| trustedAccountBoundNativeCatalogSlug(entry) !== undefined
|
|
559
|
+
|| entry.opencodex_catalog_kind === CODEX_CUSTOM_MODEL_CATALOG_KIND
|
|
560
|
+
|| isOcxAuthoredRoutedEntry(entry)
|
|
561
|
+
|| typeof entry.slug !== "string") return false;
|
|
562
|
+
const slug = entry.slug;
|
|
563
|
+
if (!slug.includes("/")) {
|
|
564
|
+
if (!includeNativeOpenAi || policy.nativeBackfillSlugs.includes(slug)) return false;
|
|
565
|
+
return policy.unsupportedNativeEntries === "preserve" || !isUnsupportedOpenAiNativeSlug(slug);
|
|
566
|
+
}
|
|
567
|
+
if (isRoutedModelCompatibilityExcluded(slug)) return false;
|
|
568
|
+
if (!hasPhysicalComboProvider && slug.startsWith(`${COMBO_NAMESPACE}/`)) return false;
|
|
569
|
+
const key = slugEquivalenceKey(slug);
|
|
570
|
+
if (freshAccountKeys.has(key)) return false;
|
|
571
|
+
if (disabledModelKeys.has(key)) return false;
|
|
572
|
+
const slash = slug.indexOf("/");
|
|
573
|
+
const provider = slug.slice(0, slash);
|
|
574
|
+
if (pendingProviderNames.has(provider)) return false;
|
|
575
|
+
const selected = selectedModelKeysByProvider.get(provider);
|
|
576
|
+
if (selected !== undefined && !selected.has(key)) return false;
|
|
577
|
+
return !gatheredProviderNames.has(provider) || degradedProviderNames.has(provider);
|
|
578
|
+
};
|
|
579
|
+
const validRoutedEntries = detachedRoutedEntries.filter(entry => {
|
|
580
|
+
return !isExactComboCatalogEntry(entry, exactComboSlugs)
|
|
581
|
+
|| (Array.isArray(entry.input_modalities) && entry.input_modalities.length > 0);
|
|
582
|
+
});
|
|
583
|
+
const restorableCatalogKeys = new Set(detachedBaselineCatalogModels.flatMap(entry => (
|
|
584
|
+
wouldSurviveUnreplaced(entry) && typeof entry.slug === "string"
|
|
585
|
+
? [slugEquivalenceKey(entry.slug)]
|
|
586
|
+
: []
|
|
587
|
+
)));
|
|
588
|
+
const unrestorableCatalogKeys = new Set(detachedCatalogModels.flatMap(entry => {
|
|
589
|
+
if (!wouldSurviveUnreplaced(entry) || typeof entry.slug !== "string") return [];
|
|
590
|
+
const key = slugEquivalenceKey(entry.slug);
|
|
591
|
+
return restorableCatalogKeys.has(key) ? [] : [key];
|
|
592
|
+
}));
|
|
593
|
+
const admittedRoutedEntries = validRoutedEntries.filter(entry => {
|
|
594
|
+
if (!isExactComboCatalogEntry(entry, exactComboSlugs)) return true;
|
|
595
|
+
const slug = entry.slug as string;
|
|
596
|
+
const key = slugEquivalenceKey(slug);
|
|
597
|
+
if (!unrestorableCatalogKeys.has(key)) return true;
|
|
598
|
+
if (policy.warningPolicy === "emit") warnComboUnrestorableShadowOnce(slug);
|
|
599
|
+
return false;
|
|
600
|
+
});
|
|
601
|
+
// A fresh non-custom row authoritatively resolves a historically ambiguous slug as a normal
|
|
602
|
+
// provider model. Persist that classification so the durable deletion evidence cannot remove
|
|
603
|
+
// the legitimate row during a later degraded refresh.
|
|
604
|
+
for (const entry of admittedRoutedEntries) {
|
|
605
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
606
|
+
if (!slug
|
|
607
|
+
|| entry.opencodex_catalog_kind !== undefined
|
|
608
|
+
|| entry.owned_by === COMBO_NAMESPACE
|
|
609
|
+
|| !isOcxAuthoredRoutedEntry(entry)
|
|
610
|
+
|| !legacyCustomModelKeys.has(slugEquivalenceKey(slug))) continue;
|
|
611
|
+
entry.opencodex_catalog_kind = CODEX_PROVIDER_MODEL_CATALOG_KIND;
|
|
612
|
+
}
|
|
613
|
+
const freshExactComboEntries = new Set(admittedRoutedEntries.filter(entry => (
|
|
614
|
+
isExactComboCatalogEntry(entry, exactComboSlugs)
|
|
615
|
+
&& typeof entry.description === "string"
|
|
616
|
+
&& entry.description.startsWith(`Routed via opencodex → ${COMBO_NAMESPACE} (`)
|
|
617
|
+
)));
|
|
618
|
+
const rank = new Map(featured.map((slug, i) => [slug, i] as const));
|
|
619
|
+
const freshEquivalentKeys = new Set(admittedRoutedEntries.flatMap(entry => (
|
|
620
|
+
typeof entry.slug === "string" ? [slugEquivalenceKey(entry.slug)] : []
|
|
621
|
+
)));
|
|
622
|
+
const freshEquivalent = (slug: string): boolean => (
|
|
623
|
+
freshEquivalentKeys.has(slugEquivalenceKey(slug))
|
|
624
|
+
);
|
|
625
|
+
const freshBareComboAliases = new Set(admittedRoutedEntries.flatMap(entry => (
|
|
626
|
+
typeof entry.slug === "string"
|
|
627
|
+
&& !entry.slug.includes("/")
|
|
628
|
+
&& entry.owned_by === COMBO_NAMESPACE
|
|
629
|
+
? [entry.slug]
|
|
630
|
+
: []
|
|
631
|
+
)));
|
|
632
|
+
const staleComboKeys = new Set(detachedCatalogModels.flatMap(entry => (
|
|
633
|
+
typeof entry.slug === "string"
|
|
634
|
+
&& entry.owned_by === COMBO_NAMESPACE
|
|
635
|
+
&& !freshEquivalent(entry.slug)
|
|
636
|
+
? [slugEquivalenceKey(entry.slug)]
|
|
637
|
+
: []
|
|
638
|
+
)));
|
|
639
|
+
const currentNonComboKeys = new Set(detachedCatalogModels.flatMap(entry => (
|
|
640
|
+
entry.owned_by !== COMBO_NAMESPACE && typeof entry.slug === "string"
|
|
641
|
+
? [slugEquivalenceKey(entry.slug)]
|
|
642
|
+
: []
|
|
643
|
+
)));
|
|
644
|
+
const restoredComboShadows = detachedBaselineCatalogModels.filter(entry => {
|
|
645
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
646
|
+
if (!slug || entry.owned_by === COMBO_NAMESPACE) return false;
|
|
647
|
+
const key = slugEquivalenceKey(slug);
|
|
648
|
+
return staleComboKeys.has(key) && !currentNonComboKeys.has(key);
|
|
649
|
+
});
|
|
650
|
+
const catalogModelsForMerge = [...detachedCatalogModels, ...restoredComboShadows];
|
|
651
|
+
const nativePriority = (slug: string, fallback: unknown): number => {
|
|
652
|
+
const base = baseline.get(slug)
|
|
653
|
+
?? (typeof fallback === "number" ? fallback : 9);
|
|
654
|
+
if (rank.has(slug)) return rank.get(slug)!;
|
|
655
|
+
return featured.length > 0 ? Math.max(base, featured.length + 100) : base;
|
|
656
|
+
};
|
|
657
|
+
const nativeSourceEntries = includeNativeOpenAi
|
|
658
|
+
? catalogModelsForMerge
|
|
659
|
+
.filter(m => typeof m.slug === "string"
|
|
660
|
+
&& !(m.slug as string).includes("/")
|
|
661
|
+
&& m.owned_by !== COMBO_NAMESPACE
|
|
662
|
+
&& (policy.unsupportedNativeEntries === "preserve"
|
|
663
|
+
|| policy.nativeBackfillSlugs.includes(m.slug as string)
|
|
664
|
+
|| !isUnsupportedOpenAiNativeSlug(m.slug as string)))
|
|
665
|
+
.map(m => {
|
|
666
|
+
const slug = m.slug as string;
|
|
667
|
+
// Fallback-quality entries (ocx synthesis / codex-rs model_info fallback: display_name
|
|
668
|
+
// stamped with the bare slug) are upgraded to the pinned upstream snapshot entry so a
|
|
669
|
+
// previously synthesized ladder (e.g. luna advertising ultra) self-heals on sync. A
|
|
670
|
+
// genuine catalog entry (real display name) is preserved untouched.
|
|
671
|
+
if (shouldUpgradeToUpstreamEntry(m)) {
|
|
672
|
+
const upstream = upstreamNativeEntry(slug)!;
|
|
673
|
+
const finished = finishUpstreamNativeEntry(upstream, 9, openaiContextCap);
|
|
674
|
+
finished.priority = nativePriority(slug, upstream.priority);
|
|
675
|
+
return finished;
|
|
676
|
+
}
|
|
677
|
+
const preserved = normalizeServiceTiers({ ...m, priority: nativePriority(slug, m[SPAWN_PRIORITY_FIELD] ?? m.priority) });
|
|
678
|
+
// Recompute spawn rank from current featured models, not a prior picker override.
|
|
679
|
+
delete preserved[SPAWN_PRIORITY_FIELD];
|
|
680
|
+
// Older natives kept from disk still need the mock top tiers (max + ultra always
|
|
681
|
+
// for subagent max spawns; wire-clamped to the model's real top rung).
|
|
682
|
+
if (!isGpt56NativeSlug(slug) && slug !== NATIVE_RESERVE_MODEL) ensureUltraReasoningLevel(preserved);
|
|
683
|
+
return preserved;
|
|
684
|
+
})
|
|
685
|
+
: [];
|
|
686
|
+
const native = nativeSourceEntries.filter(entry =>
|
|
687
|
+
typeof entry.slug !== "string"
|
|
688
|
+
|| (!freshBareComboAliases.has(entry.slug) && !suppressedBareNativeSlugs.has(entry.slug))
|
|
689
|
+
);
|
|
690
|
+
|
|
691
|
+
// Backfill any native OpenAI slug that the on-disk catalog is missing (e.g. gpt-5.5), so a
|
|
692
|
+
// routed provider exposing the same id can never delete the native OpenAI/Codex base row.
|
|
693
|
+
// Skip when no enabled canonical openai provider exists (#636) — bare gpt-* would 404.
|
|
694
|
+
const nativeSlugs = new Set(native.flatMap(m => typeof m.slug === "string" ? [m.slug] : []));
|
|
695
|
+
if (includeNativeOpenAi) {
|
|
696
|
+
for (const slug of policy.nativeBackfillSlugs) {
|
|
697
|
+
if (nativeSlugs.has(slug) || freshBareComboAliases.has(slug) || suppressedBareNativeSlugs.has(slug)) continue;
|
|
698
|
+
nativeSlugs.add(slug);
|
|
699
|
+
const entry = deriveEntry(
|
|
700
|
+
template ? JSON.parse(JSON.stringify(template)) : null,
|
|
701
|
+
slug,
|
|
702
|
+
"OpenAI native model (Codex OAuth passthrough).",
|
|
703
|
+
nativePriority(slug, upstreamNativeEntry(slug)?.priority),
|
|
704
|
+
undefined,
|
|
705
|
+
new Set(),
|
|
706
|
+
openaiContextCap,
|
|
707
|
+
);
|
|
708
|
+
entry.priority = nativePriority(slug, upstreamNativeEntry(slug)?.priority);
|
|
709
|
+
native.push(entry);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const nativeSourceBySlug = new Map([...nativeSourceEntries, ...native].flatMap(entry =>
|
|
714
|
+
typeof entry.slug === "string" ? [[entry.slug, entry] as const] : []
|
|
715
|
+
));
|
|
716
|
+
const alignedAccountBoundEntries = detachedAccountBoundEntries.map(entry => {
|
|
717
|
+
// The explicit Reserve source is already chosen (actual row or documented Luna adaptation).
|
|
718
|
+
// A generic native merge must not replace its provenance or capability ladder.
|
|
719
|
+
if (isReserveCatalogProjection(entry)) return entry;
|
|
720
|
+
const nativeSlug = trustedAccountBoundNativeCatalogSlug(entry);
|
|
721
|
+
const source = nativeSlug === undefined ? undefined : nativeSourceBySlug.get(nativeSlug);
|
|
722
|
+
if (!source) return entry;
|
|
723
|
+
const aligned = JSON.parse(JSON.stringify(source)) as RawEntry;
|
|
724
|
+
aligned.slug = entry.slug;
|
|
725
|
+
aligned.display_name = entry.display_name;
|
|
726
|
+
aligned.priority = entry.priority;
|
|
727
|
+
aligned.visibility = "list";
|
|
728
|
+
aligned.opencodex_catalog_kind = CODEX_ACCOUNT_BOUND_CATALOG_KIND;
|
|
729
|
+
return aligned;
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
const freshSlugs = new Set(
|
|
733
|
+
admittedRoutedEntries.flatMap(entry => typeof entry.slug === "string" ? [entry.slug] : []),
|
|
734
|
+
);
|
|
735
|
+
const existingRoutedEntries = catalogModelsForMerge.filter(m =>
|
|
736
|
+
typeof m.slug === "string"
|
|
737
|
+
&& (m.slug.includes("/") || isNativeAliasCatalogEntry(m))
|
|
738
|
+
&& trustedAccountBoundNativeCatalogSlug(m) === undefined
|
|
739
|
+
);
|
|
740
|
+
const preservedRoutedEntries = existingRoutedEntries.filter(entry => {
|
|
741
|
+
const slug = entry.slug as string;
|
|
742
|
+
if (freshEquivalent(slug)) return false;
|
|
743
|
+
if (isNativeAliasCatalogEntry(entry)) return exactComboSlugs.has(slug);
|
|
744
|
+
// Current custom rows are always regenerated from config, even while provider discovery is
|
|
745
|
+
// degraded. A marked row absent from the fresh projection is therefore an intentional delete.
|
|
746
|
+
if (entry.opencodex_catalog_kind === CODEX_CUSTOM_MODEL_CATALOG_KIND) return false;
|
|
747
|
+
// Before custom rows had a marker, a config deletion could otherwise be mistaken for a
|
|
748
|
+
// provider outage. Only explicit save-boundary evidence may classify an unmarked OpenCodex
|
|
749
|
+
// row; foreign and future-marked rows fail closed and remain preserved.
|
|
750
|
+
if (entry.opencodex_catalog_kind === undefined
|
|
751
|
+
&& entry.owned_by !== COMBO_NAMESPACE
|
|
752
|
+
&& isOcxAuthoredRoutedEntry(entry)
|
|
753
|
+
&& legacyCustomModelKeys.has(slugEquivalenceKey(slug))) return false;
|
|
754
|
+
const provider = slug.slice(0, slug.indexOf("/"));
|
|
755
|
+
if (gatheredProviderNames.has(provider)) {
|
|
756
|
+
// A provider-local degraded observation preserves only that namespace. Authoritative empty
|
|
757
|
+
// catalogs and successful removals still delete stale rows even when another provider fails.
|
|
758
|
+
return degradedProviderNames.has(provider);
|
|
759
|
+
}
|
|
760
|
+
// Deleted/disabled providers cannot retain OpenCodex-authored ghosts. Foreign catalog rows
|
|
761
|
+
// remain outside provider ownership and survive unless a fresh row replaces their exact slug.
|
|
762
|
+
return !isOcxAuthoredRoutedEntry(entry);
|
|
763
|
+
});
|
|
764
|
+
// Retained rows bypass the builder. Recompute managed spawn ranks from current config
|
|
765
|
+
// before either display-order mode; a saved display override is not current roster authority.
|
|
766
|
+
const pickerOrder = normalizeModelPickerOrder(modelPickerOrder);
|
|
767
|
+
const fullPickerOrder = pickerOrder.some(slug => !slug.includes("/"));
|
|
768
|
+
const rankOf = modelPickerRank(pickerOrder);
|
|
769
|
+
const featuredRankOf = modelPickerRank(featured);
|
|
770
|
+
const priorityStride = Math.max(accountSelectors.length, 1);
|
|
771
|
+
for (const entry of preservedRoutedEntries) {
|
|
772
|
+
const natural = entry[SPAWN_PRIORITY_FIELD];
|
|
773
|
+
if (typeof natural === "number") {
|
|
774
|
+
entry.priority = natural;
|
|
775
|
+
delete entry[SPAWN_PRIORITY_FIELD];
|
|
776
|
+
}
|
|
777
|
+
const slug = String(entry.slug);
|
|
778
|
+
if (!isOcxAuthoredRoutedEntry(entry) || isNativeAliasCatalogEntry(entry)) continue;
|
|
779
|
+
const featuredRank = featuredRankOf(slug);
|
|
780
|
+
entry.priority = featuredRank !== undefined
|
|
781
|
+
? featuredRank * priorityStride
|
|
782
|
+
: (accountSelectors.length > 0 ? 1_000 : 0) + 5;
|
|
783
|
+
if (featuredRank !== undefined || fullPickerOrder) continue;
|
|
784
|
+
const pickerIndex = rankOf(slug);
|
|
785
|
+
if (pickerIndex !== undefined) {
|
|
786
|
+
entry[SPAWN_PRIORITY_FIELD] = entry.priority;
|
|
787
|
+
entry.priority = PICKER_ORDER_PRIORITY_BASE + pickerIndex * priorityStride;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
let finalRoutedEntries = [...admittedRoutedEntries, ...preservedRoutedEntries];
|
|
791
|
+
finalRoutedEntries = finalRoutedEntries.filter(entry => {
|
|
792
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
793
|
+
if (!slug.includes("/")) return true;
|
|
794
|
+
if (disabledModelKeys.has(slugEquivalenceKey(slug))) return false;
|
|
795
|
+
// Provider allowlists own provider rows, not a current combo's public alias. Exempt only an
|
|
796
|
+
// identity from this gather's generated combo projection: provider discovery may supply a
|
|
797
|
+
// spoofed `owned_by`, and persisted combo-shaped rows are not fresh authority.
|
|
798
|
+
if (freshExactComboEntries.has(entry)) return true;
|
|
799
|
+
const slash = slug.indexOf("/");
|
|
800
|
+
const provider = slug.slice(0, slash);
|
|
801
|
+
if (pendingProviderNames.has(provider)) return false;
|
|
802
|
+
const selected = selectedModelKeysByProvider.get(provider);
|
|
803
|
+
return selected === undefined || selected.has(slugEquivalenceKey(slug));
|
|
804
|
+
});
|
|
805
|
+
if (!hasPhysicalComboProvider) {
|
|
806
|
+
finalRoutedEntries = finalRoutedEntries.filter(entry => {
|
|
807
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
808
|
+
const comboOwned = slug.startsWith(`${COMBO_NAMESPACE}/`) || entry.owned_by === COMBO_NAMESPACE;
|
|
809
|
+
const retainedNativeAlias = isNativeAliasCatalogEntry(entry) && exactComboSlugs.has(slug);
|
|
810
|
+
return !comboOwned || freshSlugs.has(slug) || retainedNativeAlias;
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
finalRoutedEntries = finalRoutedEntries.filter(entry => {
|
|
814
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
815
|
+
const retainedNativeAlias = isNativeAliasCatalogEntry(entry) && exactComboSlugs.has(slug);
|
|
816
|
+
return retainedNativeAlias
|
|
817
|
+
|| !isExactComboCatalogEntry(entry, exactComboSlugs)
|
|
818
|
+
|| (Array.isArray(entry.input_modalities) && entry.input_modalities.length > 0);
|
|
819
|
+
});
|
|
820
|
+
// Reapply final catalog policy to rows preserved from disk. Those rows bypass
|
|
821
|
+
// gatherRoutedModels, so filtering only the freshly gathered list can resurrect an excluded id.
|
|
822
|
+
finalRoutedEntries = finalRoutedEntries.filter(entry =>
|
|
823
|
+
typeof entry.slug !== "string" || !isRoutedModelCompatibilityExcluded(entry.slug)
|
|
824
|
+
);
|
|
825
|
+
const accountBoundSlugs = new Set(alignedAccountBoundEntries.flatMap(entry =>
|
|
826
|
+
typeof entry.slug === "string" ? [entry.slug] : []
|
|
827
|
+
));
|
|
828
|
+
finalRoutedEntries = finalRoutedEntries.filter(entry => {
|
|
829
|
+
if (typeof entry.slug !== "string" || !accountBoundSlugs.has(entry.slug)) return true;
|
|
830
|
+
if (freshSlugs.has(entry.slug) && policy.warningPolicy === "emit") {
|
|
831
|
+
warnAccountSelectorShadowedProviderOnce(entry.slug);
|
|
832
|
+
}
|
|
833
|
+
return false;
|
|
834
|
+
});
|
|
835
|
+
const finalRoutedEntrySet = new Set(finalRoutedEntries);
|
|
836
|
+
const degradedPreservedCount = preservedRoutedEntries.filter(entry => {
|
|
837
|
+
if (!finalRoutedEntrySet.has(entry)) return false;
|
|
838
|
+
const slug = entry.slug as string;
|
|
839
|
+
const provider = slug.slice(0, slug.indexOf("/"));
|
|
840
|
+
return gatheredProviderNames.has(provider) && degradedProviderNames.has(provider);
|
|
841
|
+
}).length;
|
|
842
|
+
if (degradedPreservedCount > 0 && policy.warningPolicy === "emit") {
|
|
843
|
+
console.warn(`[opencodex] catalog sync: provider discovery degraded; preserving ${degradedPreservedCount} existing routed entr${degradedPreservedCount === 1 ? "y" : "ies"} on disk.`);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const managedEntries = [...finalRoutedEntries, ...alignedAccountBoundEntries];
|
|
847
|
+
const observedNativeSlugs = new Set(alignedAccountBoundEntries.flatMap(entry => {
|
|
848
|
+
const slug = trustedAccountBoundNativeCatalogSlug(entry);
|
|
849
|
+
return slug === undefined ? [] : [slug];
|
|
850
|
+
}));
|
|
851
|
+
for (const slug of policy.nativeBackfillSlugs) observedNativeSlugs.add(slug);
|
|
852
|
+
const mergedEntries = [...native, ...managedEntries].map(m => {
|
|
853
|
+
const reserveProjection = isReserveCatalogProjection(m);
|
|
854
|
+
const normalized = reserveProjection ? m : normalizeServiceTiers(m);
|
|
855
|
+
if (!reserveProjection && !isNativeAliasCatalogEntry(normalized)) applyNativeOpenAiContextOverride(normalized, openaiContextCap);
|
|
856
|
+
const exactCombo = isExactComboCatalogEntry(m, exactComboSlugs);
|
|
857
|
+
const e = reserveProjection ? normalized : ensureStrictCatalogFields(normalized, {
|
|
858
|
+
preserveExactInputModalities: exactCombo,
|
|
859
|
+
isRouted: finalRoutedEntrySet.has(m),
|
|
860
|
+
});
|
|
861
|
+
// Mock-max universality (260709): preserved routed entries from disk may predate
|
|
862
|
+
// the max rung — ensure it here so subagent max spawns validate on every
|
|
863
|
+
// reasoning-capable entry. max only: 5.6 exact ladders (luna: no ultra) stay intact.
|
|
864
|
+
if (!freshCustomEntries.has(m) && !exactCombo && !reserveProjection && !String(e.slug ?? "").startsWith("opencode-go/")) {
|
|
865
|
+
const levels = Array.isArray(e.supported_reasoning_levels)
|
|
866
|
+
? e.supported_reasoning_levels as Array<{ effort?: string }>
|
|
867
|
+
: [];
|
|
868
|
+
if (levels.length > 0 && !levels.some(level => level.effort === "max")) {
|
|
869
|
+
levels.push(CODEX_REASONING_LEVELS.find(level => level.effort === "max")
|
|
870
|
+
?? { effort: "max", description: "Maximum reasoning depth for the hardest problems" });
|
|
871
|
+
e.supported_reasoning_levels = levels;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
if (wsEnabled) e.supports_websockets = true;
|
|
875
|
+
else {
|
|
876
|
+
delete e.supports_websockets;
|
|
877
|
+
// Match buildCatalogEntries: never advertise a websocket preference while WS is off.
|
|
878
|
+
delete e.prefer_websockets;
|
|
879
|
+
}
|
|
880
|
+
return e;
|
|
881
|
+
});
|
|
882
|
+
// Native enable/disable runs as the LAST pass so the upstream-upgrade branch above can never
|
|
883
|
+
// clobber a hide flag back to list. Bare ids disable every account clone; qualified ids disable
|
|
884
|
+
// only their generated account row.
|
|
885
|
+
const versionedEntries = applyMultiAgentMode(
|
|
886
|
+
applyNativeVisibility(mergedEntries, disabledModels, alignedAccountBoundEntries.length > 0, observedNativeSlugs),
|
|
887
|
+
multiAgentMode,
|
|
888
|
+
multiAgentV2Enabled,
|
|
889
|
+
{ keepNativeChatGptOnV1, preserveDefaultMultiAgentVersion: isReserveCatalogProjection },
|
|
890
|
+
);
|
|
891
|
+
applyFullModelPickerOrder(versionedEntries, modelPickerOrder);
|
|
892
|
+
for (const entry of versionedEntries) {
|
|
893
|
+
// Templates and account clones must not inherit the native row's overlay marker.
|
|
894
|
+
delete entry.opencodex_native_display_name;
|
|
895
|
+
const slug = recoverableNativeSlug(entry);
|
|
896
|
+
if (slug !== null) {
|
|
897
|
+
const label = nativeDisplayNames && Object.hasOwn(nativeDisplayNames, slug)
|
|
898
|
+
? nativeDisplayNames[slug]?.trim() : undefined;
|
|
899
|
+
if (label && label !== entry.display_name) {
|
|
900
|
+
entry.opencodex_native_display_name = { slug, original: entry.display_name, applied: label };
|
|
901
|
+
entry.display_name = label;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
const kind = entry.opencodex_catalog_kind;
|
|
905
|
+
if (trustedAccountBoundNativeCatalogSlug(entry) === undefined
|
|
906
|
+
&& kind !== CODEX_CUSTOM_MODEL_CATALOG_KIND
|
|
907
|
+
&& kind !== CODEX_PROVIDER_MODEL_CATALOG_KIND) continue;
|
|
908
|
+
// Canonicalize extension-field order after every normalizer. This keeps an unchanged catalog
|
|
909
|
+
// byte-idempotent whether an owned row was freshly built or retained from the prior pass.
|
|
910
|
+
delete entry.opencodex_catalog_kind;
|
|
911
|
+
entry.opencodex_catalog_kind = kind;
|
|
912
|
+
}
|
|
913
|
+
return versionedEntries;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/** Merge retained-sync rows using the process-observed Codex feature state. */
|
|
917
|
+
export function mergeCatalogEntriesForSync(
|
|
918
|
+
catalogModels: RawEntry[],
|
|
919
|
+
routedEntries: RawEntry[],
|
|
920
|
+
baseline: Map<string, number>,
|
|
921
|
+
featured: string[],
|
|
922
|
+
wsEnabled: boolean,
|
|
923
|
+
_goIds: Set<string> = new Set(),
|
|
924
|
+
template: RawEntry | null = null,
|
|
925
|
+
disabledModels: ReadonlySet<string> = new Set(),
|
|
926
|
+
gatheredProviderNames?: Set<string>,
|
|
927
|
+
multiAgentMode: MultiAgentMode = "default",
|
|
928
|
+
exactComboSlugs: ReadonlySet<string> = new Set(),
|
|
929
|
+
hasPhysicalComboProvider = false,
|
|
930
|
+
includeNativeOpenAi = true,
|
|
931
|
+
accountBoundEntries: readonly RawEntry[] = [],
|
|
932
|
+
legacyCustomModelSlugs: ReadonlySet<string> = new Set(),
|
|
933
|
+
suppressedBareNativeSlugs: ReadonlySet<string> = new Set(
|
|
934
|
+
routedEntries.flatMap(entry => (
|
|
935
|
+
isNativeAliasCatalogEntry(entry) && typeof entry.slug === "string" ? [entry.slug] : []
|
|
936
|
+
)),
|
|
937
|
+
),
|
|
938
|
+
openaiContextCap?: NativeContextLimitsInput,
|
|
939
|
+
keepNativeChatGptOnV1 = false,
|
|
940
|
+
): RawEntry[] {
|
|
941
|
+
// Retained for source compatibility with the original helper contract. Raw provider ids must
|
|
942
|
+
// not suppress same-named native rows; actual admitted combo entries own that decision now.
|
|
943
|
+
void _goIds;
|
|
944
|
+
const effectiveGatheredProviderNames = gatheredProviderNames ?? new Set(
|
|
945
|
+
routedEntries.flatMap(entry => {
|
|
946
|
+
// A slashed combo alias is not evidence that its public prefix is an authoritative provider
|
|
947
|
+
// namespace. Treating it as one would let the combo replace an unrestorable foreign row.
|
|
948
|
+
if (isExactComboCatalogEntry(entry, exactComboSlugs)) return [];
|
|
949
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
950
|
+
const slash = slug.indexOf("/");
|
|
951
|
+
return slash > 0 ? [slug.slice(0, slash)] : [];
|
|
952
|
+
}),
|
|
953
|
+
);
|
|
954
|
+
return mergeCatalogEntriesFromObservedState({
|
|
955
|
+
catalogModels,
|
|
956
|
+
baselineCatalogModels: [],
|
|
957
|
+
routedEntries,
|
|
958
|
+
baseline,
|
|
959
|
+
featured,
|
|
960
|
+
wsEnabled,
|
|
961
|
+
template,
|
|
962
|
+
disabledModels,
|
|
963
|
+
selectedModelsByProvider: new Map(),
|
|
964
|
+
gatheredProviderNames: effectiveGatheredProviderNames,
|
|
965
|
+
degradedProviderNames: new Set(),
|
|
966
|
+
legacyCustomModelSlugs,
|
|
967
|
+
multiAgentMode,
|
|
968
|
+
multiAgentV2Enabled: isMultiAgentV2Enabled(),
|
|
969
|
+
keepNativeChatGptOnV1,
|
|
970
|
+
exactComboSlugs,
|
|
971
|
+
hasPhysicalComboProvider,
|
|
972
|
+
includeNativeOpenAi,
|
|
973
|
+
accountBoundEntries,
|
|
974
|
+
suppressedBareNativeSlugs,
|
|
975
|
+
openaiContextCap,
|
|
976
|
+
policy: {
|
|
977
|
+
...CANONICAL_NATIVE_CATALOG_CONTENT_POLICY,
|
|
978
|
+
warningPolicy: "emit",
|
|
979
|
+
},
|
|
980
|
+
});
|
|
981
|
+
}
|