From f4446117d92202bc1da71321c4b3662f7f21beb6 Mon Sep 17 00:00:00 2001 From: Hanbin Lee Date: Wed, 27 May 2026 23:49:15 +0900 Subject: [PATCH] fix(xiaomi): add CN token-plan cluster as last fallback When tp- keys fail discovery on SGP and AMS, try the CN cluster as a final fallback. CN-scoped token-plan keys previously received 401 because only SGP and AMS were attempted. Constraint: Xiaomi issues region-scoped tp- keys that only work against their assigned cluster. Rejected: Trying CN first (as PR #1416) | SGP/AMS cover majority; CN is the least common region, so last fallback is appropriate. Confidence: high Scope-risk: narrow Directive: If Xiaomi adds more token-plan regions, append here. Tested: bun check + issue-772 tests pass Not-tested: Actual CN tp- key against token-plan-cn endpoint --- .../ai/src/provider-models/openai-compat.ts | 75 +++++++++---------- 1 file changed, 34 insertions(+), 41 deletions(-) diff --git a/packages/ai/src/provider-models/openai-compat.ts b/packages/ai/src/provider-models/openai-compat.ts index c9ea3da72..897cd7f71 100644 --- a/packages/ai/src/provider-models/openai-compat.ts +++ b/packages/ai/src/provider-models/openai-compat.ts @@ -1475,58 +1475,51 @@ export function xiaomiModelManagerOptions( ): ModelManagerOptions<"openai-completions"> { const apiKey = config?.apiKey; // Xiaomi splits API keys across two backends: standard `sk-` keys hit - // api.xiaomimimo.com; "token plan" `tp-` keys hit either the SG or EU - // token-plan host. Try SGP first; if discovery fails, retry AMS. - const TOKEN_PLAN_SGP_BASE_URL = "https://token-plan-sgp.xiaomimimo.com/v1"; - const TOKEN_PLAN_AMS_BASE_URL = "https://token-plan-ams.xiaomimimo.com/v1"; - const defaultBaseUrl = apiKey?.startsWith("tp-") ? TOKEN_PLAN_SGP_BASE_URL : "https://api.xiaomimimo.com/v1"; - // Token-plan keys always use the TP baseUrl; config?.baseUrl (from catalog) + // api.xiaomimimo.com; "token plan" `tp-` keys are scoped to a regional + // cluster and are tried in order until discovery succeeds. + const TOKEN_PLAN_BASE_URLS = [ + "https://token-plan-sgp.xiaomimimo.com/v1", + "https://token-plan-ams.xiaomimimo.com/v1", + "https://token-plan-cn.xiaomimimo.com/v1", + ] as const; + const STANDARD_BASE_URL = "https://api.xiaomimimo.com/v1"; + const isTokenPlanKey = apiKey?.startsWith("tp-"); + // Token-plan keys always use a TP cluster; config?.baseUrl (from catalog) // would incorrectly pin to the standard endpoint (api.xiaomimimo.com). - const baseUrl = apiKey?.startsWith("tp-") ? defaultBaseUrl : (config?.baseUrl ?? defaultBaseUrl); + const baseUrl = isTokenPlanKey ? TOKEN_PLAN_BASE_URLS[0] : (config?.baseUrl ?? STANDARD_BASE_URL); const references = createBundledReferenceMap<"openai-completions">("xiaomi"); + const fetchModels = (url: string) => + fetchOpenAICompatibleModels({ + api: "openai-completions", + provider: "xiaomi", + baseUrl: url, + apiKey, + filterModel: (_entry, model) => !model.id.includes("-tts"), + mapModel: (entry, defaults) => { + const reference = references.get(defaults.id); + const model = mapWithBundledReference(entry, defaults, reference); + return { + ...model, + name: toModelName(entry.display_name, model.name), + }; + }, + }); return { providerId: "xiaomi", ...(apiKey && { fetchDynamicModels: async () => { - const sgpResult = await fetchOpenAICompatibleModels({ - api: "openai-completions", - provider: "xiaomi", - baseUrl, - apiKey, - filterModel: (_entry, model) => !model.id.includes("-tts"), - mapModel: (entry, defaults) => { - const reference = references.get(defaults.id); - const model = mapWithBundledReference(entry, defaults, reference); - return { - ...model, - name: toModelName(entry.display_name, model.name), - }; - }, - }); - if (sgpResult || !apiKey?.startsWith("tp-")) { - return sgpResult; + if (!isTokenPlanKey) { + return fetchModels(baseUrl); } - // Token-plan discovery failed with SGP; retry with AMS - return fetchOpenAICompatibleModels({ - api: "openai-completions", - provider: "xiaomi", - baseUrl: TOKEN_PLAN_AMS_BASE_URL, - apiKey, - filterModel: (_entry, model) => !model.id.includes("-tts"), - mapModel: (entry, defaults) => { - const reference = references.get(defaults.id); - const model = mapWithBundledReference(entry, defaults, reference); - return { - ...model, - name: toModelName(entry.display_name, model.name), - }; - }, - }); + for (const url of TOKEN_PLAN_BASE_URLS) { + const result = await fetchModels(url); + if (result) return result; + } + return null; }, }), }; } - // --------------------------------------------------------------------------- // 21. LiteLLM // ---------------------------------------------------------------------------