From 796f7fe34a3d503bc48b2588413ba2ef73ac8499 Mon Sep 17 00:00:00 2001 From: luvs01 Date: Mon, 7 Sep 2026 15:25:14 +0900 Subject: [PATCH] fix(routing): suppress colliding registry aliases (cherry picked from commit 7ad74746ed260b441ddbceb7a46204085945aa3e) Co-authored-by: luvs01 --- .../ja/reference/configuration/providers.md | 4 ++ .../ko/reference/configuration/providers.md | 4 ++ .../docs/reference/configuration/providers.md | 4 ++ .../ru/reference/configuration/providers.md | 4 ++ .../reference/configuration/providers.md | 4 ++ src/providers/default-aliases.ts | 5 ++- src/router.ts | 7 ++- .../providers/provider-model-aliases.test.ts | 44 +++++++++++++++++++ 8 files changed, 73 insertions(+), 3 deletions(-) diff --git a/docs-site/src/content/docs/ja/reference/configuration/providers.md b/docs-site/src/content/docs/ja/reference/configuration/providers.md index eb79145fac..608e66deb2 100644 --- a/docs-site/src/content/docs/ja/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ja/reference/configuration/providers.md @@ -66,6 +66,10 @@ account を削除しても mapping は保持され、同じ id を再追加す `openaiProviderTierVersion: 2` は、現在の単一プロバイダーの投影をマークします。出荷された v1 設定を移行する前に、opencodex は別のバックアップを置き換えずに `config.json.pre-openai-tiers-v2.bak` を作成し、既知の名前空間で選択された既知のレガシー ID を裸の ID に書き換えます。 +## プロバイダー名前空間のエイリアス + +プロバイダーには、`google-antigravity` の `agy` のような組み込みの短縮名があります。設定済みのプロバイダー名または明示的なエイリアスが、大文字と小文字を区別せずにその短縮名を使用している場合、別のプロバイダーの組み込み短縮名はカタログ表示とエイリアスルーティングの両方で無効になります。たとえば、`agy` というプロバイダーを設定すると、Google のモデルは `google-antigravity/` と表示され、`agy/` は設定済みのプロバイダーを選択します。正規のプロバイダー名は引き続き大文字と小文字の完全一致が必要で、認識されない接頭辞には既存のモデルルーティングのフォールバックが適用されます。 + ## プロバイダーエントリー (`OcxProviderConfig`) |フィールド |タイプ |意味 | diff --git a/docs-site/src/content/docs/ko/reference/configuration/providers.md b/docs-site/src/content/docs/ko/reference/configuration/providers.md index 8b5f310f7a..342441eca1 100644 --- a/docs-site/src/content/docs/ko/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ko/reference/configuration/providers.md @@ -66,6 +66,10 @@ managed map을 활성화하면 privacy-safe selector를 만들고, 이후 계정 `openaiProviderTierVersion: 2`는 현재의 단일 공급자 투영을 표시합니다. 출시된 v1 설정을 마이그레이션하기 전에 opencodex는 `config.json.pre-openai-tiers-v2.bak`를 만들고, 기존에 다른 백업이 있더라도 덮어쓰지 않으며, 알려진 레거시 네임스페이스 지정 선택 id를 bare id로 다시 씁니다. +## 공급자 네임스페이스 별칭 + +공급자는 `google-antigravity`의 `agy`처럼 기본 축약 이름을 제공할 수 있습니다. 설정된 공급자 이름이나 명시적 별칭이 대소문자 구분 없이 그 이름을 사용하면, 다른 공급자의 기본 축약 이름은 카탈로그 표시와 별칭 라우팅 모두에서 비활성화됩니다. 예를 들어 `agy`라는 공급자를 설정하면 Google 모델은 `google-antigravity/`로 표시되고, `agy/`는 설정된 공급자를 선택합니다. 정식 공급자 이름은 계속 대소문자가 정확히 일치해야 하며, 인식되지 않는 접두사는 기존 모델 라우팅의 대체 경로를 따릅니다. + ## 공급자 항목 (`OcxProviderConfig`) | 필드 | 타입 | 의미 | diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index 5514ad7694..8f55d6b51f 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -116,6 +116,10 @@ published long-context bands on `openai` and `openai-apikey`. The two Daybreak B follow the Sol API reference. These are comparison estimates, not invoices or credit-balance predictions. Explicit provider/model price overrides still take precedence. +## Provider namespace aliases + +Providers can expose a built-in shorthand, such as `agy` for `google-antigravity`. A configured provider name or explicit alias claims that shorthand case-insensitively; a different provider's built-in shorthand is then suppressed in both catalog names and alias routing. For example, configuring a provider named `agy` keeps Google's models under `google-antigravity/`, while `agy/` selects the configured provider. Canonical provider names still require an exact case match, and unrecognized prefixes retain the existing model-routing fallback. + ## Provider entries (`OcxProviderConfig`) | Field | Type | Meaning | diff --git a/docs-site/src/content/docs/ru/reference/configuration/providers.md b/docs-site/src/content/docs/ru/reference/configuration/providers.md index 1a4643cc1e..78bca4d40d 100644 --- a/docs-site/src/content/docs/ru/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ru/reference/configuration/providers.md @@ -79,6 +79,10 @@ cross-route credential fallback не существует. Строки API GPT- перезаписывая отличающуюся backup-копию, и переписывает известные legacy namespaced-id, выбранные в `selectedModels`, в bare-id. +## Псевдонимы пространств имён провайдеров + +У провайдеров могут быть встроенные сокращения, например `agy` для `google-antigravity`. Если имя настроенного провайдера или явный псевдоним занимает такое сокращение без учёта регистра, встроенное сокращение другого провайдера отключается и в каталоге, и при маршрутизации по псевдониму. Например, при настройке провайдера с именем `agy` модели Google отображаются как `google-antigravity/`, а `agy/` выбирает настроенного провайдера. Канонические имена провайдеров по-прежнему требуют точного совпадения регистра; для нераспознанных префиксов сохраняется существующий резервный путь маршрутизации модели. + ## Записи провайдеров (`OcxProviderConfig`) | Поле | Тип | Значение | diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md index a3008db320..99fba1fbb4 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md @@ -66,6 +66,10 @@ selector,而不是分配一个新名称。 `openaiProviderTierVersion: 2` 标记当前的单提供者投影。对已发布的 v1 配置进行迁移之前,opencodex 会创建 `config.json.pre-openai-tiers-v2.bak`,且不会覆盖不同的备份文件,并会把已知的旧式命名空间选择 id 重写为裸 id。 +## 提供者命名空间别名 + +提供者可以有内置缩写,例如 `google-antigravity` 的 `agy`。如果已配置的提供者名称或显式别名占用了该缩写(不区分大小写),另一个提供者的内置缩写就会在目录名称和别名路由中同时禁用。例如,配置名为 `agy` 的提供者后,Google 模型会显示为 `google-antigravity/`,而 `agy/` 会选择已配置的提供者。规范提供者名称仍要求大小写完全一致;无法识别的前缀继续沿用现有的模型路由回退行为。 + ## 提供者条目(`OcxProviderConfig`) | 字段 | 类型 | 含义 | diff --git a/src/providers/default-aliases.ts b/src/providers/default-aliases.ts index b11078bedc..dffbd9eb3c 100644 --- a/src/providers/default-aliases.ts +++ b/src/providers/default-aliases.ts @@ -15,7 +15,10 @@ export function effectiveProviderAlias( 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 + name !== providerName && ( + name.toLowerCase() === lower + || (typeof p.alias === "string" && p.alias.trim().toLowerCase() === lower) + ) ); if (claimedByOther) return undefined; } diff --git a/src/router.ts b/src/router.ts index b2f887f0a9..ad1aaeff35 100644 --- a/src/router.ts +++ b/src/router.ts @@ -703,13 +703,16 @@ function routeModelInternal( 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) + // and whose registry alias has not been claimed by another configured provider name or alias 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 + otherName !== name && ( + otherName.toLowerCase() === requestedLower + || (typeof p.alias === "string" && p.alias.trim().toLowerCase() === requestedLower) + ) ); return !claimedByOther; }); diff --git a/tests/providers/provider-model-aliases.test.ts b/tests/providers/provider-model-aliases.test.ts index d326a8562e..5ba8b5981e 100644 --- a/tests/providers/provider-model-aliases.test.ts +++ b/tests/providers/provider-model-aliases.test.ts @@ -217,6 +217,50 @@ describe("provider and model aliases", () => { routeReason: "explicit-provider-namespace", }); }); + test.each(["agy", "AgY"])("canonical provider name %s suppresses a colliding built-in alias", async canonicalName => { + const c = { + port: 10100, + defaultProvider: "google-antigravity", + providers: { + [canonicalName]: { + adapter: "openai-chat", + baseUrl: "https://custom.test/v1", + models: ["gemini-3.8-flash"], + liveModels: false, + }, + "google-antigravity": { + adapter: "google", + baseUrl: "https://daily-cloudcode-pa.googleapis.com", + authMode: "oauth", + models: ["gemini-3.8-flash"], + liveModels: false, + }, + }, + } as unknown as OcxConfig; + + const models = await gatherRoutedModels(c); + const googleModel = models.find(m => m.provider === "google-antigravity" && m.id === "gemini-3.8-flash")!; + expect(googleModel.providerAlias).toBeNull(); + + const [googleEntry] = buildCatalogEntries(null, [], [googleModel]); + expect(googleEntry!.display_name).toBe("google-antigravity/gemini-3.8-flash"); + expect(routeModel(c, googleEntry!.display_name)).toMatchObject({ + providerName: "google-antigravity", + modelId: "gemini-3.8-flash", + }); + expect(routeModel(c, `${canonicalName}/gemini-3.8-flash`)).toMatchObject({ + providerName: canonicalName, + modelId: "gemini-3.8-flash", + }); + + // Canonical names stay case-sensitive. A case variant must not activate the + // claimed registry alias; preserve the unknown slash-id default fallback. + expect(routeModel({ ...c, defaultProvider: canonicalName }, "AGY/gemini-3.8-flash")).toMatchObject({ + providerName: canonicalName, + modelId: "AGY/gemini-3.8-flash", + routeReason: "default-provider", + }); + }); test("static gather (liveModels: false) suppresses agy when other provider explicitly owns it", async () => { const c = { port: 10100,