From ae0bbe32fc112cba8e3c2dcc921493eed32978b4 Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Wed, 29 Apr 2026 13:54:43 +0200 Subject: [PATCH] =?UTF-8?q?feat(providers):=20add=20xiaomi=20direct=20API?= =?UTF-8?q?=20(token-plan-{ams,sgp,cn})=20=E2=80=94=20additive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds direct xiaomi token-plan API access alongside the existing OpenRouter-routed xiaomi entries. ADDITIVE only — OpenRouter cleanup is a separate follow-up. Three new region providers: - xiaomi-token-plan-ams (Amsterdam, default for plain `xiaomi`) - xiaomi-token-plan-sgp (Singapore) - xiaomi-token-plan-cn (China) All use Anthropic Messages API. Env-var resolution: XIAOMI_API_KEY → XIAOMI_TOKEN_PLAN_API_KEY → MIMO_API_KEY (in that fallback order). Three xiaomi MiMo models registered under each direct provider: - mimo-v2-flash (256k ctx, 64k output, text-only, reasoning) - mimo-v2-omni (256k ctx, 128k output, text+image, reasoning) - mimo-v2-pro (1M ctx, 128k output, text-only, reasoning) Same model literals × 4 provider keys, different baseUrls per region. Test count assertion bumped 22 → 26 providers. Co-Authored-By: Claude Sonnet 4.6 --- packages/mcp-server/src/tool-credentials.ts | 3 + packages/pi-ai/src/env-api-keys.ts | 13 ++ packages/pi-ai/src/models.generated.test.ts | 10 +- packages/pi-ai/src/models.generated.ts | 221 ++++++++++++++++++ packages/pi-ai/src/types.ts | 3 + .../pi-ai/src/web-runtime-env-api-keys.ts | 13 ++ 6 files changed, 260 insertions(+), 3 deletions(-) diff --git a/packages/mcp-server/src/tool-credentials.ts b/packages/mcp-server/src/tool-credentials.ts index e84d9f074..a5dee5301 100644 --- a/packages/mcp-server/src/tool-credentials.ts +++ b/packages/mcp-server/src/tool-credentials.ts @@ -18,6 +18,9 @@ const AUTH_ENV_KEYS = [ ["openrouter", "OPENROUTER_API_KEY"], ["mistral", "MISTRAL_API_KEY"], ["xiaomi", "XIAOMI_API_KEY"], + ["xiaomi-token-plan-ams", "XIAOMI_API_KEY"], + ["xiaomi-token-plan-sgp", "XIAOMI_API_KEY"], + ["xiaomi-token-plan-cn", "XIAOMI_API_KEY"], ["ollama-cloud", "OLLAMA_API_KEY"], ["custom-openai", "CUSTOM_OPENAI_API_KEY"], ["cerebras", "CEREBRAS_API_KEY"], diff --git a/packages/pi-ai/src/env-api-keys.ts b/packages/pi-ai/src/env-api-keys.ts index 41513e0ee..0d76cb1c5 100644 --- a/packages/pi-ai/src/env-api-keys.ts +++ b/packages/pi-ai/src/env-api-keys.ts @@ -99,6 +99,16 @@ export function getEnvApiKey(provider: any): string | undefined { } } + // Xiaomi MiMo token-plan providers share a single key; allow legacy fallbacks. + if ( + provider === "xiaomi" || + provider === "xiaomi-token-plan-ams" || + provider === "xiaomi-token-plan-sgp" || + provider === "xiaomi-token-plan-cn" + ) { + return process.env.XIAOMI_API_KEY || process.env.XIAOMI_TOKEN_PLAN_API_KEY || process.env.MIMO_API_KEY; + } + if (provider === "amazon-bedrock") { // Amazon Bedrock supports multiple credential sources: // 1. AWS_PROFILE - named profile from ~/.aws/credentials @@ -137,6 +147,9 @@ export function getEnvApiKey(provider: any): string | undefined { "opencode-go": "OPENCODE_API_KEY", "kimi-coding": "KIMI_API_KEY", xiaomi: "XIAOMI_API_KEY", + "xiaomi-token-plan-ams": "XIAOMI_API_KEY", + "xiaomi-token-plan-sgp": "XIAOMI_API_KEY", + "xiaomi-token-plan-cn": "XIAOMI_API_KEY", "alibaba-coding-plan": "ALIBABA_API_KEY", "alibaba-dashscope": "DASHSCOPE_API_KEY", ollama: "OLLAMA_API_KEY", diff --git a/packages/pi-ai/src/models.generated.test.ts b/packages/pi-ai/src/models.generated.test.ts index f5139db4a..7644e5ad8 100644 --- a/packages/pi-ai/src/models.generated.test.ts +++ b/packages/pi-ai/src/models.generated.test.ts @@ -257,9 +257,9 @@ describe("MODELS structural invariants", () => { // ═══════════════════════════════════════════════════════════════════════════ describe("MODELS registry shape", () => { - it("has exactly 22 providers", () => { + it("has exactly 26 providers", () => { const count = Object.keys(MODELS).length; - assert.equal(count, 22, `Expected 22 providers, got ${count}: ${Object.keys(MODELS).join(", ")}`); + assert.equal(count, 26, `Expected 26 providers, got ${count}: ${Object.keys(MODELS).join(", ")}`); }); it("has at least 200 models in total (sanity check)", () => { @@ -270,7 +270,7 @@ describe("MODELS registry shape", () => { assert.ok(total >= 200, `Registry has only ${total} models — unexpectedly small`); }); - it("all 22 expected providers are present", () => { + it("all 26 expected providers are present", () => { const expected = [ "amazon-bedrock", "anthropic", @@ -293,6 +293,10 @@ describe("MODELS registry shape", () => { "openrouter", "vercel-ai-gateway", "xai", + "xiaomi", + "xiaomi-token-plan-ams", + "xiaomi-token-plan-cn", + "xiaomi-token-plan-sgp", "zai", ]; const actual = Object.keys(MODELS).sort(); diff --git a/packages/pi-ai/src/models.generated.ts b/packages/pi-ai/src/models.generated.ts index 2c11c7a6a..ae5208156 100644 --- a/packages/pi-ai/src/models.generated.ts +++ b/packages/pi-ai/src/models.generated.ts @@ -14379,4 +14379,225 @@ export const MODELS = { maxTokens: 131072, } satisfies Model<"openai-completions">, }, + // ────────────────────────────────────────────────────────────────────────── + // Xiaomi MiMo direct token-plan providers (Anthropic Messages API). + // Additive to the existing OpenRouter-routed xiaomi entries above; users + // with a XIAOMI_API_KEY can hit these endpoints directly. + // - "xiaomi" → default region (Amsterdam) + // - "xiaomi-token-plan-ams" → Amsterdam (Europe) + // - "xiaomi-token-plan-sgp" → Singapore + // - "xiaomi-token-plan-cn" → China + // ────────────────────────────────────────────────────────────────────────── + "xiaomi": { + "mimo-v2-flash": { + id: "mimo-v2-flash", + name: "MiMo-V2-Flash", + api: "anthropic-messages", + provider: "xiaomi", + baseUrl: "https://token-plan-ams.xiaomimimo.com/anthropic", + reasoning: true, + input: ["text"], + cost: { + input: 0.1, + output: 0.3, + cacheRead: 0.01, + cacheWrite: 0, + }, + contextWindow: 256000, + maxTokens: 64000, + } satisfies Model<"anthropic-messages">, + "mimo-v2-omni": { + id: "mimo-v2-omni", + name: "MiMo-V2-Omni", + api: "anthropic-messages", + provider: "xiaomi", + baseUrl: "https://token-plan-ams.xiaomimimo.com/anthropic", + reasoning: true, + input: ["text", "image"], + cost: { + input: 0.4, + output: 2, + cacheRead: 0.08, + cacheWrite: 0, + }, + contextWindow: 256000, + maxTokens: 128000, + } satisfies Model<"anthropic-messages">, + "mimo-v2-pro": { + id: "mimo-v2-pro", + name: "MiMo-V2-Pro", + api: "anthropic-messages", + provider: "xiaomi", + baseUrl: "https://token-plan-ams.xiaomimimo.com/anthropic", + reasoning: true, + input: ["text"], + cost: { + input: 1, + output: 3, + cacheRead: 0.2, + cacheWrite: 0, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"anthropic-messages">, + }, + "xiaomi-token-plan-ams": { + "mimo-v2-flash": { + id: "mimo-v2-flash", + name: "MiMo-V2-Flash", + api: "anthropic-messages", + provider: "xiaomi-token-plan-ams", + baseUrl: "https://token-plan-ams.xiaomimimo.com/anthropic", + reasoning: true, + input: ["text"], + cost: { + input: 0.1, + output: 0.3, + cacheRead: 0.01, + cacheWrite: 0, + }, + contextWindow: 256000, + maxTokens: 64000, + } satisfies Model<"anthropic-messages">, + "mimo-v2-omni": { + id: "mimo-v2-omni", + name: "MiMo-V2-Omni", + api: "anthropic-messages", + provider: "xiaomi-token-plan-ams", + baseUrl: "https://token-plan-ams.xiaomimimo.com/anthropic", + reasoning: true, + input: ["text", "image"], + cost: { + input: 0.4, + output: 2, + cacheRead: 0.08, + cacheWrite: 0, + }, + contextWindow: 256000, + maxTokens: 128000, + } satisfies Model<"anthropic-messages">, + "mimo-v2-pro": { + id: "mimo-v2-pro", + name: "MiMo-V2-Pro", + api: "anthropic-messages", + provider: "xiaomi-token-plan-ams", + baseUrl: "https://token-plan-ams.xiaomimimo.com/anthropic", + reasoning: true, + input: ["text"], + cost: { + input: 1, + output: 3, + cacheRead: 0.2, + cacheWrite: 0, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"anthropic-messages">, + }, + "xiaomi-token-plan-sgp": { + "mimo-v2-flash": { + id: "mimo-v2-flash", + name: "MiMo-V2-Flash", + api: "anthropic-messages", + provider: "xiaomi-token-plan-sgp", + baseUrl: "https://token-plan-sgp.xiaomimimo.com/anthropic", + reasoning: true, + input: ["text"], + cost: { + input: 0.1, + output: 0.3, + cacheRead: 0.01, + cacheWrite: 0, + }, + contextWindow: 256000, + maxTokens: 64000, + } satisfies Model<"anthropic-messages">, + "mimo-v2-omni": { + id: "mimo-v2-omni", + name: "MiMo-V2-Omni", + api: "anthropic-messages", + provider: "xiaomi-token-plan-sgp", + baseUrl: "https://token-plan-sgp.xiaomimimo.com/anthropic", + reasoning: true, + input: ["text", "image"], + cost: { + input: 0.4, + output: 2, + cacheRead: 0.08, + cacheWrite: 0, + }, + contextWindow: 256000, + maxTokens: 128000, + } satisfies Model<"anthropic-messages">, + "mimo-v2-pro": { + id: "mimo-v2-pro", + name: "MiMo-V2-Pro", + api: "anthropic-messages", + provider: "xiaomi-token-plan-sgp", + baseUrl: "https://token-plan-sgp.xiaomimimo.com/anthropic", + reasoning: true, + input: ["text"], + cost: { + input: 1, + output: 3, + cacheRead: 0.2, + cacheWrite: 0, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"anthropic-messages">, + }, + "xiaomi-token-plan-cn": { + "mimo-v2-flash": { + id: "mimo-v2-flash", + name: "MiMo-V2-Flash", + api: "anthropic-messages", + provider: "xiaomi-token-plan-cn", + baseUrl: "https://token-plan-cn.xiaomimimo.com/anthropic", + reasoning: true, + input: ["text"], + cost: { + input: 0.1, + output: 0.3, + cacheRead: 0.01, + cacheWrite: 0, + }, + contextWindow: 256000, + maxTokens: 64000, + } satisfies Model<"anthropic-messages">, + "mimo-v2-omni": { + id: "mimo-v2-omni", + name: "MiMo-V2-Omni", + api: "anthropic-messages", + provider: "xiaomi-token-plan-cn", + baseUrl: "https://token-plan-cn.xiaomimimo.com/anthropic", + reasoning: true, + input: ["text", "image"], + cost: { + input: 0.4, + output: 2, + cacheRead: 0.08, + cacheWrite: 0, + }, + contextWindow: 256000, + maxTokens: 128000, + } satisfies Model<"anthropic-messages">, + "mimo-v2-pro": { + id: "mimo-v2-pro", + name: "MiMo-V2-Pro", + api: "anthropic-messages", + provider: "xiaomi-token-plan-cn", + baseUrl: "https://token-plan-cn.xiaomimimo.com/anthropic", + reasoning: true, + input: ["text"], + cost: { + input: 1, + output: 3, + cacheRead: 0.2, + cacheWrite: 0, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"anthropic-messages">, + }, } as const; diff --git a/packages/pi-ai/src/types.ts b/packages/pi-ai/src/types.ts index c0f258197..b57a06789 100644 --- a/packages/pi-ai/src/types.ts +++ b/packages/pi-ai/src/types.ts @@ -43,6 +43,9 @@ export type KnownProvider = | "opencode-go" | "kimi-coding" | "xiaomi" + | "xiaomi-token-plan-ams" + | "xiaomi-token-plan-sgp" + | "xiaomi-token-plan-cn" | "alibaba-coding-plan" | "alibaba-dashscope" | "ollama" diff --git a/packages/pi-ai/src/web-runtime-env-api-keys.ts b/packages/pi-ai/src/web-runtime-env-api-keys.ts index 107485c15..2bf827236 100644 --- a/packages/pi-ai/src/web-runtime-env-api-keys.ts +++ b/packages/pi-ai/src/web-runtime-env-api-keys.ts @@ -47,6 +47,16 @@ export function getEnvApiKey(provider: string): string | undefined { } } + // Xiaomi MiMo token-plan providers share a single key; allow legacy fallbacks. + if ( + provider === "xiaomi" || + provider === "xiaomi-token-plan-ams" || + provider === "xiaomi-token-plan-sgp" || + provider === "xiaomi-token-plan-cn" + ) { + return process.env.XIAOMI_API_KEY || process.env.XIAOMI_TOKEN_PLAN_API_KEY || process.env.MIMO_API_KEY; + } + if ( provider === "amazon-bedrock" && ( @@ -79,6 +89,9 @@ export function getEnvApiKey(provider: string): string | undefined { "opencode-go": "OPENCODE_API_KEY", "kimi-coding": "KIMI_API_KEY", xiaomi: "XIAOMI_API_KEY", + "xiaomi-token-plan-ams": "XIAOMI_API_KEY", + "xiaomi-token-plan-sgp": "XIAOMI_API_KEY", + "xiaomi-token-plan-cn": "XIAOMI_API_KEY", "alibaba-coding-plan": "ALIBABA_API_KEY", };