From 1a2dffd021e7f14765b4b8cc90c84b1e80cf904c Mon Sep 17 00:00:00 2001 From: Hanbin Lee Date: Mon, 25 May 2026 00:47:18 +0900 Subject: [PATCH] fix(xiaomi): use token-plan base URL for tp- API keys Root cause: getProviderBaseUrl('xiaomi') returned the standard endpoint baseUrl from the bundled catalog (api.xiaomimimo.com/anthropic), overriding the token-plan URL (token-plan-ams.xiaomimimo.com/anthropic) that should be used for tp- prefixed keys. Fix: - xiaomiModelManagerOptions: for tp- keys, compute baseUrl from the key prefix alone, ignoring config?.baseUrl from the bundled catalog. - Add usesApiKeyAuth flag to AnthropicCompat / AnthropicHeaderOptions. - Xiaomi models set usesApiKeyAuth: true so the anthropic provider sends X-Api-Key header (matching what the Xiaomi validation code expects) instead of Authorization: Bearer. --- packages/ai/src/provider-models/openai-compat.ts | 11 +++++++---- packages/ai/src/providers/anthropic.ts | 5 ++++- packages/ai/src/types.ts | 2 ++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/ai/src/provider-models/openai-compat.ts b/packages/ai/src/provider-models/openai-compat.ts index 7239e6dd5..c20ecd0fd 100644 --- a/packages/ai/src/provider-models/openai-compat.ts +++ b/packages/ai/src/provider-models/openai-compat.ts @@ -1410,13 +1410,15 @@ export function xiaomiModelManagerOptions( config?: XiaomiModelManagerConfig, ): ModelManagerOptions<"anthropic-messages"> { const apiKey = config?.apiKey; - // Xiaomi splits API keys across two backends: standard `sk-` keys hit - // api.xiaomimimo.com; "token plan" `tp-` keys hit the EU token-plan host. - // Both expose the same Anthropic-compat layout under /anthropic/v1/*. const defaultBaseUrl = apiKey?.startsWith("tp-") ? "https://token-plan-ams.xiaomimimo.com/anthropic" : "https://api.xiaomimimo.com/anthropic"; - const baseUrl = normalizeAnthropicBaseUrl(config?.baseUrl, defaultBaseUrl); + // Token-plan keys always use the TP baseUrl; config?.baseUrl (from catalog) + // would incorrectly pin to the standard endpoint (api.xiaomimimo.com). + const baseUrl = normalizeAnthropicBaseUrl( + apiKey?.startsWith("tp-") ? undefined : config?.baseUrl, + defaultBaseUrl, + ); // Xiaomi hosts chat completions under /anthropic/* but exposes model // discovery at the OpenAI-style /v1/models endpoint on the root host. const discoveryRoot = baseUrl.endsWith("/anthropic") ? baseUrl.slice(0, -"/anthropic".length) : baseUrl; @@ -1439,6 +1441,7 @@ export function xiaomiModelManagerOptions( ...model, name: toModelName(entry.display_name, model.name), baseUrl, + compat: { ...model.compat, usesApiKeyAuth: true }, }; }, }), diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts index 9cfd7d310..ea5ecf7dd 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/providers/anthropic.ts @@ -80,6 +80,8 @@ export type AnthropicHeaderOptions = { stream?: boolean; modelHeaders?: Record; isCloudflareAiGateway?: boolean; + /** Use X-Api-Key header instead of Authorization: Bearer. */ + usesApiKeyAuth?: boolean; }; export function normalizeAnthropicBaseUrl(baseUrl?: string): string | undefined { @@ -182,7 +184,7 @@ export function buildAnthropicHeaders(options: AnthropicHeaderOptions): Record