From 332c738844b7a8aa776b6036ea2431def6f3faa6 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 9 Aug 2026 09:52:28 -0300 Subject: [PATCH] fix(sse): route claude// aliases for catalog-only providers (#9856) The /v1/models catalog mirrors `claude//` ids purely from the alias gate -- ccAliasPredicate.ts consults no provider registry. The request path additionally required the prefix to be an open-sse REGISTRY entry or an operator-defined custom node. Enterprise-cloud providers such as azure-ai / azure-openai live only in the provider catalog (src/shared/constants/providers/apikey/enterprise-cloud.ts). They route fine directly -- `azure-ai/Phi-4` returns 200 -- but have no open-sse registry entry, so the two sides disagreed: the catalog advertised `claude/azure-ai/` while stripCcDiscoveryAlias refused to strip it. The unstripped id then fell through to normal resolution, which splits on the first / and parsed `claude` as the provider. Every Claude Code request for an Azure model was routed to the Claude provider instead: ROUTING: Provider: claude, Model: azure-ai/DeepSeek-V4-Flash Extract the predicate as `isRoutableProviderPrefix()` and widen it to the provider catalog (id + alias) alongside the open-sse registry, so the request path recognises exactly what the catalog can advertise. Regression guard: tests/unit/cc-discovery-alias-routable-prefix.test.ts pins azure-ai/azure-openai/azure as routable, keeps openai/anthropic routable, and keeps an unknown prefix non-routable. Verified failing before the widening. Co-authored-by: Mihaly Bodo --- src/lib/ccDiscoveryAliasResolve.ts | 19 +++++++- ...cc-discovery-alias-routable-prefix.test.ts | 43 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/unit/cc-discovery-alias-routable-prefix.test.ts diff --git a/src/lib/ccDiscoveryAliasResolve.ts b/src/lib/ccDiscoveryAliasResolve.ts index 2112affbec..00a0d7ba83 100644 --- a/src/lib/ccDiscoveryAliasResolve.ts +++ b/src/lib/ccDiscoveryAliasResolve.ts @@ -20,6 +20,7 @@ import { } from "@omniroute/open-sse/handlers/chatCore/ccDiscoveryAliasStrip.ts"; import { getModelsByProviderId } from "@omniroute/open-sse/config/providerModels.ts"; import { getRegistryEntry } from "@omniroute/open-sse/config/providerRegistry.ts"; +import { getProviderById, getProviderByAlias } from "@/shared/constants/providers"; import { getCachedProviderNodes } from "@/lib/db/readCache"; import { getComboByName } from "@/lib/db/combos"; import { @@ -136,6 +137,22 @@ export async function resolveCcDiscoveryAliasStripWith( }); } +/** + * True when `prefix` names a provider the router can actually reach. + * + * Deliberately broader than the `open-sse` REGISTRY alone: enterprise-cloud + * providers such as `azure-ai` / `azure-openai` live only in the provider + * CATALOG (src/shared/constants/providers/…) yet route fine, so a registry-only + * check made the request path reject `claude/azure-ai/` ids that the + * catalog had already advertised — see cc-discovery-alias-routable-prefix.test.ts. + */ +export function isRoutableProviderPrefix(prefix: string): boolean { + if (!prefix) return false; + if (getRegistryEntry(prefix) !== null) return true; + if (getProviderById(prefix) !== undefined) return true; + return getProviderByAlias(prefix) !== null; +} + /** * Production entry point: build the real lookups and resolve. Cheap-exit for any * id that does not start with `claude/` (the overwhelmingly common case) so a @@ -169,7 +186,7 @@ export async function resolveCcDiscoveryAliasStrip( const result = await resolveCcDiscoveryAliasStripWith(modelStr, { claudeModelIds, - isRegistryProvider: (prefix) => getRegistryEntry(prefix) !== null, + isRegistryProvider: (prefix) => isRoutableProviderPrefix(prefix), customProviderPrefixes, getCombo: (name) => getComboByName(name), gateGlobal: () => globalEnabled, diff --git a/tests/unit/cc-discovery-alias-routable-prefix.test.ts b/tests/unit/cc-discovery-alias-routable-prefix.test.ts new file mode 100644 index 0000000000..f884a7c17a --- /dev/null +++ b/tests/unit/cc-discovery-alias-routable-prefix.test.ts @@ -0,0 +1,43 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; + +import { isRoutableProviderPrefix } from "../../src/lib/ccDiscoveryAliasResolve.ts"; + +/** + * Regression guard for the "listed but rejected" cc-discovery alias mismatch. + * + * The /v1/models catalog mirrors `claude//` ids based purely on + * the alias gate (src/app/api/v1/models/ccAliasPredicate.ts — it does NOT consult + * any provider registry). The request path additionally required the prefix to be + * an `open-sse` REGISTRY entry or an operator-defined custom node. + * + * Enterprise-cloud providers such as `azure-ai` / `azure-openai` live in the + * provider CATALOG (src/shared/constants/providers/apikey/enterprise-cloud.ts) + * and route fine directly (`azure-ai/Phi-4` → 200), but have no `open-sse` + * registry entry. So the catalog advertised `claude/azure-ai/` while the + * request path refused to strip it — the id fell through with `claude` parsed as + * the provider, and every request was routed to the Claude provider instead. + * + * These assertions pin the predicate to "can the router actually reach it", + * which is the property the catalog side already assumes. + */ + +test("catalog-only enterprise-cloud providers are routable (azure-ai regression)", () => { + assert.equal(isRoutableProviderPrefix("azure-ai"), true); + assert.equal(isRoutableProviderPrefix("azure-openai"), true); +}); + +test("open-sse registry providers stay routable", () => { + assert.equal(isRoutableProviderPrefix("openai"), true); + assert.equal(isRoutableProviderPrefix("anthropic"), true); +}); + +test("provider aliases resolve too", () => { + // `azure` is the declared alias of the `azure-openai` catalog entry. + assert.equal(isRoutableProviderPrefix("azure"), true); +}); + +test("an unknown prefix is not routable", () => { + assert.equal(isRoutableProviderPrefix("definitely-not-a-provider-xyz"), false); + assert.equal(isRoutableProviderPrefix(""), false); +});