Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/codex/catalog/parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ export const CODEX_PROVIDER_MODEL_CATALOG_KIND = "provider-model-v1";
export interface CatalogModel {
id: string;
provider: string;
/** Canonical or configured short alias for the provider segment. */
providerAlias?: string | null;
/** Public Codex-facing slug override (used by combo aliases). */
alias?: string;
/** Explicit combo takeover of a bare OpenAI-native catalog id. */
Expand Down
36 changes: 24 additions & 12 deletions src/codex/catalog/provider-fetch.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { effectiveProviderAlias, effectiveProviderAliasDecision } from "../../providers/default-aliases";
import { execFileSync } from "node:child_process";
import { createHash, createHmac, randomBytes } from "node:crypto";
import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
Expand Down Expand Up @@ -164,6 +165,7 @@ interface CapturedProviderGather {
readonly request: CapturedModelsRequest;
readonly fastPolicyAuthority: FastPolicyAuthority;
readonly metadataModelIdCaseFold: boolean;
readonly effectiveAlias?: string | null;
readonly observedAuth?: ModelsAuthResolution;
/**
* Configured model ids this provider must keep even when live discovery omits
Expand Down Expand Up @@ -414,6 +416,7 @@ function captureProviderGather(
configured: OcxProviderConfig,
authResolver: ModelsAuthResolver,
retainConfiguredModelIds?: ReadonlySet<string>,
config?: Pick<OcxConfig, "providers">,
): CapturedProviderGather {
const enriched = detachedClone(withCanonicalOpenAiForwardAuthDefault(name, configured));
enrichProviderFromRegistry(name, enriched);
Expand Down Expand Up @@ -455,6 +458,7 @@ function captureProviderGather(
maxModels: discovery.maxModels,
trustedOpenAiApi,
});
const effectiveAlias = effectiveProviderAliasDecision(name, configured, config);
return Object.freeze({
name,
provider,
Expand All @@ -463,6 +467,7 @@ function captureProviderGather(
request,
fastPolicyAuthority,
metadataModelIdCaseFold,
effectiveAlias,
...(observedAuth ? { observedAuth: Object.freeze({ ...observedAuth }) } : {}),
...(retainConfiguredModelIds && retainConfiguredModelIds.size > 0
? { retainConfiguredModelIds }
Expand Down Expand Up @@ -504,6 +509,7 @@ function captureGatherFlight(
provider,
authResolver,
comboTargetsByProvider.get(name),
config,
));
const discoveryPolicySnapshots = Object.freeze(providers.map(provider => provider.policy));
return Object.freeze({
Expand Down Expand Up @@ -729,8 +735,10 @@ export function applyProviderConfigHints(
model: CatalogModel,
providerCap?: number,
metadataModelIdCaseFold?: boolean,
effectiveAlias?: string | null,
): CatalogModel {
const displayName = configuredModelDisplayName(prov, model.id);
const providerAlias = typeof effectiveAlias === "string" || effectiveAlias === null ? effectiveAlias : effectiveProviderAliasDecision(name, prov);
const configuredCap = configuredContextWindow(prov, model.id);
const configuredMaxInput = configuredMaxInputTokens(prov, model.id);
const maxOutputTokens = routedMaxOutputTokens(name, prov, model, model.id, metadataModelIdCaseFold);
Expand All @@ -756,6 +764,7 @@ export function applyProviderConfigHints(
const {
supportsServiceTier: _staleServiceTier,
fastTierDescription: _staleFastTierDescription,
providerAlias: _staleProviderAlias,
...modelWithoutServiceTier
} = model;
// 已发现窗口只允许被配置值压低;缺窗口时,已开的 Context cap 就是实际窗口。
Expand All @@ -768,6 +777,7 @@ export function applyProviderConfigHints(
const hinted = {
...modelWithoutServiceTier,
...(displayName !== undefined ? { displayName } : {}),
...(providerAlias !== undefined ? { providerAlias } : {}),
...(hintedWindow !== undefined ? { contextWindow: hintedWindow } : {}),
...(inputModalities ? { inputModalities } : {}),
...(reasoningEfforts !== undefined ? { reasoningEfforts } : {}),
Expand Down Expand Up @@ -827,8 +837,9 @@ export function catalogHintsFromProviderConfig(
id: string,
contextCap?: number,
metadataModelIdCaseFold?: boolean,
effectiveAlias?: string | null,
): Partial<CatalogModel> {
const hinted = applyProviderConfigHints(name, prov, { id, provider: name }, contextCap, metadataModelIdCaseFold);
const hinted = applyProviderConfigHints(name, prov, { id, provider: name }, contextCap, metadataModelIdCaseFold, effectiveAlias);
const { provider: _provider, id: _id, ...hints } = hinted;
return hints;
}
Expand All @@ -839,8 +850,9 @@ export function applyConfigHintsToCachedModels(
models: CatalogModel[],
contextCap?: number,
metadataModelIdCaseFold?: boolean,
effectiveAlias?: string | null,
): CatalogModel[] {
return models.map(model => applyProviderConfigHints(name, prov, model, contextCap, metadataModelIdCaseFold));
return models.map(model => applyProviderConfigHints(name, prov, model, contextCap, metadataModelIdCaseFold, effectiveAlias));
}


Expand Down Expand Up @@ -1468,7 +1480,7 @@ async function fetchProviderModelsWithAuth(
const configured: CatalogModel[] = configuredIds.map(id => ({
id,
provider: name,
...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold),
...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
}));
const withConfiguredRetention = (
models: CatalogModel[],
Expand Down Expand Up @@ -1522,7 +1534,7 @@ async function fetchProviderModelsWithAuth(
: [{
id: prov.defaultModel,
provider: name,
...catalogHintsFromProviderConfig(name, prov, prov.defaultModel, contextCap, metadataModelIdCaseFold),
...catalogHintsFromProviderConfig(name, prov, prov.defaultModel, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
}];
const vertexDefaultSeed = seedVertexDefault ? configured[0] : undefined;
const withVertexDefaultSeed = (models: CatalogModel[]): CatalogModel[] => (
Expand All @@ -1539,15 +1551,15 @@ async function fetchProviderModelsWithAuth(
const cachedCursor = getFreshCached(name, ttlMs);
if (cachedCursor) {
return observed(
withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedCursor, undefined, metadataModelIdCaseFold)),
withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias)),
"authoritative",
);
}
if (isModelsFetchCoolingDown(name)) {
const cooling = getStaleCached(name);
return observed(
withConfiguredRetention(
cooling ? applyConfigHintsToCachedModels(name, prov, cooling, undefined, metadataModelIdCaseFold) : configured,
cooling ? applyConfigHintsToCachedModels(name, prov, cooling, undefined, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
),
"degraded",
);
Expand Down Expand Up @@ -1588,7 +1600,7 @@ async function fetchProviderModelsWithAuth(
const staleCursor = getStaleCached(name);
return observed(
withConfiguredRetention(
staleCursor ? applyConfigHintsToCachedModels(name, prov, staleCursor, undefined, metadataModelIdCaseFold) : configured,
staleCursor ? applyConfigHintsToCachedModels(name, prov, staleCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
),
"degraded",
);
Expand All @@ -1606,7 +1618,7 @@ async function fetchProviderModelsWithAuth(
if (fresh) {
return observed(
withConfiguredRetention(
withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold)),
withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias)),
),
"authoritative",
); // dedups Codex's frequent /v1/models polling within the TTL
Expand All @@ -1618,7 +1630,7 @@ async function fetchProviderModelsWithAuth(
return observed(
withConfiguredRetention(
stale
? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold))
? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias))
: failedDiscoveryConfigured,
),
"degraded",
Expand Down Expand Up @@ -1658,7 +1670,7 @@ async function fetchProviderModelsWithAuth(
return {
models: withConfiguredRetention(
stale
? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold))
? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias))
: failedDiscoveryConfigured,
),
fallback: stale ? "stale" : "configured",
Expand Down Expand Up @@ -1735,7 +1747,7 @@ async function fetchProviderModelsWithAuth(
reasoningEfforts: [],
...(model.contextWindow ? { contextWindow: model.contextWindow } : {}),
...(model.inputModalities ? { inputModalities: model.inputModalities } : {}),
}, contextCap, metadataModelIdCaseFold));
}, contextCap, metadataModelIdCaseFold, captured.effectiveAlias));
const forCache = withConfiguredRetention(live, { retainComboTargets: false });
if (!setCached(name, forCache, Date.now(), cacheGeneration)) {
return observed(withConfiguredRetention(configured), "degraded");
Expand Down Expand Up @@ -1798,7 +1810,7 @@ async function fetchProviderModelsWithAuth(
provider: name,
...(ownedBy ? { owned_by: ownedBy } : {}),
...discoveredHints,
}, contextCap, metadataModelIdCaseFold);
}, contextCap, metadataModelIdCaseFold, captured.effectiveAlias);
})
.filter(m => shouldExposeProviderModel(name, m.id));
// Capture the count BEFORE the alias/configured augmentation below pushes extra rows into
Expand Down
22 changes: 15 additions & 7 deletions src/codex/catalog/sync.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { effectiveProviderAlias } from "../../providers/default-aliases";
import { execFileSync } from "node:child_process";
import { createHash } from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
Expand Down Expand Up @@ -269,15 +270,22 @@ function isExactComboCatalogEntry(
* The model-id portion also carries a redundant `<vendor>-` prefix (`deepseek-deepseek-v4-flash`)
* that is dropped for display. All other providers keep the raw slug exactly as before.
*/
function routedDisplayName(slug: string): string {
function routedDisplayName(slug: string, model?: CatalogModel, config?: Pick<OcxConfig, "providers">): string {
const slash = slug.indexOf("/");
if (slash <= 0) return slug;
const provider = slug.slice(0, slash);
let model = slug.slice(slash + 1);
let modelId = slug.slice(slash + 1);
if (provider === "google-antigravity") {
if (model?.providerAlias === null) return slug;
const alias = (typeof model?.providerAlias === "string" && model.providerAlias.trim().length > 0)
? model.providerAlias.trim()
: effectiveProviderAlias(provider, undefined, config);
return alias ? `${alias}/${modelId}` : slug;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (provider === "command-code" || provider === "commandcode") {
const m = model.match(/^([a-z0-9]+)-([a-z0-9]+(?:-[a-z0-9]+)+)$/i);
if (m && model.startsWith(`${m[1]}-${m[1]}-`)) model = model.slice(m[1]!.length + 1);
return `${provider === "command-code" ? "commandcode-auth" : "commandcode-api"}/${model}`;
const m = modelId.match(/^([a-z0-9]+)-([a-z0-9]+(?:-[a-z0-9]+)+)$/i);
if (m && modelId.startsWith(`${m[1]}-${m[1]}-`)) modelId = modelId.slice(m[1]!.length + 1);
return `${provider === "command-code" ? "commandcode-auth" : "commandcode-api"}/${modelId}`;
}
return slug;
}
Expand Down Expand Up @@ -306,7 +314,7 @@ export function deriveEntry(
if (template || codexForwardNativeCapabilityAlias) {
const e = JSON.parse(JSON.stringify(codexForwardNativeCapabilityAlias ?? template)) as RawEntry;
e.slug = slug;
e.display_name = routedDisplayName(slug);
e.display_name = routedDisplayName(slug, model);
e.description = desc;
e.priority = priority;
e.visibility = "list";
Expand Down Expand Up @@ -375,7 +383,7 @@ export function deriveEntry(
// Cursor still omits hosted web-search metadata because runTurn bypasses that separate sidecar.
const isCursorFallback = isRouted && model?.provider === "cursor";
const entry: RawEntry = {
slug, display_name: routedDisplayName(slug), description: desc,
slug, display_name: routedDisplayName(slug, model), description: desc,
shell_type: "unified_exec", visibility: "list", supported_in_api: true,
priority, base_instructions: "You are a helpful coding assistant.",
...(isRouted
Expand Down
39 changes: 39 additions & 0 deletions src/providers/default-aliases.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,42 @@

import { PROVIDER_REGISTRY } from "./registry";

export function effectiveProviderAlias(
providerName: string,
provider?: Pick<OcxProviderConfig, "alias">,
config?: Pick<OcxConfig, "providers">,
): string | undefined {
if (provider && provider.alias !== undefined) {
const trimmed = provider.alias.trim();
return trimmed.length > 0 ? trimmed : undefined;
}
const regAlias = PROVIDER_REGISTRY.find(e => e.id === providerName)?.alias;
if (!regAlias) return undefined;
if (config?.providers) {
const lower = regAlias.toLowerCase();
const claimedByOther = Object.entries(config.providers).some(([name, p]) =>
name !== providerName && typeof p.alias === "string" && p.alias.trim().toLowerCase() === lower
);
if (claimedByOther) return undefined;
}
return regAlias;
}

export function effectiveProviderAliasDecision(
providerName: string,
provider?: Pick<OcxProviderConfig, "alias">,
config?: Pick<OcxConfig, "providers">,
): string | null | undefined {
const active = effectiveProviderAlias(providerName, provider, config);
if (active !== undefined) return active;
const hasRegistryAlias = Boolean(PROVIDER_REGISTRY.find(e => e.id === providerName)?.alias);
const hasConfiguredAlias = provider?.alias !== undefined;
if (hasRegistryAlias || hasConfiguredAlias) {
return null;
}
return undefined;
}

import type { OcxConfig, OcxProviderConfig } from "../types";

export const MODEL_ALIAS_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
Expand Down
1 change: 1 addition & 0 deletions src/providers/derive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderCon
baseUrl: entry.baseUrl,
...(entry.apiKeyTransport !== undefined ? { apiKeyTransport: entry.apiKeyTransport } : {}),
...(entry.responsesPath ? { responsesPath: entry.responsesPath } : {}),
...(entry.alias ? { alias: entry.alias } : {}),
// Preserve the registry auth kind verbatim (including "local") so fail-closed gates that
// distinguish local runtimes from API-key providers keep working after the seed round-trip.
authMode: entry.authKind,
Expand Down
3 changes: 2 additions & 1 deletion src/providers/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export interface ProviderRegistryEntry {
adapter: string;
baseUrl: string;
apiKeyTransport?: OcxProviderConfig["apiKeyTransport"];
alias?: string;
authKind: ProviderAuthKind;
codexAccountMode?: CodexAccountMode;
/** OAuth preset may explicitly honor a persisted API-key billing mode. */
Expand Down Expand Up @@ -1900,7 +1901,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
// 2026-07-10: defaultModel is frozen pending Vertex-specific Tier-2 evidence; Gemini API
// evidence from ai.google.dev does not establish Vertex publisher availability.
{ id: "google-vertex", label: "Google Vertex AI", adapter: "google", baseUrl: "https://aiplatform.googleapis.com", authKind: "key", dashboardUrl: "https://console.cloud.google.com/vertex-ai", defaultModel: "gemini-3-pro", googleMode: "vertex", jawcodeBundle: "google", extraMetadataAliases: ["gemini-vertex"] },
{ id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", allowBaseUrlOverride: true, dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, liveModels: true, defaultModel: "gemini-3.8-flash", modelContextWindows: ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, modelInputModalities: ANTIGRAVITY_MODEL_INPUT_MODALITIES, modelReasoningEfforts: ANTIGRAVITY_MODEL_EFFORTS, googleMode: "cloud-code-assist", jawcodeBundle: "google", extraMetadataAliases: ["antigravity", "gemini-antigravity"] },
{ id: "google-antigravity", alias: "agy", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", allowBaseUrlOverride: true, dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, liveModels: true, defaultModel: "gemini-3.8-flash", modelContextWindows: ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, modelInputModalities: ANTIGRAVITY_MODEL_INPUT_MODALITIES, modelReasoningEfforts: ANTIGRAVITY_MODEL_EFFORTS, googleMode: "cloud-code-assist", jawcodeBundle: "google", extraMetadataAliases: ["antigravity", "gemini-antigravity"] },
{ id: "azure-openai", label: "Azure OpenAI", adapter: "azure-openai", baseUrl: "https://{resource}.openai.azure.com/openai", authKind: "key", featured: true, dashboardUrl: "https://portal.azure.com" },
{ id: "ollama", label: "Ollama (local)", adapter: "openai-chat", baseUrl: "http://localhost:11434/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
{ id: "vllm", label: "vLLM (local)", adapter: "openai-chat", baseUrl: "http://localhost:8000/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
Expand Down
37 changes: 33 additions & 4 deletions src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -679,10 +679,39 @@ function routeModelInternal(
// no such provider exists.
if (slash > 0) {
const requestedProvider = modelId.slice(0, slash);
const provName = hasOwnProvider(config.providers, requestedProvider)
? requestedProvider
: Object.entries(config.providers).find(([, provider]) =>
typeof provider.alias === "string" && provider.alias.toLowerCase() === requestedProvider.toLowerCase())?.[0];
const requestedLower = requestedProvider.toLowerCase();
let provName: string | undefined;

if (hasOwnProvider(config.providers, requestedProvider)) {
provName = requestedProvider;
} else {
// Pass 1: explicit configured provider aliases (operator override always wins)
const configuredMatches = Object.entries(config.providers).filter(([, provider]) =>
typeof provider.alias === "string" && provider.alias.trim().toLowerCase() === requestedLower,
);
if (configuredMatches.length === 1) {
provName = configuredMatches[0]![0];
} else if (configuredMatches.length > 1) {
throw new Error("provider alias '" + requestedProvider + "' is ambiguous: " + configuredMatches.map(([n]) => n).sort().join(", "));
} else {
// Pass 2: built-in registry aliases, only for providers that do NOT have an explicit alias override
// and whose registry alias has not been claimed by another configured provider (#3531 review)
const registryMatches = Object.entries(config.providers).filter(([name, provider]) => {
if (provider.alias !== undefined) return false;
const regAlias = PROVIDER_REGISTRY.find(e => e.id === name)?.alias;
if (!regAlias || regAlias.toLowerCase() !== requestedLower) return false;
const claimedByOther = Object.entries(config.providers).some(([otherName, p]) =>
otherName !== name && typeof p.alias === "string" && p.alias.trim().toLowerCase() === requestedLower
);
return !claimedByOther;
});
if (registryMatches.length === 1) {
provName = registryMatches[0]![0];
} else if (registryMatches.length > 1) {
throw new Error("provider alias '" + requestedProvider + "' is ambiguous across registry fallbacks: " + registryMatches.map(([n]) => n).sort().join(", "));
}
}
}
if (!provName) {
// A genuine slash-containing native model id still falls through unchanged.
} else {
Expand Down
Loading
Loading