@bitkyc08/opencodex 2.11.0 → 2.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/{index-Bk-PN-70.css → index-BA1dgo4q.css} +1 -1
- package/gui/dist/assets/index-DDZpgzKk.js +70 -0
- package/gui/dist/index.html +2 -2
- package/package.json +6 -4
- package/src/adapters/anthropic.ts +10 -2
- package/src/adapters/cursor/native-exec-common.ts +6 -2
- package/src/adapters/google-antigravity-replay.ts +3 -2
- package/src/adapters/google-antigravity-wire.ts +38 -6
- package/src/adapters/google.ts +57 -10
- package/src/adapters/openai-chat.ts +19 -2
- package/src/claude/model-info.ts +1 -1
- package/src/cli/account-auth.ts +3 -1
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +15 -1
- package/src/cli/codex-shim-readiness.ts +69 -0
- package/src/cli/combo.ts +5 -0
- package/src/cli/config-command.ts +33 -4
- package/src/cli/doctor.ts +21 -0
- package/src/cli/export-command.ts +6 -6
- package/src/cli/help.ts +6 -6
- package/src/cli/index.ts +22 -5
- package/src/cli/models-runtime.ts +13 -1
- package/src/cli/provider.ts +7 -0
- package/src/clients/config-export.ts +67 -5
- package/src/codex/account-lifecycle.ts +99 -10
- package/src/codex/auth-api.ts +222 -34
- package/src/codex/catalog/account-models.ts +9 -4
- package/src/codex/catalog/aggregation.ts +41 -8
- package/src/codex/catalog/bundled.ts +54 -22
- package/src/codex/catalog/effort.ts +47 -20
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +70 -18
- package/src/codex/catalog/native-models.ts +7 -0
- package/src/codex/catalog/parsing.ts +29 -11
- package/src/codex/catalog/provider-fetch.ts +335 -50
- package/src/codex/catalog/sync.ts +588 -126
- package/src/codex/catalog-refresh-status.ts +87 -0
- package/src/codex/catalog-write-serialization.ts +2 -1
- package/src/codex/catalog.ts +4 -3
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +190 -52
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/features.ts +79 -1
- package/src/codex/history-job.ts +81 -1
- package/src/codex/history-lock.ts +2 -1
- package/src/codex/history-provider.ts +4 -3
- package/src/codex/inject.ts +56 -12
- package/src/codex/model-cache.ts +50 -10
- package/src/codex/transition-state.ts +10 -2
- package/src/codex/user-identity.ts +110 -2
- package/src/combos/index.ts +3 -0
- package/src/combos/types.ts +75 -9
- package/src/config.ts +26 -12
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +12 -10
- package/src/integrations/registry.ts +7 -0
- package/src/integrations/writer.ts +1 -1
- package/src/lib/pinned-http.ts +40 -9
- package/src/lib/process-control.ts +4 -1
- package/src/lib/provider-outbound.ts +42 -9
- package/src/oauth/cursor.ts +25 -5
- package/src/oauth/index.ts +59 -45
- package/src/providers/antigravity-models.ts +115 -3
- package/src/providers/context-cap.ts +13 -5
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +14 -15
- package/src/providers/provider-id-rewrite.ts +29 -0
- package/src/providers/quota.ts +93 -19
- package/src/providers/registry.ts +16 -5
- package/src/providers/slug-codec.ts +13 -6
- package/src/reasoning-effort.ts +22 -0
- package/src/router.ts +0 -20
- package/src/routing/history/indexer.ts +54 -39
- package/src/routing/quota.ts +77 -56
- package/src/server/index.ts +32 -7
- package/src/server/management/combo-routes.ts +43 -19
- package/src/server/management/config-routes.ts +115 -20
- package/src/server/management/model-routes.ts +10 -7
- package/src/server/management/model-rows.ts +6 -2
- package/src/server/management/oauth-account-routes.ts +12 -0
- package/src/server/management/provider-routes.ts +114 -40
- package/src/server/management/routing-profile-routes.ts +0 -22
- package/src/server/management-api.ts +7 -25
- package/src/server/responses/core.ts +3 -3
- package/src/server/responses/policy-fallback.ts +152 -0
- package/src/server/responses.ts +3 -2
- package/src/types.ts +15 -2
- package/src/usage/cost.ts +0 -0
- package/src/vision/describe.ts +3 -1
- package/src/vision/index.ts +17 -6
- package/src/vision/reasoning.ts +55 -0
- package/src/web-search/parse.ts +15 -3
- package/gui/dist/assets/index-BynIEIV-.js +0 -70
|
@@ -7,10 +7,12 @@ import { CODEX_CONFIG_PATH, CODEX_MODELS_CACHE_PATH, DEFAULT_CATALOG_PATH, readR
|
|
|
7
7
|
import {
|
|
8
8
|
clearModelCache,
|
|
9
9
|
clearProviderDiscoveryStatus,
|
|
10
|
+
captureModelCacheGeneration,
|
|
10
11
|
DEFAULT_MODEL_CACHE_TTL_MS,
|
|
11
12
|
getFreshCached,
|
|
12
13
|
getStaleCached,
|
|
13
14
|
isModelsFetchCoolingDown,
|
|
15
|
+
isModelCacheGenerationCurrent,
|
|
14
16
|
markModelsFetchFailure,
|
|
15
17
|
markProviderDiscoveryFailed,
|
|
16
18
|
markProviderDiscoveryOk,
|
|
@@ -20,6 +22,7 @@ import {
|
|
|
20
22
|
} from "../model-cache";
|
|
21
23
|
import {
|
|
22
24
|
buildModelsRequest,
|
|
25
|
+
getValidAccessTokenSnapshot,
|
|
23
26
|
observeActiveOAuthAccessToken,
|
|
24
27
|
resolveModelsAuthToken,
|
|
25
28
|
type OAuthActiveTokenObservation,
|
|
@@ -27,9 +30,10 @@ import {
|
|
|
27
30
|
import type { OcxConfig, OcxProviderConfig } from "../../types";
|
|
28
31
|
import { modelInList } from "../../types";
|
|
29
32
|
import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
|
|
30
|
-
import {
|
|
33
|
+
import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, resolveMetadataProvider } from "../../generated/model-metadata";
|
|
31
34
|
import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
|
|
32
|
-
import { getProviderRegistryEntry, providerMatchesRegistryTransport } from "../../providers/registry";
|
|
35
|
+
import { effectiveGoogleMode, getProviderRegistryEntry, providerMatchesRegistryTransport } from "../../providers/registry";
|
|
36
|
+
import { parseAntigravityAvailableModels } from "../../providers/antigravity-models";
|
|
33
37
|
import { applyProviderContextCap, providerContextCap } from "../../providers/context-cap";
|
|
34
38
|
import { routedSlug, slugEquals, slugsEquivalent } from "../../providers/slug-codec";
|
|
35
39
|
import { CODEX_GPT5_IDENTITY_LINE } from "../../adapters/identity";
|
|
@@ -47,6 +51,7 @@ import type { NormalizedComboConfig } from "../../combos/types";
|
|
|
47
51
|
import {
|
|
48
52
|
ProviderOutboundPolicyError,
|
|
49
53
|
providerOutboundGet,
|
|
54
|
+
providerOutboundPost,
|
|
50
55
|
providerRedirectError,
|
|
51
56
|
} from "../../lib/provider-outbound";
|
|
52
57
|
import { redactSecretString } from "../../lib/redact";
|
|
@@ -62,9 +67,9 @@ import upstreamModelsSnapshot from "../data/upstream-models.json";
|
|
|
62
67
|
import { createAdmissionGate, ResourceAdmissionError, type AdmissionMetrics } from "../../lib/admission";
|
|
63
68
|
|
|
64
69
|
|
|
65
|
-
import { JAWCODE_CATALOG_AUGMENT_PROVIDERS, catalogModelSlug, shouldExposeRoutedModel } from "./parsing";
|
|
70
|
+
import { CODEX_CUSTOM_MODEL_CATALOG_KIND, JAWCODE_CATALOG_AUGMENT_PROVIDERS, catalogModelSlug, shouldExposeRoutedModel } from "./parsing";
|
|
66
71
|
import type { CatalogModel } from "./parsing";
|
|
67
|
-
import { disabledNativeSlugs, hasComboTargets, nativeInputModalities, nativeOpenAiContextWindow, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
|
|
72
|
+
import { disabledNativeSlugs, hasComboTargets, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiContextWindow, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
|
|
68
73
|
import { deriveComboCatalogModel, normalizedOpenAiApiSignature, openAiApiCollisionWarnings, replaceLastComboCatalogOmissions, warnUncataloguedComboOnce } from "./aggregation";
|
|
69
74
|
import type { ComboCatalogOmission } from "./aggregation";
|
|
70
75
|
import type { CatalogGatherProviderAuthEvidence } from "./filesystem-evidence";
|
|
@@ -87,9 +92,16 @@ export interface CatalogGatherProviderAuthOutcome {
|
|
|
87
92
|
readonly state: OAuthActiveTokenObservation["kind"];
|
|
88
93
|
}
|
|
89
94
|
|
|
95
|
+
export interface CatalogGatherProviderModelOutcome {
|
|
96
|
+
readonly provider: string;
|
|
97
|
+
readonly state: "authoritative" | "degraded";
|
|
98
|
+
}
|
|
99
|
+
|
|
90
100
|
export interface GatherRoutedModelsOptions {
|
|
91
101
|
comboOmissions?: ComboCatalogOmission[];
|
|
92
102
|
providerAuthOutcomes?: CatalogGatherProviderAuthOutcome[];
|
|
103
|
+
/** Flight-local authority of each provider's returned model rows. */
|
|
104
|
+
providerModelOutcomes?: CatalogGatherProviderModelOutcome[];
|
|
93
105
|
/** Internal convergence sink for the immutable policy that produced the returned rows. */
|
|
94
106
|
discoveryPolicySnapshots?: CatalogProviderDiscoveryPolicySnapshot[];
|
|
95
107
|
}
|
|
@@ -98,13 +110,20 @@ interface GatherFlightResult {
|
|
|
98
110
|
models: CatalogModel[];
|
|
99
111
|
comboOmissions: ComboCatalogOmission[];
|
|
100
112
|
providerAuthOutcomes: readonly CatalogGatherProviderAuthOutcome[];
|
|
113
|
+
providerModelOutcomes: readonly CatalogGatherProviderModelOutcome[];
|
|
101
114
|
discoveryPolicySnapshots: readonly CatalogProviderDiscoveryPolicySnapshot[];
|
|
102
115
|
}
|
|
103
116
|
|
|
117
|
+
interface ProviderModelsResult {
|
|
118
|
+
readonly models: CatalogModel[];
|
|
119
|
+
readonly outcome: CatalogGatherProviderModelOutcome;
|
|
120
|
+
}
|
|
121
|
+
|
|
104
122
|
interface ModelsAuthResolution {
|
|
105
123
|
readonly apiKey: string | undefined;
|
|
106
124
|
readonly observed: boolean;
|
|
107
125
|
readonly oauthApiBaseUrl?: string;
|
|
126
|
+
readonly oauthProjectId?: string;
|
|
108
127
|
}
|
|
109
128
|
|
|
110
129
|
type ModelsAuthResolver =
|
|
@@ -119,7 +138,7 @@ type ModelsAuthResolverFactory = (
|
|
|
119
138
|
) => ModelsAuthResolver;
|
|
120
139
|
|
|
121
140
|
interface CapturedModelsRequest {
|
|
122
|
-
readonly method: "GET";
|
|
141
|
+
readonly method: "GET" | "POST";
|
|
123
142
|
readonly url: string;
|
|
124
143
|
readonly headersWithoutCredential: Readonly<Record<string, string>>;
|
|
125
144
|
readonly headersWithCredential: Readonly<Record<string, string>>;
|
|
@@ -346,11 +365,12 @@ function captureModelsRequest(
|
|
|
346
365
|
: undefined;
|
|
347
366
|
const withoutCredential = buildModelsRequest(provider, undefined, name, observed);
|
|
348
367
|
const withCredential = buildModelsRequest(provider, REQUEST_CREDENTIAL_SENTINEL, name, observed);
|
|
349
|
-
|
|
368
|
+
const method = withoutCredential.method ?? "GET";
|
|
369
|
+
if (withoutCredential.url !== withCredential.url || method !== (withCredential.method ?? "GET")) {
|
|
350
370
|
throw new TypeError(`Provider model discovery URL for ${name} depends on credential bytes.`);
|
|
351
371
|
}
|
|
352
372
|
return detachedFrozen({
|
|
353
|
-
method
|
|
373
|
+
method,
|
|
354
374
|
url: withoutCredential.url,
|
|
355
375
|
headersWithoutCredential: withoutCredential.headers,
|
|
356
376
|
headersWithCredential: withCredential.headers,
|
|
@@ -607,6 +627,141 @@ export function applyConfigHintsToCachedModels(name: string, prov: OcxProviderCo
|
|
|
607
627
|
return models.map(model => applyProviderConfigHints(name, prov, model, contextCap));
|
|
608
628
|
}
|
|
609
629
|
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Last-resort context window for combo member synthesis when discovery and
|
|
633
|
+
* provider config both omit one. Matches the catalog entry default in
|
|
634
|
+
* `normalizeRoutedCatalogEntry` so incomplete live rows still catalog.
|
|
635
|
+
*/
|
|
636
|
+
const COMBO_MEMBER_CONTEXT_FALLBACK = 128_000;
|
|
637
|
+
|
|
638
|
+
interface ComboCatalogMemberFallback {
|
|
639
|
+
readonly contextWindow?: number;
|
|
640
|
+
readonly inputModalities?: readonly string[];
|
|
641
|
+
readonly reasoningEfforts?: readonly string[];
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Resolve a combo target to a catalog member for derivation.
|
|
646
|
+
* Prefer discovery metadata; when the target is missing from the gather map or
|
|
647
|
+
* lacks a positive contextWindow, synthesize from the (registry-enriched)
|
|
648
|
+
* provider config so combos remain catalogued when targets are configured but
|
|
649
|
+
* discovery metadata is incomplete. Disabled providers stay unresolved.
|
|
650
|
+
* When hints still omit contextWindow, prefer known maxInputTokens, else
|
|
651
|
+
* COMBO_MEMBER_CONTEXT_FALLBACK so a live row without ctx does not drop the
|
|
652
|
+
* whole combo from the public catalog.
|
|
653
|
+
*/
|
|
654
|
+
export function resolveComboCatalogMember(
|
|
655
|
+
target: { provider: string; model: string },
|
|
656
|
+
memberByKey: ReadonlyMap<string, CatalogModel>,
|
|
657
|
+
providers: ReadonlyMap<string, OcxProviderConfig>,
|
|
658
|
+
contextCap?: number,
|
|
659
|
+
fallback?: ComboCatalogMemberFallback,
|
|
660
|
+
): CatalogModel | undefined {
|
|
661
|
+
const existing = memberByKey.get(targetKey(target));
|
|
662
|
+
const prov = providers.get(target.provider);
|
|
663
|
+
// Disabled providers never contribute members — even a complete discovery row
|
|
664
|
+
// is unusable for catalog derivation while the provider is off.
|
|
665
|
+
if (prov?.disabled === true) return undefined;
|
|
666
|
+
|
|
667
|
+
const withFallbackMetadata = (member: CatalogModel): CatalogModel => {
|
|
668
|
+
if (!fallback) return member;
|
|
669
|
+
const contextWindow = typeof member.contextWindow === "number" && member.contextWindow > 0
|
|
670
|
+
? member.contextWindow
|
|
671
|
+
: undefined;
|
|
672
|
+
const addMaxInput = contextWindow !== undefined
|
|
673
|
+
&& !(typeof member.maxInputTokens === "number" && member.maxInputTokens > 0);
|
|
674
|
+
const addModalities = (!Array.isArray(member.inputModalities) || member.inputModalities.length === 0)
|
|
675
|
+
&& fallback.inputModalities !== undefined;
|
|
676
|
+
const addReasoning = member.reasoningEfforts === undefined
|
|
677
|
+
&& fallback.reasoningEfforts !== undefined;
|
|
678
|
+
if (!addMaxInput && !addModalities && !addReasoning) return member;
|
|
679
|
+
return {
|
|
680
|
+
...member,
|
|
681
|
+
...(addMaxInput ? { maxInputTokens: contextWindow } : {}),
|
|
682
|
+
...(addModalities ? { inputModalities: [...fallback.inputModalities!] } : {}),
|
|
683
|
+
...(addReasoning ? { reasoningEfforts: [...fallback.reasoningEfforts!] } : {}),
|
|
684
|
+
};
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
// Complete live/configured rows still honour providerContextCaps so a high
|
|
688
|
+
// discovery window cannot outrun an operator-configured cap. Native-alias
|
|
689
|
+
// fallback metadata may fill only capability gaps; it never raises an explicit
|
|
690
|
+
// discovered/configured context window.
|
|
691
|
+
if (
|
|
692
|
+
existing
|
|
693
|
+
&& typeof existing.contextWindow === "number"
|
|
694
|
+
&& existing.contextWindow > 0
|
|
695
|
+
) {
|
|
696
|
+
const capped = applyProviderContextCap(existing.contextWindow, contextCap);
|
|
697
|
+
if (capped === undefined || capped === existing.contextWindow) {
|
|
698
|
+
return withFallbackMetadata(existing);
|
|
699
|
+
}
|
|
700
|
+
const maxInput = typeof existing.maxInputTokens === "number" && existing.maxInputTokens > 0
|
|
701
|
+
? Math.min(existing.maxInputTokens, capped)
|
|
702
|
+
: capped;
|
|
703
|
+
return withFallbackMetadata({
|
|
704
|
+
...existing,
|
|
705
|
+
contextWindow: capped,
|
|
706
|
+
maxInputTokens: maxInput,
|
|
707
|
+
contextCap,
|
|
708
|
+
contextCapped: true as const,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const base: CatalogModel = existing ?? {
|
|
713
|
+
id: target.model,
|
|
714
|
+
provider: target.provider,
|
|
715
|
+
};
|
|
716
|
+
const hinted = prov
|
|
717
|
+
? applyProviderConfigHints(target.provider, prov, base, contextCap)
|
|
718
|
+
: base;
|
|
719
|
+
const hintedContext = typeof hinted.contextWindow === "number" && hinted.contextWindow > 0
|
|
720
|
+
? hinted.contextWindow
|
|
721
|
+
: undefined;
|
|
722
|
+
const knownMaxInput = typeof hinted.maxInputTokens === "number" && hinted.maxInputTokens > 0
|
|
723
|
+
? hinted.maxInputTokens
|
|
724
|
+
: (typeof base.maxInputTokens === "number" && base.maxInputTokens > 0
|
|
725
|
+
? base.maxInputTokens
|
|
726
|
+
: undefined);
|
|
727
|
+
// Real discovery/config values win. A native alias is the next fallback tier.
|
|
728
|
+
// The generic 128k/text synthesis from #1305 remains the final fallback.
|
|
729
|
+
const fallbackContext = existing || prov ? fallback?.contextWindow : undefined;
|
|
730
|
+
const uncappedContext = hintedContext
|
|
731
|
+
?? knownMaxInput
|
|
732
|
+
?? fallbackContext
|
|
733
|
+
?? (existing || prov ? COMBO_MEMBER_CONTEXT_FALLBACK : undefined);
|
|
734
|
+
if (uncappedContext === undefined) return undefined;
|
|
735
|
+
const usedFallback = hintedContext === undefined;
|
|
736
|
+
const cappedContext = applyProviderContextCap(uncappedContext, contextCap);
|
|
737
|
+
const contextWindow = cappedContext ?? uncappedContext;
|
|
738
|
+
const fallbackCapped = usedFallback
|
|
739
|
+
&& contextCap !== undefined
|
|
740
|
+
&& cappedContext !== undefined
|
|
741
|
+
&& cappedContext !== uncappedContext;
|
|
742
|
+
|
|
743
|
+
const inputModalities = hinted.inputModalities
|
|
744
|
+
?? base.inputModalities
|
|
745
|
+
?? (fallback?.inputModalities ? [...fallback.inputModalities] : undefined)
|
|
746
|
+
?? ["text"];
|
|
747
|
+
const reasoningEfforts = hinted.reasoningEfforts
|
|
748
|
+
?? (prov ? configuredReasoningEfforts(prov, target.model) : undefined)
|
|
749
|
+
?? base.reasoningEfforts
|
|
750
|
+
?? (fallback?.reasoningEfforts ? [...fallback.reasoningEfforts] : undefined);
|
|
751
|
+
const maxInputTokens = knownMaxInput !== undefined
|
|
752
|
+
? Math.min(knownMaxInput, contextWindow)
|
|
753
|
+
: contextWindow;
|
|
754
|
+
|
|
755
|
+
return {
|
|
756
|
+
...hinted,
|
|
757
|
+
inputModalities,
|
|
758
|
+
...(reasoningEfforts !== undefined ? { reasoningEfforts } : {}),
|
|
759
|
+
contextWindow,
|
|
760
|
+
maxInputTokens,
|
|
761
|
+
...(fallbackCapped ? { contextCap, contextCapped: true as const } : {}),
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
|
|
610
765
|
export function isDatedVariantId(liveId: string, configuredId: string): boolean {
|
|
611
766
|
if (!liveId.startsWith(`${configuredId}-`)) return false;
|
|
612
767
|
return /^\d{8}$/.test(liveId.slice(configuredId.length + 1));
|
|
@@ -817,6 +972,7 @@ function observedModelsAuthResolver(
|
|
|
817
972
|
apiKey: observation.snapshot.accessToken,
|
|
818
973
|
observed: true,
|
|
819
974
|
...(observation.snapshot.apiBaseUrl ? { oauthApiBaseUrl: observation.snapshot.apiBaseUrl } : {}),
|
|
975
|
+
...(observation.snapshot.projectId ? { oauthProjectId: observation.snapshot.projectId } : {}),
|
|
820
976
|
};
|
|
821
977
|
},
|
|
822
978
|
};
|
|
@@ -827,9 +983,17 @@ async function fetchProviderModelsWithAuth(
|
|
|
827
983
|
ttlMs: number,
|
|
828
984
|
contextCap: number | undefined,
|
|
829
985
|
resolveAuth: ModelsAuthResolver,
|
|
830
|
-
): Promise<
|
|
986
|
+
): Promise<ProviderModelsResult> {
|
|
831
987
|
const { name, provider: prov, discovery, request } = captured;
|
|
832
|
-
|
|
988
|
+
const observed = (
|
|
989
|
+
models: CatalogModel[],
|
|
990
|
+
state: CatalogGatherProviderModelOutcome["state"],
|
|
991
|
+
): ProviderModelsResult => ({ models, outcome: { provider: name, state } });
|
|
992
|
+
// Capture before any credential refresh or outbound await. OAuth account changes clear this
|
|
993
|
+
// generation, so a request started with the former account cannot later publish its result.
|
|
994
|
+
const cacheGeneration = captureModelCacheGeneration(name);
|
|
995
|
+
const isCurrentCacheGeneration = () => isModelCacheGenerationCurrent(name, cacheGeneration);
|
|
996
|
+
if (prov.authMode === "forward") return observed([], "authoritative"); // ChatGPT backend has no /models
|
|
833
997
|
const seedVertexDefault = prov.adapter === "google"
|
|
834
998
|
&& prov.googleMode === "vertex"
|
|
835
999
|
&& (prov.models?.length ?? 0) === 0
|
|
@@ -844,10 +1008,18 @@ async function fetchProviderModelsWithAuth(
|
|
|
844
1008
|
// discovery failure left by an older live configuration even when the account is logged out.
|
|
845
1009
|
if (prov.liveModels === false) {
|
|
846
1010
|
clearProviderDiscoveryStatus(name);
|
|
847
|
-
return configured;
|
|
1011
|
+
return observed(configured, "authoritative");
|
|
848
1012
|
}
|
|
849
1013
|
const auth: ModelsAuthResolution = captured.observedAuth ?? (resolveAuth.kind === "refreshing"
|
|
850
|
-
?
|
|
1014
|
+
? prov.authMode === "oauth" && effectiveGoogleMode(name, prov) === "cloud-code-assist"
|
|
1015
|
+
? await getValidAccessTokenSnapshot(name)
|
|
1016
|
+
.then(snapshot => ({
|
|
1017
|
+
apiKey: snapshot.accessToken,
|
|
1018
|
+
observed: false,
|
|
1019
|
+
...(snapshot.projectId ? { oauthProjectId: snapshot.projectId } : {}),
|
|
1020
|
+
}))
|
|
1021
|
+
.catch(() => ({ apiKey: undefined, observed: false }))
|
|
1022
|
+
: { apiKey: await resolveModelsAuthToken(name, prov), observed: false }
|
|
851
1023
|
: resolveAuth.resolve(name, prov));
|
|
852
1024
|
const apiKey = auth.apiKey;
|
|
853
1025
|
// A configured default is a real callable selector and must remain discoverable when a
|
|
@@ -868,47 +1040,70 @@ async function fetchProviderModelsWithAuth(
|
|
|
868
1040
|
: models
|
|
869
1041
|
);
|
|
870
1042
|
if (prov.adapter === "cursor") {
|
|
871
|
-
if (!apiKey) return configured;
|
|
1043
|
+
if (!apiKey) return observed(configured, "degraded");
|
|
872
1044
|
// Cursor uses a bespoke GetUsableModels RPC (not /models), returning the full effort-suffixed
|
|
873
1045
|
// variants this PLAN can use. Keep the base-model UX (the request builder appends the effort
|
|
874
1046
|
// suffix) but filter the static seed to the bases the account actually has — so models not on the
|
|
875
1047
|
// plan (e.g. claude-fable-5) drop out instead of failing ERROR_BAD_MODEL_NAME. Fall back to the seed.
|
|
876
1048
|
const cachedCursor = getFreshCached(name, ttlMs);
|
|
877
|
-
if (cachedCursor)
|
|
1049
|
+
if (cachedCursor) {
|
|
1050
|
+
return observed(applyConfigHintsToCachedModels(name, prov, cachedCursor), "authoritative");
|
|
1051
|
+
}
|
|
878
1052
|
if (isModelsFetchCoolingDown(name)) {
|
|
879
1053
|
const cooling = getStaleCached(name);
|
|
880
|
-
return
|
|
1054
|
+
return observed(
|
|
1055
|
+
cooling ? applyConfigHintsToCachedModels(name, prov, cooling) : configured,
|
|
1056
|
+
"degraded",
|
|
1057
|
+
);
|
|
881
1058
|
}
|
|
882
1059
|
const liveResult = await fetchCursorUsableModels({ apiKey, baseUrl: prov.baseUrl });
|
|
883
1060
|
if (liveResult.ok) {
|
|
884
1061
|
const available = filterCursorConfiguredModelsByLiveDiscovery(configured, liveResult.models);
|
|
885
1062
|
const result = available.length > 0 ? available : configured;
|
|
886
1063
|
// Count what discovery actually returned, not the configured rows we fall back to.
|
|
1064
|
+
if (!setCached(name, result, Date.now(), cacheGeneration)) return observed(configured, "degraded");
|
|
887
1065
|
markProviderDiscoveryOk(name, liveResult.models.length);
|
|
888
|
-
|
|
889
|
-
|
|
1066
|
+
return observed(result, "authoritative");
|
|
1067
|
+
}
|
|
1068
|
+
if (isCurrentCacheGeneration()) {
|
|
1069
|
+
markModelsFetchFailure(name);
|
|
1070
|
+
markProviderDiscoveryFailed(name, { reason: "provider" });
|
|
1071
|
+
console.warn(
|
|
1072
|
+
`[opencodex] Cursor model discovery for "${name}" failed [${liveResult.error}]${liveResult.detail ? `: ${liveResult.detail}` : ""}; using stale/static catalog degradation.`,
|
|
1073
|
+
);
|
|
890
1074
|
}
|
|
891
|
-
markModelsFetchFailure(name);
|
|
892
|
-
markProviderDiscoveryFailed(name, { reason: "provider" });
|
|
893
|
-
console.warn(
|
|
894
|
-
`[opencodex] Cursor model discovery for "${name}" failed [${liveResult.error}]${liveResult.detail ? `: ${liveResult.detail}` : ""}; using stale/static catalog degradation.`,
|
|
895
|
-
);
|
|
896
1075
|
const staleCursor = getStaleCached(name);
|
|
897
|
-
return
|
|
1076
|
+
return observed(
|
|
1077
|
+
staleCursor ? applyConfigHintsToCachedModels(name, prov, staleCursor) : configured,
|
|
1078
|
+
"degraded",
|
|
1079
|
+
);
|
|
898
1080
|
}
|
|
899
1081
|
if (prov.authMode === "oauth" && !apiKey) {
|
|
900
1082
|
// No usable token (logged out, or account marked needsReauth). Still surface the
|
|
901
1083
|
// configured static catalog so the GUI Models tab / rail counts are not empty —
|
|
902
1084
|
// matching Cursor's !apiKey → configured degradation and fetch-failure fallback.
|
|
903
|
-
return configured;
|
|
1085
|
+
return observed(configured, "degraded");
|
|
904
1086
|
}
|
|
1087
|
+
const cloudCodeAssist = effectiveGoogleMode(name, prov) === "cloud-code-assist";
|
|
1088
|
+
const project = prov.project ?? auth.oauthProjectId;
|
|
1089
|
+
if (cloudCodeAssist && !project) return observed(configured, "degraded");
|
|
905
1090
|
const fresh = getFreshCached(name, ttlMs);
|
|
906
|
-
if (fresh)
|
|
1091
|
+
if (fresh) {
|
|
1092
|
+
return observed(
|
|
1093
|
+
withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, fresh, contextCap)),
|
|
1094
|
+
"authoritative",
|
|
1095
|
+
); // dedups Codex's frequent /v1/models polling within the TTL
|
|
1096
|
+
}
|
|
907
1097
|
if (isModelsFetchCoolingDown(name)) {
|
|
908
1098
|
// A recently-failed provider (unreachable API, missing proxy, bad key) must not re-pay the
|
|
909
1099
|
// fetch timeout on every catalog poll — the dashboard polls this path per page load.
|
|
910
1100
|
const stale = getStaleCached(name);
|
|
911
|
-
return
|
|
1101
|
+
return observed(
|
|
1102
|
+
stale
|
|
1103
|
+
? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap))
|
|
1104
|
+
: failedDiscoveryConfigured,
|
|
1105
|
+
"degraded",
|
|
1106
|
+
);
|
|
912
1107
|
}
|
|
913
1108
|
const url = request.url;
|
|
914
1109
|
const headers = materializeCapturedHeaders(request, apiKey);
|
|
@@ -918,6 +1113,9 @@ async function fetchProviderModelsWithAuth(
|
|
|
918
1113
|
const failedDiscoveryFallback = (
|
|
919
1114
|
failure: ProviderModelDiscoveryFailure,
|
|
920
1115
|
): { models: CatalogModel[]; fallback: "stale" | "configured"; shouldLog: boolean } => {
|
|
1116
|
+
if (!isCurrentCacheGeneration()) {
|
|
1117
|
+
return { models: failedDiscoveryConfigured, fallback: "configured", shouldLog: false };
|
|
1118
|
+
}
|
|
921
1119
|
// Decide logging BEFORE recording the new status, so we can compare against the prior one and
|
|
922
1120
|
// suppress an identical repeated failure (#395 log flood). The failure stays observable via the
|
|
923
1121
|
// discovery-status API regardless.
|
|
@@ -934,10 +1132,16 @@ async function fetchProviderModelsWithAuth(
|
|
|
934
1132
|
};
|
|
935
1133
|
};
|
|
936
1134
|
try {
|
|
937
|
-
const res =
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1135
|
+
const res = request.method === "POST"
|
|
1136
|
+
? await providerOutboundPost(name, prov, url, {
|
|
1137
|
+
headers,
|
|
1138
|
+
body: JSON.stringify({ project }),
|
|
1139
|
+
signal: AbortSignal.timeout(8000),
|
|
1140
|
+
})
|
|
1141
|
+
: await providerOutboundGet(name, prov, url, {
|
|
1142
|
+
headers,
|
|
1143
|
+
signal: AbortSignal.timeout(8000),
|
|
1144
|
+
});
|
|
941
1145
|
const redirectError = await providerRedirectError(res, url);
|
|
942
1146
|
if (redirectError) {
|
|
943
1147
|
const { models, fallback, shouldLog } = failedDiscoveryFallback({ reason: "http", httpStatus: res.status });
|
|
@@ -946,7 +1150,7 @@ async function fetchProviderModelsWithAuth(
|
|
|
946
1150
|
`[opencodex] Provider model discovery for "${name}" ${redirectError} [urlClass=${urlClass}, fallback=${fallback}].`,
|
|
947
1151
|
);
|
|
948
1152
|
}
|
|
949
|
-
return models;
|
|
1153
|
+
return observed(models, "degraded");
|
|
950
1154
|
}
|
|
951
1155
|
if (!res.ok) {
|
|
952
1156
|
const { models, fallback, shouldLog } = failedDiscoveryFallback({ reason: "http", httpStatus: res.status });
|
|
@@ -955,7 +1159,7 @@ async function fetchProviderModelsWithAuth(
|
|
|
955
1159
|
`[opencodex] Provider model discovery for "${name}" failed with HTTP ${res.status} [urlClass=${urlClass}, fallback=${fallback}].`,
|
|
956
1160
|
);
|
|
957
1161
|
}
|
|
958
|
-
return models;
|
|
1162
|
+
return observed(models, "degraded");
|
|
959
1163
|
}
|
|
960
1164
|
|
|
961
1165
|
const contentType = (
|
|
@@ -974,7 +1178,33 @@ async function fetchProviderModelsWithAuth(
|
|
|
974
1178
|
`[opencodex] Provider model discovery for "${name}" ${diagnostic} [status=${res.status}, contentType=${contentType}, urlClass=${urlClass}, fallback=${fallback}].`,
|
|
975
1179
|
);
|
|
976
1180
|
}
|
|
977
|
-
return models;
|
|
1181
|
+
return observed(models, "degraded");
|
|
1182
|
+
}
|
|
1183
|
+
const antigravity = cloudCodeAssist
|
|
1184
|
+
? parseAntigravityAvailableModels(bounded.value, discovery.maxModels)
|
|
1185
|
+
: undefined;
|
|
1186
|
+
if (cloudCodeAssist && !antigravity) {
|
|
1187
|
+
const { models, fallback, shouldLog } = failedDiscoveryFallback({ reason: "invalid_response" });
|
|
1188
|
+
if (shouldLog) {
|
|
1189
|
+
console.warn(
|
|
1190
|
+
`[opencodex] Provider model discovery for "${name}" returned malformed CCA model data [status=${res.status}, contentType=${contentType}, urlClass=${urlClass}, fallback=${fallback}].`,
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
return observed(models, "degraded");
|
|
1194
|
+
}
|
|
1195
|
+
if (antigravity) {
|
|
1196
|
+
const live = antigravity.map(model => applyProviderConfigHints(name, prov, {
|
|
1197
|
+
id: model.id,
|
|
1198
|
+
provider: name,
|
|
1199
|
+
// CCA only exposes a numeric thinking budget. Until the adapter owns an exact Codex
|
|
1200
|
+
// effort-to-wire mapping for a newly discovered model, do not advertise a false ladder.
|
|
1201
|
+
reasoningEfforts: [],
|
|
1202
|
+
...(model.contextWindow ? { contextWindow: model.contextWindow } : {}),
|
|
1203
|
+
...(model.inputModalities ? { inputModalities: model.inputModalities } : {}),
|
|
1204
|
+
}, contextCap));
|
|
1205
|
+
if (!setCached(name, live, Date.now(), cacheGeneration)) return observed(configured, "degraded");
|
|
1206
|
+
markProviderDiscoveryOk(name, live.length);
|
|
1207
|
+
return observed(live, "authoritative");
|
|
978
1208
|
}
|
|
979
1209
|
const extracted = extractProviderModelItems(bounded.value, discovery);
|
|
980
1210
|
if (!extracted.ok) {
|
|
@@ -990,7 +1220,7 @@ async function fetchProviderModelsWithAuth(
|
|
|
990
1220
|
`[opencodex] Provider model discovery for "${name}" ${diagnostic[extracted.reason]} [status=${res.status}, contentType=${contentType}, urlClass=${urlClass}, fallback=${fallback}].`,
|
|
991
1221
|
);
|
|
992
1222
|
}
|
|
993
|
-
return models;
|
|
1223
|
+
return observed(models, "degraded");
|
|
994
1224
|
}
|
|
995
1225
|
const items = extracted.items;
|
|
996
1226
|
const live = items.map(m => {
|
|
@@ -1034,9 +1264,9 @@ async function fetchProviderModelsWithAuth(
|
|
|
1034
1264
|
&& !QUIET_AUTHORITATIVE_CATALOG_PROVIDERS.has(name)) {
|
|
1035
1265
|
warnDroppedConfiguredIdsOnce(name, droppedConfiguredIds);
|
|
1036
1266
|
}
|
|
1267
|
+
if (!setCached(name, live, Date.now(), cacheGeneration)) return observed(configured, "degraded");
|
|
1037
1268
|
markProviderDiscoveryOk(name, liveModelCount);
|
|
1038
|
-
|
|
1039
|
-
return live;
|
|
1269
|
+
return observed(live, "authoritative");
|
|
1040
1270
|
} catch (error) {
|
|
1041
1271
|
if (error instanceof ProviderOutboundPolicyError) {
|
|
1042
1272
|
const { models, fallback, shouldLog } = failedDiscoveryFallback({ reason: "blocked" });
|
|
@@ -1045,7 +1275,7 @@ async function fetchProviderModelsWithAuth(
|
|
|
1045
1275
|
`[opencodex] Provider model discovery for "${name}" was blocked by destination policy: ${error.message} [urlClass=${urlClass}, fallback=${fallback}].`,
|
|
1046
1276
|
);
|
|
1047
1277
|
}
|
|
1048
|
-
return models;
|
|
1278
|
+
return observed(models, "degraded");
|
|
1049
1279
|
}
|
|
1050
1280
|
const { models, fallback, shouldLog } = failedDiscoveryFallback({ reason: "network" });
|
|
1051
1281
|
if (shouldLog) {
|
|
@@ -1053,7 +1283,7 @@ async function fetchProviderModelsWithAuth(
|
|
|
1053
1283
|
`[opencodex] Provider model discovery for "${name}" threw ${error instanceof Error ? error.name : "unknown"} [urlClass=${urlClass}, fallback=${fallback}].`,
|
|
1054
1284
|
);
|
|
1055
1285
|
}
|
|
1056
|
-
return models;
|
|
1286
|
+
return observed(models, "degraded");
|
|
1057
1287
|
}
|
|
1058
1288
|
}
|
|
1059
1289
|
|
|
@@ -1064,7 +1294,12 @@ export async function fetchProviderModels(
|
|
|
1064
1294
|
contextCap?: number,
|
|
1065
1295
|
): Promise<CatalogModel[]> {
|
|
1066
1296
|
const captured = captureProviderGather(name, prov, refreshingModelsAuthResolver);
|
|
1067
|
-
return fetchProviderModelsWithAuth(
|
|
1297
|
+
return (await fetchProviderModelsWithAuth(
|
|
1298
|
+
captured,
|
|
1299
|
+
ttlMs,
|
|
1300
|
+
contextCap,
|
|
1301
|
+
refreshingModelsAuthResolver,
|
|
1302
|
+
)).models;
|
|
1068
1303
|
}
|
|
1069
1304
|
|
|
1070
1305
|
export function shouldExposeProviderModel(providerName: string, modelId: string): boolean {
|
|
@@ -1089,11 +1324,12 @@ export function filterCatalogVisibleModels(
|
|
|
1089
1324
|
if (Array.isArray(sel) && sel.length > 0) allowByProvider.set(name, new Set(sel));
|
|
1090
1325
|
}
|
|
1091
1326
|
return models.filter(m => {
|
|
1327
|
+
const nativeAlias = m.provider === COMBO_NAMESPACE && m.nativeAlias === true;
|
|
1092
1328
|
// disabledModels may be stored raw (canonical) or encoded (legacy UI writes).
|
|
1093
1329
|
for (const stored of disabled) {
|
|
1094
1330
|
// Combo management stores the public alias, while canonical `combo/<id>` references
|
|
1095
1331
|
// remain valid for backward compatibility through slugEquals below.
|
|
1096
|
-
if (m.alias !== undefined && stored === catalogModelSlug(m)) return false;
|
|
1332
|
+
if (m.alias !== undefined && stored === catalogModelSlug(m) && !nativeAlias) return false;
|
|
1097
1333
|
if (slugEquals(stored, m.provider, m.id)) return false;
|
|
1098
1334
|
}
|
|
1099
1335
|
const allow = allowByProvider.get(m.provider);
|
|
@@ -1176,6 +1412,7 @@ async function gatherRoutedModelsWithAuth(
|
|
|
1176
1412
|
models,
|
|
1177
1413
|
comboOmissions,
|
|
1178
1414
|
providerAuthOutcomes,
|
|
1415
|
+
providerModelOutcomes,
|
|
1179
1416
|
discoveryPolicySnapshots,
|
|
1180
1417
|
} = await entry.promise;
|
|
1181
1418
|
if (options?.comboOmissions) {
|
|
@@ -1186,6 +1423,10 @@ async function gatherRoutedModelsWithAuth(
|
|
|
1186
1423
|
options.providerAuthOutcomes.length = 0;
|
|
1187
1424
|
options.providerAuthOutcomes.push(...providerAuthOutcomes);
|
|
1188
1425
|
}
|
|
1426
|
+
if (options?.providerModelOutcomes) {
|
|
1427
|
+
options.providerModelOutcomes.length = 0;
|
|
1428
|
+
options.providerModelOutcomes.push(...providerModelOutcomes);
|
|
1429
|
+
}
|
|
1189
1430
|
if (options?.discoveryPolicySnapshots) {
|
|
1190
1431
|
options.discoveryPolicySnapshots.length = 0;
|
|
1191
1432
|
options.discoveryPolicySnapshots.push(...discoveryPolicySnapshots);
|
|
@@ -1209,7 +1450,7 @@ async function gatherRoutedModelsUncached(
|
|
|
1209
1450
|
// vision-sidecar model advertised text-only, blocking image attachments app-side).
|
|
1210
1451
|
// Enrich a CLONE: hydrated defaults must never leak into the persisted config.
|
|
1211
1452
|
const activeProviders = capture.providers;
|
|
1212
|
-
const
|
|
1453
|
+
const providerResults = await Promise.all(
|
|
1213
1454
|
activeProviders.map(provider => fetchProviderModelsWithAuth(
|
|
1214
1455
|
provider,
|
|
1215
1456
|
ttlMs,
|
|
@@ -1217,12 +1458,13 @@ async function gatherRoutedModelsUncached(
|
|
|
1217
1458
|
resolveAuth,
|
|
1218
1459
|
)),
|
|
1219
1460
|
);
|
|
1461
|
+
const lists = providerResults.map(result => result.models);
|
|
1220
1462
|
const apiAugmented = augmentRoutedModelsWithCapturedOpenAiApiRows(
|
|
1221
1463
|
lists.flat(),
|
|
1222
1464
|
config,
|
|
1223
1465
|
capture.openAiApiPolicy,
|
|
1224
1466
|
);
|
|
1225
|
-
const all =
|
|
1467
|
+
const all = augmentRoutedModelsWithMetadata(apiAugmented, activeProviders.map(provider => provider.name), config.providers, config)
|
|
1226
1468
|
// Drop image/video generation models (e.g. Grok image/video) by default. Cursor's static catalog
|
|
1227
1469
|
// intentionally mirrors Cursor's public model table, including Gemini image preview, so the
|
|
1228
1470
|
// exposure decision goes through shouldExposeRoutedModel (single choke point).
|
|
@@ -1253,8 +1495,16 @@ async function gatherRoutedModelsUncached(
|
|
|
1253
1495
|
// configs that will never need it.
|
|
1254
1496
|
} else {
|
|
1255
1497
|
const disabled = disabledNativeSlugs(config);
|
|
1498
|
+
const requiredNativeComboTargets = new Set(listComboIds(config).flatMap(id => {
|
|
1499
|
+
const combo = getCombo(config, id);
|
|
1500
|
+
return combo?.targets.flatMap(target => (
|
|
1501
|
+
target.provider === "openai" ? [target.model] : []
|
|
1502
|
+
)) ?? [];
|
|
1503
|
+
}));
|
|
1256
1504
|
for (const slug of nativeOpenAiSlugs()) {
|
|
1257
|
-
|
|
1505
|
+
// A bare native disable key hides the native row, not a combo that targets it.
|
|
1506
|
+
// Keep synthetic native metadata available to those combos.
|
|
1507
|
+
if (disabled.has(slug) && !requiredNativeComboTargets.has(slug)) continue;
|
|
1258
1508
|
const contextWindow = nativeOpenAiContextWindow(slug);
|
|
1259
1509
|
if (contextWindow === undefined) continue;
|
|
1260
1510
|
const synthetic: CatalogModel = {
|
|
@@ -1273,21 +1523,47 @@ async function gatherRoutedModelsUncached(
|
|
|
1273
1523
|
if (!memberByKey.has(key)) memberByKey.set(key, synthetic);
|
|
1274
1524
|
}
|
|
1275
1525
|
}
|
|
1526
|
+
// Enriched (registry-hydrated) provider clones — shared by combo member synthesis and
|
|
1527
|
+
// custom-model vision-sidecar inheritance so both see the same merged registry view.
|
|
1528
|
+
const enrichedByName = new Map(activeProviders.map(provider => [provider.name, provider.provider]));
|
|
1276
1529
|
for (const id of listComboIds(config)) {
|
|
1277
1530
|
const combo = getCombo(config, id);
|
|
1278
1531
|
if (!combo) continue;
|
|
1532
|
+
const nativeContextWindow = combo.nativeAlias && combo.alias
|
|
1533
|
+
? nativeOpenAiContextWindow(combo.alias)
|
|
1534
|
+
: undefined;
|
|
1535
|
+
const nativeAliasFallback = combo.nativeAlias && combo.alias && nativeContextWindow !== undefined
|
|
1536
|
+
? {
|
|
1537
|
+
contextWindow: nativeContextWindow,
|
|
1538
|
+
inputModalities: nativeInputModalities(combo.alias),
|
|
1539
|
+
reasoningEfforts: nativeReasoningEfforts(combo.alias),
|
|
1540
|
+
}
|
|
1541
|
+
: undefined;
|
|
1279
1542
|
const members = combo.targets
|
|
1280
|
-
.map(target =>
|
|
1543
|
+
.map(target => resolveComboCatalogMember(
|
|
1544
|
+
target,
|
|
1545
|
+
memberByKey,
|
|
1546
|
+
enrichedByName,
|
|
1547
|
+
providerContextCap(config, target.provider),
|
|
1548
|
+
nativeAliasFallback,
|
|
1549
|
+
))
|
|
1281
1550
|
.filter((member): member is CatalogModel => member !== undefined);
|
|
1282
1551
|
const derived = deriveComboCatalogModel(id, combo, members);
|
|
1283
|
-
if (derived)
|
|
1552
|
+
if (derived) {
|
|
1553
|
+
const nativeDefault = combo.nativeAlias && combo.alias
|
|
1554
|
+
? nativeDefaultReasoningEffort(combo.alias)
|
|
1555
|
+
: undefined;
|
|
1556
|
+
if (combo.defaultEffort === null
|
|
1557
|
+
&& nativeDefault
|
|
1558
|
+
&& derived.reasoningEfforts?.includes(nativeDefault)) {
|
|
1559
|
+
derived.defaultReasoningEffort = nativeDefault;
|
|
1560
|
+
}
|
|
1561
|
+
all.push(derived);
|
|
1562
|
+
}
|
|
1284
1563
|
else warnUncataloguedComboOnce(id, combo, members, localOmissions);
|
|
1285
1564
|
}
|
|
1286
1565
|
replaceLastComboCatalogOmissions(localOmissions);
|
|
1287
1566
|
all.sort((a, b) => (a.provider === b.provider ? a.id.localeCompare(b.id) : a.provider.localeCompare(b.provider)));
|
|
1288
|
-
// Enriched (registry-hydrated) provider clones, keyed by name — the same view used above so
|
|
1289
|
-
// custom rows get the same noVisionModels / inputModalities treatment as discovered rows.
|
|
1290
|
-
const enrichedByName = new Map(activeProviders.map(provider => [provider.name, provider.provider]));
|
|
1291
1567
|
// Provider-derived rows keyed by their Codex-facing slug: a custom override replaces the row
|
|
1292
1568
|
// with the same slug below, so that row's provider capability metadata is the inheritance source.
|
|
1293
1569
|
const replacedByRoutedSlug = new Map(all.map(model => [routedSlug(model.provider, model.id), model]));
|
|
@@ -1297,6 +1573,7 @@ async function gatherRoutedModelsUncached(
|
|
|
1297
1573
|
const base: CatalogModel = {
|
|
1298
1574
|
id: cm.modelId,
|
|
1299
1575
|
provider: cm.provider,
|
|
1576
|
+
catalogKind: CODEX_CUSTOM_MODEL_CATALOG_KIND,
|
|
1300
1577
|
// Display-only label: never feeds routing (customModels are keyed by routedSlug below).
|
|
1301
1578
|
...(cm.displayName ? { displayName: cm.displayName } : {}),
|
|
1302
1579
|
...(cm.contextWindow ? { contextWindow: cm.contextWindow } : {}),
|
|
@@ -1339,10 +1616,18 @@ async function gatherRoutedModelsUncached(
|
|
|
1339
1616
|
// Custom rows override discovered rows that encode to the same Codex-facing slug.
|
|
1340
1617
|
const customKeys = new Set(customModels.map(c => routedSlug(c.provider, c.id)));
|
|
1341
1618
|
const deduped = all.filter(m => !customKeys.has(routedSlug(m.provider, m.id)));
|
|
1619
|
+
const providerModelOutcomes = providerResults.map(result => (
|
|
1620
|
+
result.outcome.provider === OPENAI_API_PROVIDER_ID
|
|
1621
|
+
&& capture.openAiApiPolicy.state === "captured"
|
|
1622
|
+
&& capture.openAiApiPolicy.models !== undefined
|
|
1623
|
+
? { provider: result.outcome.provider, state: "authoritative" as const }
|
|
1624
|
+
: result.outcome
|
|
1625
|
+
));
|
|
1342
1626
|
return {
|
|
1343
1627
|
models: [...deduped, ...customModels],
|
|
1344
1628
|
comboOmissions: localOmissions,
|
|
1345
1629
|
providerAuthOutcomes: localProviderAuthOutcomes,
|
|
1630
|
+
providerModelOutcomes,
|
|
1346
1631
|
discoveryPolicySnapshots: capture.discoveryPolicySnapshots,
|
|
1347
1632
|
};
|
|
1348
1633
|
}
|
|
@@ -1413,7 +1698,7 @@ function augmentRoutedModelsWithCapturedOpenAiApiRows(
|
|
|
1413
1698
|
];
|
|
1414
1699
|
}
|
|
1415
1700
|
|
|
1416
|
-
export function
|
|
1701
|
+
export function augmentRoutedModelsWithMetadata(
|
|
1417
1702
|
models: CatalogModel[],
|
|
1418
1703
|
providerNames: string[],
|
|
1419
1704
|
providers?: Record<string, OcxProviderConfig>,
|
|
@@ -1424,9 +1709,9 @@ export function augmentRoutedModelsWithJawcodeMetadata(
|
|
|
1424
1709
|
for (const provider of providerNames) {
|
|
1425
1710
|
if (!JAWCODE_CATALOG_AUGMENT_PROVIDERS.has(provider)) continue;
|
|
1426
1711
|
if (providers?.[provider]?.liveModels === false) continue;
|
|
1427
|
-
const jawcodeProvider =
|
|
1712
|
+
const jawcodeProvider = resolveMetadataProvider(provider);
|
|
1428
1713
|
if (!jawcodeProvider) continue;
|
|
1429
|
-
for (const meta of
|
|
1714
|
+
for (const meta of listModelMetadata(jawcodeProvider)) {
|
|
1430
1715
|
const key = `${provider}/${meta.id}`;
|
|
1431
1716
|
if (seen.has(key)) continue;
|
|
1432
1717
|
seen.add(key);
|