@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,375 @@
|
|
|
1
|
+
import { effectiveProviderAlias, effectiveProviderAliasDecision } from "../../providers/default-aliases";
|
|
2
|
+
import { initialModelSelectionPending } from "../../providers/initial-model-selection";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { createHash, createHmac, randomBytes } from "node:crypto";
|
|
5
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
|
|
6
|
+
import { delimiter, dirname, join, resolve } from "node:path";
|
|
7
|
+
import { atomicWriteFile, expandUserPath, getConfigDir, websocketsEnabled } from "../../config";
|
|
8
|
+
import { resolveProviderApiKey } from "../../providers/key-store";
|
|
9
|
+
import { CODEX_CONFIG_PATH, CODEX_MODELS_CACHE_PATH, DEFAULT_CATALOG_PATH, readRootTomlString, resolveCodexConfigPath } from "../paths";
|
|
10
|
+
import {
|
|
11
|
+
clearModelCache,
|
|
12
|
+
clearProviderDiscoveryStatus,
|
|
13
|
+
captureModelCacheGeneration,
|
|
14
|
+
DEFAULT_MODEL_CACHE_TTL_MS,
|
|
15
|
+
getFreshCached,
|
|
16
|
+
getStaleCached,
|
|
17
|
+
isModelsFetchCoolingDown,
|
|
18
|
+
isModelCacheGenerationCurrent,
|
|
19
|
+
markModelsFetchFailure,
|
|
20
|
+
markProviderDiscoveryFailed,
|
|
21
|
+
markProviderDiscoveryOk,
|
|
22
|
+
shouldLogDiscoveryFailure,
|
|
23
|
+
setCached,
|
|
24
|
+
type ProviderModelDiscoveryFailure,
|
|
25
|
+
} from "../model-cache";
|
|
26
|
+
import {
|
|
27
|
+
buildModelsRequest,
|
|
28
|
+
getValidAccessTokenSnapshot,
|
|
29
|
+
observeActiveOAuthAccessToken,
|
|
30
|
+
resolveModelsAuthToken,
|
|
31
|
+
type OAuthActiveTokenObservation,
|
|
32
|
+
} from "../../oauth";
|
|
33
|
+
import type { OcxConfig, OcxProviderConfig } from "../../types";
|
|
34
|
+
import { modelInList } from "../../types";
|
|
35
|
+
import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
|
|
36
|
+
import { isModelVisionSidecarConsumer } from "../../vision/eligibility";
|
|
37
|
+
import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, resolveMetadataProvider, type ModelMetadata } from "../../generated/model-metadata";
|
|
38
|
+
import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
|
|
39
|
+
import {
|
|
40
|
+
captureFastPolicyAuthority,
|
|
41
|
+
fastPolicyForModel,
|
|
42
|
+
serviceTierSupportFromPolicy,
|
|
43
|
+
} from "../../providers/service-tier";
|
|
44
|
+
import type { FastPolicyAuthority } from "../../providers/fastwire";
|
|
45
|
+
import { effectiveGoogleMode, getProviderRegistryEntry, providerMatchesRegistryTransport, registryEntryForProviderDestination } from "../../providers/registry";
|
|
46
|
+
import { parseAntigravityAvailableModels, registerAntigravityDiscoveredWireModels } from "../../providers/antigravity-models";
|
|
47
|
+
import { applyProviderContextCap, providerContextCap, resolveUnknownRoutedContextWindow } from "../../providers/context-cap";
|
|
48
|
+
import { clampAutoCompactTokenLimit } from "../../providers/auto-compact-budget";
|
|
49
|
+
import { effectiveModelAliases } from "../../providers/default-aliases";
|
|
50
|
+
import { routedSlug, slugEquals, slugEquivalenceKey, slugsEquivalent } from "../../providers/slug-codec";
|
|
51
|
+
import { CODEX_GPT5_IDENTITY_LINE } from "../../adapters/identity";
|
|
52
|
+
import { filterCursorConfiguredModelsByLiveDiscovery } from "../../adapters/cursor/discovery";
|
|
53
|
+
import { fetchCursorUsableModels } from "../../adapters/cursor/live-models";
|
|
54
|
+
import { recordLiveCursorClaudeModels, recordLiveCursorMaxModeModels } from "../../adapters/cursor/catalog";
|
|
55
|
+
import { fetchQoderModels } from "../../adapters/qoder/live-models";
|
|
56
|
+
import { resolveQoderProfile } from "../../adapters/qoder/profiles";
|
|
57
|
+
import { fetchDevinUsableModels } from "../../adapters/devin/live-models";
|
|
58
|
+
import { isCanonicalOpenAiForwardProvider, OPENAI_API_PROVIDER_ID, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
|
|
59
|
+
import {
|
|
60
|
+
COMBO_NAMESPACE,
|
|
61
|
+
comboModelId,
|
|
62
|
+
getCombo,
|
|
63
|
+
listComboIds,
|
|
64
|
+
quotaInactiveReason,
|
|
65
|
+
targetKey,
|
|
66
|
+
} from "../../combos";
|
|
67
|
+
import type { NormalizedComboConfig } from "../../combos/types";
|
|
68
|
+
import {
|
|
69
|
+
ProviderOutboundPolicyError,
|
|
70
|
+
providerOutboundGet,
|
|
71
|
+
providerOutboundPost,
|
|
72
|
+
providerRedirectError,
|
|
73
|
+
} from "../../lib/provider-outbound";
|
|
74
|
+
import { redactSecretString } from "../../lib/redact";
|
|
75
|
+
import {
|
|
76
|
+
extractProviderModelItems,
|
|
77
|
+
isRegistryModelDiscoveryUrl,
|
|
78
|
+
readBoundedDiscoveryJson,
|
|
79
|
+
resolveProviderModelDiscovery,
|
|
80
|
+
type ModelDiscoveryResponseFailure,
|
|
81
|
+
type ProviderModelsApiItem,
|
|
82
|
+
type ResolvedProviderModelDiscovery,
|
|
83
|
+
} from "../../providers/model-discovery";
|
|
84
|
+
import { extractGoogleAiStudioModelItems } from "../../providers/google-ai-studio-model-discovery";
|
|
85
|
+
import { applyConfiguredHeadersLast, fetchOllamaShowEnrichment, ollamaShowEnrichable } from "../../providers/ollama-show";
|
|
86
|
+
import upstreamModelsSnapshot from "../data/upstream-models.json";
|
|
87
|
+
import { createAdmissionGate, ResourceAdmissionError, type AdmissionMetrics } from "../../lib/admission";
|
|
88
|
+
import { CODEX_CUSTOM_MODEL_CATALOG_KIND, JAWCODE_CATALOG_AUGMENT_PROVIDERS, catalogModelSlug, shouldExposeRoutedModel } from "./parsing";
|
|
89
|
+
import type { CatalogModel } from "./parsing";
|
|
90
|
+
import { disabledNativeSlugs, hasComboTargets, hasNativeOpenAiCapabilityMetadata, NATIVE_GPT56_MAX_INPUT_TOKENS, nativeContextLimits, nativeOpenAiCapabilityDisplayName, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiAutoCompactTokenLimit, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, nativeOpenAiMaxOutputTokens, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
|
|
91
|
+
import { deriveComboCatalogModel, normalizedOpenAiApiSignature, openAiApiCollisionWarnings, replaceLastComboCatalogOmissions, warnUncataloguedComboOnce } from "./aggregation";
|
|
92
|
+
import type { ComboCatalogOmission } from "./aggregation";
|
|
93
|
+
import type { CatalogGatherProviderAuthEvidence } from "./filesystem-evidence";
|
|
94
|
+
import type {
|
|
95
|
+
CatalogAdmissionSnapshot,
|
|
96
|
+
CatalogDiscoveryPolicyField,
|
|
97
|
+
CatalogGatherAuthorityIdentity,
|
|
98
|
+
CatalogProviderDiscoveryPolicySnapshot,
|
|
99
|
+
CatalogProcessLocalEvidence,
|
|
100
|
+
CatalogSourceEvidence,
|
|
101
|
+
CatalogTrustedOpenAiApiPolicySnapshot,
|
|
102
|
+
} from "../convergence-types";
|
|
103
|
+
import { applyProviderConfigHints, configuredAutoCompactTokenLimit, positiveSafeInteger } from "./model-hints";
|
|
104
|
+
|
|
105
|
+
/** Model ids each provider must retain for combo catalog derivation (OCX-111). */
|
|
106
|
+
export function configuredComboTargetModelsByProvider(
|
|
107
|
+
config: Pick<OcxConfig, "combos">,
|
|
108
|
+
): Map<string, ReadonlySet<string>> {
|
|
109
|
+
const byProvider = new Map<string, Set<string>>();
|
|
110
|
+
for (const id of listComboIds(config)) {
|
|
111
|
+
const combo = getCombo(config, id);
|
|
112
|
+
if (!combo) continue;
|
|
113
|
+
for (const target of combo.targets) {
|
|
114
|
+
let models = byProvider.get(target.provider);
|
|
115
|
+
if (!models) {
|
|
116
|
+
models = new Set();
|
|
117
|
+
byProvider.set(target.provider, models);
|
|
118
|
+
}
|
|
119
|
+
models.add(target.model);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return byProvider;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Last-resort context window for combo member synthesis when discovery,
|
|
126
|
+
* provider config, and an enabled Context cap all omit one. Matches the
|
|
127
|
+
* catalog entry default in `normalizeRoutedCatalogEntry` so incomplete live
|
|
128
|
+
* rows still catalog. An enabled Context cap is the operator-facing window,
|
|
129
|
+
* not a clamp on this placeholder.
|
|
130
|
+
*/
|
|
131
|
+
const COMBO_MEMBER_CONTEXT_FALLBACK = 128_000;
|
|
132
|
+
|
|
133
|
+
interface ComboCatalogMemberFallback {
|
|
134
|
+
readonly contextWindow?: number;
|
|
135
|
+
/** Input ceiling when it is lower than the window (native GPT-5.6: 922k under 1.05M). */
|
|
136
|
+
readonly maxInputTokens?: number;
|
|
137
|
+
readonly maxOutputTokens?: number;
|
|
138
|
+
readonly autoCompactTokenLimit?: number;
|
|
139
|
+
readonly inputModalities?: readonly string[];
|
|
140
|
+
readonly reasoningEfforts?: readonly string[];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Ladder advertised for a combo member whose vendor metadata says it reasons but
|
|
145
|
+
* carries no explicit ladder (Claude, Grok). Codex needs a non-empty ladder to show
|
|
146
|
+
* the effort control; the routed adapters clamp to the real upstream top rung.
|
|
147
|
+
*/
|
|
148
|
+
const ROUTED_COMBO_MEMBER_REASONING_EFFORTS: readonly string[] = ["low", "medium", "high", "xhigh", "max"];
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Vendor-table lookup tolerant of point releases and date pins. Configured combo
|
|
152
|
+
* targets often name a variant the table does not carry (`claude-fable-5-1`,
|
|
153
|
+
* `claude-opus-4-5-20251101`); the base family row still describes its modality
|
|
154
|
+
* and reasoning capability, so fall back to it before giving up.
|
|
155
|
+
*/
|
|
156
|
+
function comboMemberVendorMetadata(provider: string, modelId: string): ModelMetadata | undefined {
|
|
157
|
+
const exact = getModelMetadataCaseInsensitive(provider, modelId);
|
|
158
|
+
if (exact) return exact;
|
|
159
|
+
let candidate = modelId.replace(/\[[^\]]*\]$/, "");
|
|
160
|
+
while (true) {
|
|
161
|
+
const trimmed = candidate.replace(/-\d+$/, "");
|
|
162
|
+
if (trimmed === candidate || !trimmed.includes("-")) return undefined;
|
|
163
|
+
const hit = getModelMetadataCaseInsensitive(provider, trimmed);
|
|
164
|
+
if (hit) return hit;
|
|
165
|
+
candidate = trimmed;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Combo members are usually thin discovery rows (id + context window). Without a
|
|
171
|
+
* capability source the combo intersection collapses to text-only / no effort ladder,
|
|
172
|
+
* and the Codex app then refuses image attachments and hides the effort picker for
|
|
173
|
+
* every Claude combo. The generated vendor table knows both, so use it as the
|
|
174
|
+
* last-resort fallback when the caller supplied none.
|
|
175
|
+
*
|
|
176
|
+
* `ModelMetadata.maxTokens` is the OUTPUT ceiling, so it fills `maxOutputTokens`.
|
|
177
|
+
* Mapping it onto `maxInputTokens` would be read by the combo intersection
|
|
178
|
+
* (`aggregation.ts` `Math.min` over member input ceilings) as a 128k input limit and
|
|
179
|
+
* shrink a 1M Claude combo window to 128k, taking autoCompactTokenLimit down with it.
|
|
180
|
+
*/
|
|
181
|
+
function vendorMetadataComboFallback(target: { provider: string; model: string }): ComboCatalogMemberFallback | undefined {
|
|
182
|
+
const metadataProvider = resolveMetadataProvider(target.provider);
|
|
183
|
+
// Custom OpenAI-compatible routes commonly retain the canonical OpenAI model id
|
|
184
|
+
// while using a provider name that has no metadata alias. Reuse only its effort
|
|
185
|
+
// ladder below; context/modality rows remain provider-owned.
|
|
186
|
+
const metadata = metadataProvider
|
|
187
|
+
? comboMemberVendorMetadata(metadataProvider, target.model)
|
|
188
|
+
: comboMemberVendorMetadata("openai", target.model);
|
|
189
|
+
if (!metadata) return undefined;
|
|
190
|
+
return {
|
|
191
|
+
...(metadataProvider && typeof metadata.contextWindow === "number" && metadata.contextWindow > 0
|
|
192
|
+
? { contextWindow: metadata.contextWindow }
|
|
193
|
+
: {}),
|
|
194
|
+
...(metadataProvider && typeof metadata.maxTokens === "number" && metadata.maxTokens > 0
|
|
195
|
+
? { maxOutputTokens: metadata.maxTokens }
|
|
196
|
+
: {}),
|
|
197
|
+
...(metadataProvider && Array.isArray(metadata.input) && metadata.input.length > 0
|
|
198
|
+
? { inputModalities: [...metadata.input] }
|
|
199
|
+
: {}),
|
|
200
|
+
...(metadata.reasoning === true ? { reasoningEfforts: [...ROUTED_COMBO_MEMBER_REASONING_EFFORTS] } : {}),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Resolve a combo target to a catalog member for derivation.
|
|
206
|
+
* Prefer discovery metadata; when the target is missing from the gather map or
|
|
207
|
+
* lacks a positive contextWindow, synthesize from the (registry-enriched)
|
|
208
|
+
* provider config so combos remain catalogued when targets are configured but
|
|
209
|
+
* discovery metadata is incomplete. Disabled providers stay unresolved.
|
|
210
|
+
* When hints still omit contextWindow, prefer known maxInputTokens, else the
|
|
211
|
+
* enabled Context cap, else COMBO_MEMBER_CONTEXT_FALLBACK so a live row
|
|
212
|
+
* without ctx does not drop the whole combo from the public catalog.
|
|
213
|
+
*/
|
|
214
|
+
export function resolveComboCatalogMember(
|
|
215
|
+
target: { provider: string; model: string },
|
|
216
|
+
memberByKey: ReadonlyMap<string, CatalogModel>,
|
|
217
|
+
providers: ReadonlyMap<string, OcxProviderConfig>,
|
|
218
|
+
contextCap?: number,
|
|
219
|
+
callerFallback?: ComboCatalogMemberFallback,
|
|
220
|
+
metadataModelIdCaseFold?: boolean,
|
|
221
|
+
): CatalogModel | undefined {
|
|
222
|
+
const existing = memberByKey.get(targetKey(target));
|
|
223
|
+
const prov = providers.get(target.provider);
|
|
224
|
+
const fallback = callerFallback ?? vendorMetadataComboFallback(target);
|
|
225
|
+
// Disabled providers never contribute members — even a complete discovery row
|
|
226
|
+
// is unusable for catalog derivation while the provider is off.
|
|
227
|
+
if (prov?.disabled === true) return undefined;
|
|
228
|
+
|
|
229
|
+
const withFallbackMetadata = (member: CatalogModel): CatalogModel => {
|
|
230
|
+
const contextWindow = typeof member.contextWindow === "number" && member.contextWindow > 0
|
|
231
|
+
? member.contextWindow
|
|
232
|
+
: undefined;
|
|
233
|
+
const addMaxInput = fallback !== undefined && contextWindow !== undefined
|
|
234
|
+
&& !(typeof member.maxInputTokens === "number" && member.maxInputTokens > 0);
|
|
235
|
+
const addMaxOutput = fallback !== undefined
|
|
236
|
+
&& typeof fallback.maxOutputTokens === "number"
|
|
237
|
+
&& fallback.maxOutputTokens > 0
|
|
238
|
+
&& !(typeof member.maxOutputTokens === "number" && member.maxOutputTokens > 0);
|
|
239
|
+
const effectiveMaxInput = addMaxInput
|
|
240
|
+
? Math.min(fallback?.maxInputTokens ?? contextWindow!, contextWindow!)
|
|
241
|
+
: member.maxInputTokens;
|
|
242
|
+
const softCandidates = [member.autoCompactTokenLimit, fallback?.autoCompactTokenLimit]
|
|
243
|
+
.filter((value): value is number => typeof value === "number" && value > 0);
|
|
244
|
+
const autoCompactTokenLimit = contextWindow !== undefined && softCandidates.length > 0
|
|
245
|
+
? clampAutoCompactTokenLimit(contextWindow, effectiveMaxInput, Math.min(...softCandidates))
|
|
246
|
+
: member.autoCompactTokenLimit;
|
|
247
|
+
const adjustAutoCompact = autoCompactTokenLimit !== member.autoCompactTokenLimit;
|
|
248
|
+
const addModalities = (!Array.isArray(member.inputModalities) || member.inputModalities.length === 0)
|
|
249
|
+
&& fallback?.inputModalities !== undefined;
|
|
250
|
+
const addReasoning = member.reasoningEfforts === undefined
|
|
251
|
+
&& fallback?.reasoningEfforts !== undefined;
|
|
252
|
+
if (!addMaxInput && !addMaxOutput && !adjustAutoCompact && !addModalities && !addReasoning) return member;
|
|
253
|
+
return {
|
|
254
|
+
...member,
|
|
255
|
+
// Never claim a larger input budget than the window, and prefer the model's own
|
|
256
|
+
// measured ceiling when the fallback carries one.
|
|
257
|
+
...(addMaxInput ? { maxInputTokens: effectiveMaxInput } : {}),
|
|
258
|
+
...(addMaxOutput ? { maxOutputTokens: fallback!.maxOutputTokens } : {}),
|
|
259
|
+
...(adjustAutoCompact && autoCompactTokenLimit !== undefined ? { autoCompactTokenLimit } : {}),
|
|
260
|
+
...(addModalities ? { inputModalities: [...fallback!.inputModalities!] } : {}),
|
|
261
|
+
...(addReasoning ? { reasoningEfforts: [...fallback!.reasoningEfforts!] } : {}),
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Complete live/configured rows still honour providerContextCaps so a high
|
|
266
|
+
// discovery window cannot outrun an operator-configured cap. Native-alias
|
|
267
|
+
// fallback metadata may fill only capability gaps; it never raises an explicit
|
|
268
|
+
// discovered/configured context window.
|
|
269
|
+
if (
|
|
270
|
+
existing
|
|
271
|
+
&& typeof existing.contextWindow === "number"
|
|
272
|
+
&& existing.contextWindow > 0
|
|
273
|
+
) {
|
|
274
|
+
// Live discovery can explicitly say text-only even when configured routing
|
|
275
|
+
// supplies a vision sidecar. Apply the same provider hints used for thin
|
|
276
|
+
// rows before deriving a combo from this complete row.
|
|
277
|
+
const hinted = prov && isModelVisionSidecarConsumer(prov, existing.id)
|
|
278
|
+
? applyProviderConfigHints(target.provider, prov, existing, contextCap, metadataModelIdCaseFold)
|
|
279
|
+
: existing;
|
|
280
|
+
const capped = applyProviderContextCap(hinted.contextWindow, contextCap);
|
|
281
|
+
if (capped === undefined || capped === existing.contextWindow) {
|
|
282
|
+
return withFallbackMetadata(hinted);
|
|
283
|
+
}
|
|
284
|
+
const maxInput = typeof hinted.maxInputTokens === "number" && hinted.maxInputTokens > 0
|
|
285
|
+
? Math.min(hinted.maxInputTokens, capped)
|
|
286
|
+
: Math.min(fallback?.maxInputTokens ?? capped, capped);
|
|
287
|
+
return withFallbackMetadata({
|
|
288
|
+
...hinted,
|
|
289
|
+
contextWindow: capped,
|
|
290
|
+
maxInputTokens: maxInput,
|
|
291
|
+
contextCap,
|
|
292
|
+
contextCapped: true as const,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const base: CatalogModel = existing ?? {
|
|
297
|
+
id: target.model,
|
|
298
|
+
provider: target.provider,
|
|
299
|
+
};
|
|
300
|
+
const hinted = prov
|
|
301
|
+
? applyProviderConfigHints(target.provider, prov, base, contextCap, metadataModelIdCaseFold)
|
|
302
|
+
: base;
|
|
303
|
+
const hintedContext = typeof hinted.contextWindow === "number" && hinted.contextWindow > 0
|
|
304
|
+
? hinted.contextWindow
|
|
305
|
+
: undefined;
|
|
306
|
+
const knownMaxInput = typeof hinted.maxInputTokens === "number" && hinted.maxInputTokens > 0
|
|
307
|
+
? hinted.maxInputTokens
|
|
308
|
+
: (typeof base.maxInputTokens === "number" && base.maxInputTokens > 0
|
|
309
|
+
? base.maxInputTokens
|
|
310
|
+
: undefined);
|
|
311
|
+
// Kept OUT of knownMaxInput on purpose: that value doubles as a context-window fallback
|
|
312
|
+
// below, and a native alias whose input ceiling (922k) is lower than its window (1.05M)
|
|
313
|
+
// would otherwise shrink the advertised window to the input limit.
|
|
314
|
+
const fallbackMaxInput = existing || prov ? fallback?.maxInputTokens : undefined;
|
|
315
|
+
// Real discovery/config values win. A native alias is the next fallback tier.
|
|
316
|
+
// The generic 128k/text synthesis from #1305 remains the final fallback.
|
|
317
|
+
const fallbackContext = existing || prov ? fallback?.contextWindow : undefined;
|
|
318
|
+
const uncappedContext = hintedContext
|
|
319
|
+
?? knownMaxInput
|
|
320
|
+
?? fallbackContext
|
|
321
|
+
?? (existing || prov ? resolveUnknownRoutedContextWindow(contextCap) : undefined);
|
|
322
|
+
if (uncappedContext === undefined) return undefined;
|
|
323
|
+
// 真发现值才压低。resolveUnknownRoutedContextWindow 已经把 cap 当成窗口填进去了,不能再 min 一次。
|
|
324
|
+
const usedDiscoveredWindow = hintedContext !== undefined || knownMaxInput !== undefined || fallbackContext !== undefined;
|
|
325
|
+
const cappedContext = usedDiscoveredWindow
|
|
326
|
+
? applyProviderContextCap(uncappedContext, contextCap)
|
|
327
|
+
: uncappedContext;
|
|
328
|
+
const contextWindow = cappedContext ?? uncappedContext;
|
|
329
|
+
const fallbackCapped = usedDiscoveredWindow
|
|
330
|
+
&& contextCap !== undefined
|
|
331
|
+
&& cappedContext !== undefined
|
|
332
|
+
&& cappedContext !== uncappedContext;
|
|
333
|
+
|
|
334
|
+
const inputModalities = hinted.inputModalities
|
|
335
|
+
?? base.inputModalities
|
|
336
|
+
?? (fallback?.inputModalities ? [...fallback.inputModalities] : undefined)
|
|
337
|
+
?? ["text"];
|
|
338
|
+
const reasoningEfforts = hinted.reasoningEfforts
|
|
339
|
+
?? (prov ? configuredReasoningEfforts(prov, target.model) : undefined)
|
|
340
|
+
?? base.reasoningEfforts
|
|
341
|
+
?? (fallback?.reasoningEfforts ? [...fallback.reasoningEfforts] : undefined);
|
|
342
|
+
const maxOutputTokens = positiveSafeInteger(hinted.maxOutputTokens, base.maxOutputTokens)
|
|
343
|
+
?? (existing || prov ? positiveSafeInteger(fallback?.maxOutputTokens) : undefined);
|
|
344
|
+
// The model's own measured input ceiling still applies when discovery gave us nothing:
|
|
345
|
+
// GPT-5.6 advertises a 1.05M window but refuses input past 922k.
|
|
346
|
+
const effectiveMaxInput = knownMaxInput ?? fallbackMaxInput;
|
|
347
|
+
const maxInputTokens = effectiveMaxInput !== undefined
|
|
348
|
+
? Math.min(effectiveMaxInput, contextWindow)
|
|
349
|
+
: contextWindow;
|
|
350
|
+
const softCandidates = [
|
|
351
|
+
hinted.autoCompactTokenLimit,
|
|
352
|
+
base.autoCompactTokenLimit,
|
|
353
|
+
fallback?.autoCompactTokenLimit,
|
|
354
|
+
configuredAutoCompactTokenLimit(prov, target.model),
|
|
355
|
+
].filter((value): value is number => typeof value === "number" && value > 0);
|
|
356
|
+
// A generic 128k synthesis is a catalog compatibility fallback, not evidence
|
|
357
|
+
// that a configured soft policy has an authoritative window to clamp against.
|
|
358
|
+
const hasAuthoritativeAutoCompactBasis = hintedContext !== undefined
|
|
359
|
+
|| fallbackContext !== undefined
|
|
360
|
+
|| contextCap !== undefined;
|
|
361
|
+
const autoCompactTokenLimit = hasAuthoritativeAutoCompactBasis && softCandidates.length > 0
|
|
362
|
+
? clampAutoCompactTokenLimit(contextWindow, maxInputTokens, Math.min(...softCandidates))
|
|
363
|
+
: undefined;
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
...hinted,
|
|
367
|
+
inputModalities,
|
|
368
|
+
...(reasoningEfforts !== undefined ? { reasoningEfforts } : {}),
|
|
369
|
+
contextWindow,
|
|
370
|
+
maxInputTokens,
|
|
371
|
+
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
372
|
+
...(autoCompactTokenLimit !== undefined ? { autoCompactTokenLimit } : {}),
|
|
373
|
+
...(fallbackCapped ? { contextCap, contextCapped: true as const } : {}),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import type { OcxConfig } from "../../types";
|
|
2
|
+
import { effectiveProviderAlias } from "../../providers/default-aliases";
|
|
3
|
+
import { identifyRoutedModel } from "../../adapters/identity";
|
|
4
|
+
import { COMBO_NAMESPACE } from "../../combos";
|
|
5
|
+
import {
|
|
6
|
+
CODEX_CUSTOM_MODEL_CATALOG_KIND,
|
|
7
|
+
applyCatalogMetadata,
|
|
8
|
+
applyNativeOpenAiContextOverride,
|
|
9
|
+
applyRoutedCodexToolMode,
|
|
10
|
+
catalogModelSlug,
|
|
11
|
+
ensureStrictCatalogFields,
|
|
12
|
+
normalizeRoutedCatalogEntry,
|
|
13
|
+
normalizeServiceTiers,
|
|
14
|
+
} from "./parsing";
|
|
15
|
+
import type { CatalogModel, RawEntry } from "./parsing";
|
|
16
|
+
import {
|
|
17
|
+
hasNativeOpenAiCapabilityMetadata,
|
|
18
|
+
upstreamNativeEntry,
|
|
19
|
+
type NativeContextLimitsInput,
|
|
20
|
+
} from "./metadata";
|
|
21
|
+
import {
|
|
22
|
+
applyCatalogModelMetadata,
|
|
23
|
+
applyReasoningLevels,
|
|
24
|
+
ensureGpt56ReasoningLevels,
|
|
25
|
+
ensureUltraReasoningLevel,
|
|
26
|
+
isGpt56NativeSlug,
|
|
27
|
+
} from "./effort";
|
|
28
|
+
import { CATALOG_INACTIVE_REASON_FIELD, SPAWN_PRIORITY_FIELD } from "./subagent-roster";
|
|
29
|
+
|
|
30
|
+
export function finishUpstreamNativeEntry(clone: RawEntry, priority: number, contextCap?: NativeContextLimitsInput): RawEntry {
|
|
31
|
+
if (priority !== 9) clone.priority = priority;
|
|
32
|
+
applyNativeOpenAiContextOverride(clone, contextCap);
|
|
33
|
+
// GPT-5.6 natives keep their exact upstream ladders (e.g. luna has max but no ultra).
|
|
34
|
+
// Older natives (gpt-5.5) get mock max + ultra
|
|
35
|
+
// (wire-clamped to xhigh). Ultra is always advertised regardless of v2 toggle.
|
|
36
|
+
if (!isGpt56NativeSlug(String(clone.slug ?? ""))) ensureUltraReasoningLevel(clone);
|
|
37
|
+
return ensureStrictCatalogFields(normalizeServiceTiers(clone));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isExactComboCatalogModel(
|
|
41
|
+
model: CatalogModel | undefined,
|
|
42
|
+
exactComboSlugs: ReadonlySet<string>,
|
|
43
|
+
): boolean {
|
|
44
|
+
return model?.provider === COMBO_NAMESPACE && exactComboSlugs.has(catalogModelSlug(model));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isExactComboCatalogEntry(
|
|
48
|
+
entry: RawEntry,
|
|
49
|
+
exactComboSlugs: ReadonlySet<string>,
|
|
50
|
+
): boolean {
|
|
51
|
+
return entry.owned_by === COMBO_NAMESPACE
|
|
52
|
+
&& typeof entry.slug === "string"
|
|
53
|
+
&& exactComboSlugs.has(entry.slug);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Friendly Codex-picker label for a routed `provider/model` slug. Command Code's two config
|
|
58
|
+
* ids differ by a single dash (`command-code` vs `commandcode`), so relabel them to the
|
|
59
|
+
* lowercase-dash style the opencode presets use: `commandcode-auth/x` and `commandcode-api/x`.
|
|
60
|
+
* The model-id portion also carries a redundant `<vendor>-` prefix (`deepseek-deepseek-v4-flash`)
|
|
61
|
+
* that is dropped for display. Google Antigravity is relabeled to the compact `agy/` prefix for
|
|
62
|
+
* the same reason: `google-antigravity/` alone consumes most of the picker row. That prefix comes
|
|
63
|
+
* from the row's own `providerAlias`, decided once per gather flight; `null` means a cross-provider
|
|
64
|
+
* collision suppressed it and the canonical slug stands. This is the raw-slug path only -- a
|
|
65
|
+
* configured `modelAliases` entry is labeled by the effective-alias path in
|
|
66
|
+
* catalog/provider-fetch.ts (#2960) and keeps the canonical provider name. All other providers
|
|
67
|
+
* keep the raw slug exactly as before.
|
|
68
|
+
*/
|
|
69
|
+
function routedDisplayName(slug: string, model?: CatalogModel, config?: Pick<OcxConfig, "providers">): string {
|
|
70
|
+
const slash = slug.indexOf("/");
|
|
71
|
+
if (slash <= 0) return slug;
|
|
72
|
+
const provider = slug.slice(0, slash);
|
|
73
|
+
let modelId = slug.slice(slash + 1);
|
|
74
|
+
if (provider === "google-antigravity") {
|
|
75
|
+
if (model?.providerAlias === null) return slug;
|
|
76
|
+
const alias = (typeof model?.providerAlias === "string" && model.providerAlias.trim().length > 0)
|
|
77
|
+
? model.providerAlias.trim()
|
|
78
|
+
: effectiveProviderAlias(provider, undefined, config);
|
|
79
|
+
return alias ? `${alias}/${modelId}` : slug;
|
|
80
|
+
}
|
|
81
|
+
if (provider === "command-code" || provider === "commandcode") {
|
|
82
|
+
const m = modelId.match(/^([a-z0-9]+)-([a-z0-9]+(?:-[a-z0-9]+)+)$/i);
|
|
83
|
+
if (m && modelId.startsWith(`${m[1]}-${m[1]}-`)) modelId = modelId.slice(m[1]!.length + 1);
|
|
84
|
+
return `${provider === "command-code" ? "commandcode-auth" : "commandcode-api"}/${modelId}`;
|
|
85
|
+
}
|
|
86
|
+
return slug;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function preservePinnedNativeCustomReasoning(model?: CatalogModel): boolean {
|
|
90
|
+
return model !== undefined
|
|
91
|
+
&& model.catalogKind === CODEX_CUSTOM_MODEL_CATALOG_KIND
|
|
92
|
+
&& hasNativeOpenAiCapabilityMetadata(model.id)
|
|
93
|
+
&& Array.isArray(model.reasoningEfforts);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Cria uma entrada nativa ou roteada a partir do snapshot upstream, de um clone
|
|
98
|
+
* do template ou de campos mínimos. Aplica os metadados e limites pertinentes
|
|
99
|
+
* sem alterar o template nem herdar sua marca de nome ou histórico de prioridade.
|
|
100
|
+
*/
|
|
101
|
+
export function deriveEntry(
|
|
102
|
+
template: RawEntry | null,
|
|
103
|
+
slug: string,
|
|
104
|
+
desc: string,
|
|
105
|
+
priority: number,
|
|
106
|
+
model?: CatalogModel,
|
|
107
|
+
exactComboSlugs: ReadonlySet<string> = new Set(),
|
|
108
|
+
contextCap?: NativeContextLimitsInput,
|
|
109
|
+
): RawEntry {
|
|
110
|
+
const preserveExact = isExactComboCatalogModel(model, exactComboSlugs);
|
|
111
|
+
// Go exposes model-specific upstream enums; synthetic tiers mislead subagent overrides.
|
|
112
|
+
const preserveExactReasoning = preserveExact || model?.provider === "opencode-go";
|
|
113
|
+
const codexForwardNativeCapabilityAlias = model?.codexForwardNativeCapabilityAlias === true
|
|
114
|
+
? upstreamNativeEntry(model.id)
|
|
115
|
+
: null;
|
|
116
|
+
const isRouted = model !== undefined;
|
|
117
|
+
if (!isRouted && !slug.includes("/")) {
|
|
118
|
+
// Supported native slug covered by the upstream snapshot: use the REAL entry (exact
|
|
119
|
+
// reasoning ladder — e.g. luna has no ultra — default effort, identity, model_messages)
|
|
120
|
+
// instead of cloning an older template.
|
|
121
|
+
const upstream = upstreamNativeEntry(slug);
|
|
122
|
+
if (upstream) return finishUpstreamNativeEntry(upstream, priority, contextCap);
|
|
123
|
+
}
|
|
124
|
+
if (template || codexForwardNativeCapabilityAlias) {
|
|
125
|
+
const e = JSON.parse(JSON.stringify(codexForwardNativeCapabilityAlias ?? template)) as RawEntry;
|
|
126
|
+
delete e.opencodex_native_display_name;
|
|
127
|
+
// A cached template may carry display-order history; each new row owns its natural rank.
|
|
128
|
+
delete e[SPAWN_PRIORITY_FIELD];
|
|
129
|
+
e.slug = slug;
|
|
130
|
+
e.display_name = routedDisplayName(slug, model);
|
|
131
|
+
e.description = desc;
|
|
132
|
+
e.priority = priority;
|
|
133
|
+
e.visibility = "list";
|
|
134
|
+
if ("upgrade" in e) e.upgrade = null;
|
|
135
|
+
delete e.availability_nux; // don't replay another model's "now available" NUX
|
|
136
|
+
// Routed (namespaced) models inherit the gpt template — correct its OpenAI/GPT identity
|
|
137
|
+
// and advertise the reasoning ladder Codex accepts.
|
|
138
|
+
if (isRouted) {
|
|
139
|
+
// A routed model is NOT the native template: never inherit its context
|
|
140
|
+
// window when /models omits context metadata (#992). Known metadata
|
|
141
|
+
// restores exact values below; an enabled Context cap fills the gap;
|
|
142
|
+
// otherwise the strict-fields fallback supplies the 128k triple.
|
|
143
|
+
if (!codexForwardNativeCapabilityAlias) {
|
|
144
|
+
delete e.context_window;
|
|
145
|
+
delete e.max_context_window;
|
|
146
|
+
delete e.auto_compact_token_limit;
|
|
147
|
+
}
|
|
148
|
+
// Native id for identity text + metadata lookups — the slug may be an encoded
|
|
149
|
+
// alias (`provider/vendor-model`); the model object carries the native id.
|
|
150
|
+
const modelName = model?.id ?? slug.slice(slug.indexOf("/") + 1);
|
|
151
|
+
if (typeof e.base_instructions === "string") {
|
|
152
|
+
// Proxy-neutral: keep the GPT-5/OpenAI disclaimer but never advertise the opencodex proxy
|
|
153
|
+
// (leaking that into base_instructions is a non-first-party signature → ToS risk).
|
|
154
|
+
e.base_instructions = identifyRoutedModel(e.base_instructions, modelName);
|
|
155
|
+
}
|
|
156
|
+
applyReasoningLevels(
|
|
157
|
+
e,
|
|
158
|
+
model?.reasoningEfforts,
|
|
159
|
+
model?.defaultReasoningEffort,
|
|
160
|
+
preserveExactReasoning
|
|
161
|
+
|| codexForwardNativeCapabilityAlias !== null
|
|
162
|
+
|| preservePinnedNativeCustomReasoning(model),
|
|
163
|
+
);
|
|
164
|
+
// This exact provider/model pair is the ChatGPT/Codex forward surface. Keep the pinned
|
|
165
|
+
// native tool/search/responses-lite contract while preserving the routed slug and wire id.
|
|
166
|
+
if (!codexForwardNativeCapabilityAlias) {
|
|
167
|
+
normalizeRoutedCatalogEntry(e, model?.parallelToolCalls === true, model?.codexToolMode);
|
|
168
|
+
} else if (model?.codexToolMode !== undefined) {
|
|
169
|
+
applyRoutedCodexToolMode(e, model.codexToolMode);
|
|
170
|
+
}
|
|
171
|
+
if (model) applyCatalogMetadata(e, model.provider, model.id, model.contextCap);
|
|
172
|
+
applyCatalogModelMetadata(e, model);
|
|
173
|
+
if (model?.catalogKind) e.opencodex_catalog_kind = model.catalogKind;
|
|
174
|
+
// Additive only. `visibility` is untouched: an inactive row must still be OFFERED, which is
|
|
175
|
+
// the whole point of #1711 — operator disable is what removes rows, and it stays a separate
|
|
176
|
+
// path from this one.
|
|
177
|
+
if (model?.quotaInactiveReason) e[CATALOG_INACTIVE_REASON_FIELD] = model.quotaInactiveReason;
|
|
178
|
+
} else {
|
|
179
|
+
applyNativeOpenAiContextOverride(e, contextCap);
|
|
180
|
+
if (isGpt56NativeSlug(slug)) ensureGpt56ReasoningLevels(e);
|
|
181
|
+
else ensureUltraReasoningLevel(e);
|
|
182
|
+
// Older natives do not support Responses Lite. A newer template must not enable
|
|
183
|
+
// reasoning.context or WebSockets on those models.
|
|
184
|
+
if (!isGpt56NativeSlug(slug)) {
|
|
185
|
+
delete e.use_responses_lite;
|
|
186
|
+
delete e.supports_websockets;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return ensureStrictCatalogFields(normalizeServiceTiers(e), {
|
|
190
|
+
preserveExactInputModalities: preserveExact,
|
|
191
|
+
isRouted,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
// Fallback when no template is available (best-effort; strict parser may need more).
|
|
195
|
+
// Routed fallbacks default to code-mode tool exposure (or shell mode when codexToolMode === "shell");
|
|
196
|
+
// otherwise the nested catalog expands into `exec.description` and can exceed Cursor's 120 KB serialized tool limit (#1830).
|
|
197
|
+
// Cursor still omits hosted web-search metadata because runTurn bypasses that separate sidecar.
|
|
198
|
+
const isCursorFallback = isRouted && model?.provider === "cursor";
|
|
199
|
+
const entry: RawEntry = {
|
|
200
|
+
slug, display_name: routedDisplayName(slug, model), description: desc,
|
|
201
|
+
shell_type: "unified_exec", visibility: "list", supported_in_api: true,
|
|
202
|
+
priority, base_instructions: "You are a helpful coding assistant.",
|
|
203
|
+
...(isRouted
|
|
204
|
+
? isCursorFallback
|
|
205
|
+
? { supports_search_tool: true }
|
|
206
|
+
: { web_search_tool_type: "text_and_image", supports_search_tool: true }
|
|
207
|
+
: {}),
|
|
208
|
+
};
|
|
209
|
+
if (isRouted) {
|
|
210
|
+
applyRoutedCodexToolMode(entry, model?.codexToolMode);
|
|
211
|
+
applyReasoningLevels(entry, model?.reasoningEfforts, model?.defaultReasoningEffort, preserveExactReasoning || preservePinnedNativeCustomReasoning(model));
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
applyReasoningLevels(entry, isGpt56NativeSlug(slug) ? undefined : ["low", "medium", "high", "xhigh"]);
|
|
215
|
+
if (isGpt56NativeSlug(slug)) ensureGpt56ReasoningLevels(entry);
|
|
216
|
+
}
|
|
217
|
+
if (model && isRouted) applyCatalogMetadata(entry, model.provider, model.id, model.contextCap);
|
|
218
|
+
applyCatalogModelMetadata(entry, model);
|
|
219
|
+
if (model?.catalogKind) entry.opencodex_catalog_kind = model.catalogKind;
|
|
220
|
+
// Same additive stamp as the templated path above. A routed row that reaches the no-template
|
|
221
|
+
// fallback is still a served row, so omitting it here would make the field depend on whether a
|
|
222
|
+
// template happened to be cached — which is exactly what the regression test caught.
|
|
223
|
+
if (model?.quotaInactiveReason) entry[CATALOG_INACTIVE_REASON_FIELD] = model.quotaInactiveReason;
|
|
224
|
+
if (!isRouted) applyNativeOpenAiContextOverride(entry, contextCap);
|
|
225
|
+
return ensureStrictCatalogFields(normalizeServiceTiers(entry), {
|
|
226
|
+
preserveExactInputModalities: preserveExact,
|
|
227
|
+
isRouted,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
@@ -39,7 +39,6 @@ import { nativeOpenAiCapabilitySourceSlug, SELF_DESCRIBED_NATIVE_OPENAI_MODELS,
|
|
|
39
39
|
import { isReserveCatalogProjection } from "./reserve";
|
|
40
40
|
import { loadBundledCodexCatalog } from "./bundled";
|
|
41
41
|
import type { BundledCatalogDeps, ReadonlyRawCatalog } from "./bundled";
|
|
42
|
-
import { deriveEntry } from "./sync";
|
|
43
42
|
import {
|
|
44
43
|
formatClampLogLines,
|
|
45
44
|
formatRuntimeLogLine,
|