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.
This commit is contained in:
@@ -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 },
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -80,6 +80,8 @@ export type AnthropicHeaderOptions = {
|
||||
stream?: boolean;
|
||||
modelHeaders?: Record<string, string>;
|
||||
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<s
|
||||
"Anthropic-Beta": betaHeader,
|
||||
"User-Agent": userAgent,
|
||||
};
|
||||
} else if (!isAnthropicApiBaseUrl(options.baseUrl)) {
|
||||
} else if (!options.usesApiKeyAuth && !isAnthropicApiBaseUrl(options.baseUrl)) {
|
||||
return {
|
||||
...modelHeaders,
|
||||
Accept: acceptHeader,
|
||||
@@ -1576,6 +1578,7 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
|
||||
stream,
|
||||
modelHeaders: mergeHeaders(model.headers, foundryCustomHeaders, headers, dynamicHeaders),
|
||||
isCloudflareAiGateway: model.provider === "cloudflare-ai-gateway",
|
||||
usesApiKeyAuth: compat.usesApiKeyAuth,
|
||||
});
|
||||
|
||||
if (model.provider === "cloudflare-ai-gateway") {
|
||||
|
||||
@@ -791,6 +791,8 @@ export interface AnthropicCompat {
|
||||
supportsEagerToolInputStreaming?: boolean;
|
||||
/** Whether long prompt-cache retention (`ttl: "1h"`) is supported. Default: true for canonical Anthropic API. */
|
||||
supportsLongCacheRetention?: boolean;
|
||||
/** Use X-Api-Key header instead of Authorization: Bearer for non-Anthropic endpoints. */
|
||||
usesApiKeyAuth?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user