From c4e0d27adeb15f8cae76828cfe7c2d2c451e330a Mon Sep 17 00:00:00 2001 From: Vincent Koc <25068+vincentkoc@users.noreply.github.com> Date: Wed, 17 Jun 2026 07:44:32 +0800 Subject: [PATCH] fix(providers): restore ClawRouter native runtime routes --- .../clawrouter/provider-catalog.test.ts | 11 ++++++++- extensions/clawrouter/provider-catalog.ts | 23 ++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/extensions/clawrouter/provider-catalog.test.ts b/extensions/clawrouter/provider-catalog.test.ts index 01e9b487291e..0b96aac93f41 100644 --- a/extensions/clawrouter/provider-catalog.test.ts +++ b/extensions/clawrouter/provider-catalog.test.ts @@ -156,9 +156,17 @@ describe("clawrouter provider catalog", () => { const anthropic = provider.models.find((model) => model.id === "anthropic/default"); const normalized = normalizeClawRouterResolvedModel({ ...anthropic, + baseUrl: provider.baseUrl, provider: "clawrouter", } as ProviderRuntimeModel); - expect(normalized?.id).toBe("claude-sonnet-4-5-20250929"); + expect(normalized).toMatchObject({ + id: "claude-sonnet-4-5-20250929", + api: "anthropic-messages", + baseUrl: "https://clawrouter.example/v1/native/anthropic", + headers: { + Authorization: "Bearer clawrouter-test-key", + }, + }); const dynamic = resolveDiscoveredClawRouterModel({ baseUrl: provider.baseUrl, @@ -219,6 +227,7 @@ describe("clawrouter provider catalog", () => { expect( normalizeClawRouterResolvedModel({ ...anthropic, + baseUrl: provider.baseUrl, provider: "clawrouter", } as ProviderRuntimeModel), ).toBeUndefined(); diff --git a/extensions/clawrouter/provider-catalog.ts b/extensions/clawrouter/provider-catalog.ts index a54620e98628..252915f602d9 100644 --- a/extensions/clawrouter/provider-catalog.ts +++ b/extensions/clawrouter/provider-catalog.ts @@ -50,6 +50,7 @@ type RoutedModel = { type CatalogSnapshot = { apiBaseUrl: string; + authorizationHeader: string; modelsByRoute: Map; nativeModelIds: Map; }; @@ -223,6 +224,7 @@ function buildRoutedModel( function updateDiscoveredModels( rootUrl: string, providers: CatalogProvider[], + apiKey: string, ): ModelDefinitionConfig[] { const models = new Map(); const modelsByRoute = new Map(); @@ -246,6 +248,7 @@ function updateDiscoveredModels( // Keeping older credential-scoped catalogs would leak stale grants and grow forever. catalogSnapshot = { apiBaseUrl: `${rootUrl}/v1`, + authorizationHeader: `Bearer ${apiKey}`, modelsByRoute, nativeModelIds, }; @@ -278,7 +281,7 @@ export async function buildClawRouterProviderConfig(params: { api: "openai-responses", apiKey: params.apiKey, authHeader: true, - models: updateDiscoveredModels(rootUrl, providers), + models: updateDiscoveredModels(rootUrl, providers, params.apiKey), }; } @@ -297,8 +300,22 @@ export function resolveDiscoveredClawRouterModel(params: { export function normalizeClawRouterResolvedModel( model: ProviderRuntimeModel, ): ProviderRuntimeModel | undefined { - const upstreamModel = catalogSnapshot?.nativeModelIds.get(routeKey(model.baseUrl, model.id)); - return upstreamModel && upstreamModel !== model.id ? { ...model, id: upstreamModel } : undefined; + const discovered = catalogSnapshot?.modelsByRoute.get(routeKey(model.baseUrl, model.id)); + if (!catalogSnapshot || !discovered) { + return undefined; + } + const discoveredBaseUrl = discovered.baseUrl ?? catalogSnapshot.apiBaseUrl; + const upstreamModel = catalogSnapshot.nativeModelIds.get(routeKey(discoveredBaseUrl, model.id)); + return { + ...model, + api: discovered.api ?? model.api, + baseUrl: discoveredBaseUrl, + headers: { + ...model.headers, + Authorization: catalogSnapshot.authorizationHeader, + }, + ...(upstreamModel && upstreamModel !== model.id ? { id: upstreamModel } : {}), + }; } export function clearClawRouterCatalogForTests(): void {