From a44622670f8626b84f42885942c9ca340f823b5a Mon Sep 17 00:00:00 2001 From: Jake Jia <87770044+Phoen1xCode@users.noreply.github.com> Date: Sat, 2 May 2026 06:46:05 +0800 Subject: [PATCH] feat(ai): add Xiaomi MiMo provider (#4005) * fix(ai): include minimax-cn in cross-provider-handoff matrix * feat(ai): add Xiaomi MiMo provider Adds Xiaomi MiMo as an openai-completions-compatible provider. - packages/ai: register provider in types/KnownProvider, env-api-keys (XIAOMI_API_KEY), generate-models, models.generated.ts, overflow util, README, CHANGELOG - packages/ai/test: extend stream, tokens, abort, empty, context-overflow, overflow, image-tool-result, tool-call-without-result, total-tokens, unicode-surrogate, cross-provider-handoff matrices with Xiaomi - packages/coding-agent: default model (mimo-v2.5-pro), display name (Xiaomi MiMo), CLI env var docs, README, docs/providers.md closes #3912 --------- Co-authored-by: Mario Zechner --- packages/ai/CHANGELOG.md | 6 ++ packages/ai/README.md | 1 + packages/ai/scripts/generate-models.ts | 26 ++++++ packages/ai/src/env-api-keys.ts | 1 + packages/ai/src/models.generated.ts | 87 +++++++++++++++++++ packages/ai/src/types.ts | 3 +- packages/ai/src/utils/overflow.ts | 15 ++++ packages/ai/test/abort.test.ts | 12 +++ packages/ai/test/context-overflow.test.ts | 19 ++++ .../ai/test/cross-provider-handoff.test.ts | 3 + packages/ai/test/empty.test.ts | 20 +++++ packages/ai/test/image-tool-result.test.ts | 12 +++ packages/ai/test/overflow.test.ts | 35 ++++++++ packages/ai/test/stream.test.ts | 18 ++++ packages/ai/test/tokens.test.ts | 8 ++ .../ai/test/tool-call-without-result.test.ts | 8 ++ packages/ai/test/total-tokens.test.ts | 23 +++++ packages/ai/test/unicode-surrogate.test.ts | 16 ++++ packages/coding-agent/README.md | 1 + packages/coding-agent/docs/providers.md | 4 +- packages/coding-agent/src/cli/args.ts | 1 + .../coding-agent/src/core/model-resolver.ts | 1 + .../src/core/provider-display-names.ts | 1 + 23 files changed, 319 insertions(+), 2 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index d23dbaa6..080d5031 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,11 +2,17 @@ ## [Unreleased] +<<<<<<< feat/add-xiaomi-provider +### Added + +- Added Xiaomi MiMo provider (openai-completions compatible) with `XIAOMI_API_KEY` authentication. +======= ## [0.71.1] - 2026-05-01 ### Added - Added `websocket-cached` transport support for OpenAI Codex Responses used with ChatGPT subscription auth. This keeps the same WebSocket open for a session and, after the first request, sends only new conversation items instead of resending the full chat history when possible. +>>>>>>> main ## [0.71.0] - 2026-04-30 diff --git a/packages/ai/README.md b/packages/ai/README.md index baa2e15b..37f7b9d9 100644 --- a/packages/ai/README.md +++ b/packages/ai/README.md @@ -69,6 +69,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an - **OpenCode Go** - **Fireworks** (uses Anthropic-compatible API) - **Kimi For Coding** (Moonshot AI, uses Anthropic-compatible API) +- **Xiaomi MiMo** (uses OpenAI-compatible API) - **Any OpenAI-compatible API**: Ollama, vLLM, LM Studio, etc. ## Installation diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index b3927381..c94259a3 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -834,6 +834,32 @@ async function loadModelsDevData(): Promise[]> { } } + // Process Xiaomi MiMo models + if (data.xiaomi?.models) { + for (const [modelId, model] of Object.entries(data.xiaomi.models)) { + const m = model as ModelsDevModel; + if (m.tool_call !== true) continue; + + models.push({ + id: modelId, + name: m.name || modelId, + api: "openai-completions", + provider: "xiaomi", + baseUrl: "https://api.xiaomimimo.com/v1", + reasoning: m.reasoning === true, + input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], + cost: { + input: m.cost?.input || 0, + output: m.cost?.output || 0, + cacheRead: m.cost?.cache_read || 0, + cacheWrite: m.cost?.cache_write || 0, + }, + contextWindow: m.limit?.context || 4096, + maxTokens: m.limit?.output || 4096, + }); + } + } + console.log(`Loaded ${models.length} tool-capable models from models.dev`); return models; } catch (error) { diff --git a/packages/ai/src/env-api-keys.ts b/packages/ai/src/env-api-keys.ts index cfef2364..0f41d542 100644 --- a/packages/ai/src/env-api-keys.ts +++ b/packages/ai/src/env-api-keys.ts @@ -122,6 +122,7 @@ function getApiKeyEnvVars(provider: string): readonly string[] | undefined { "kimi-coding": "KIMI_API_KEY", "cloudflare-workers-ai": "CLOUDFLARE_API_KEY", "cloudflare-ai-gateway": "CLOUDFLARE_API_KEY", + xiaomi: "XIAOMI_API_KEY", }; const envVar = envMap[provider]; diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index 78f778aa..ae2ffe06 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -16231,6 +16231,93 @@ export const MODELS = { maxTokens: 4096, } satisfies Model<"openai-completions">, }, + "xiaomi": { + "mimo-v2-flash": { + id: "mimo-v2-flash", + name: "MiMo-V2-Flash", + api: "openai-completions", + provider: "xiaomi", + baseUrl: "https://api.xiaomimimo.com/v1", + reasoning: true, + input: ["text"], + cost: { + input: 0.1, + output: 0.3, + cacheRead: 0.01, + cacheWrite: 0, + }, + contextWindow: 256000, + maxTokens: 64000, + } satisfies Model<"openai-completions">, + "mimo-v2-omni": { + id: "mimo-v2-omni", + name: "MiMo-V2-Omni", + api: "openai-completions", + provider: "xiaomi", + baseUrl: "https://api.xiaomimimo.com/v1", + reasoning: true, + input: ["text", "image"], + cost: { + input: 0.4, + output: 2, + cacheRead: 0.08, + cacheWrite: 0, + }, + contextWindow: 256000, + maxTokens: 128000, + } satisfies Model<"openai-completions">, + "mimo-v2-pro": { + id: "mimo-v2-pro", + name: "MiMo-V2-Pro", + api: "openai-completions", + provider: "xiaomi", + baseUrl: "https://api.xiaomimimo.com/v1", + reasoning: true, + input: ["text"], + cost: { + input: 1, + output: 3, + cacheRead: 0.2, + cacheWrite: 0, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"openai-completions">, + "mimo-v2.5": { + id: "mimo-v2.5", + name: "MiMo-V2.5", + api: "openai-completions", + provider: "xiaomi", + baseUrl: "https://api.xiaomimimo.com/v1", + reasoning: true, + input: ["text"], + cost: { + input: 0.4, + output: 2, + cacheRead: 0.08, + cacheWrite: 0, + }, + contextWindow: 1048576, + maxTokens: 131072, + } satisfies Model<"openai-completions">, + "mimo-v2.5-pro": { + id: "mimo-v2.5-pro", + name: "MiMo-V2.5-Pro", + api: "openai-completions", + provider: "xiaomi", + baseUrl: "https://api.xiaomimimo.com/v1", + reasoning: true, + input: ["text", "image"], + cost: { + input: 1, + output: 3, + cacheRead: 0.2, + cacheWrite: 0, + }, + contextWindow: 1048576, + maxTokens: 131072, + } satisfies Model<"openai-completions">, + }, "zai": { "glm-4.5-air": { id: "glm-4.5-air", diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts index ae89baf7..6a74c6c3 100644 --- a/packages/ai/src/types.ts +++ b/packages/ai/src/types.ts @@ -42,7 +42,8 @@ export type KnownProvider = | "opencode-go" | "kimi-coding" | "cloudflare-workers-ai" - | "cloudflare-ai-gateway"; + | "cloudflare-ai-gateway" + | "xiaomi"; export type Provider = KnownProvider | string; export type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh"; diff --git a/packages/ai/src/utils/overflow.ts b/packages/ai/src/utils/overflow.ts index 314febf2..b06a3fd1 100644 --- a/packages/ai/src/utils/overflow.ts +++ b/packages/ai/src/utils/overflow.ts @@ -23,6 +23,9 @@ import type { AssistantMessage } from "../types.js"; * - Cerebras: "400/413 status code (no body)" * - Mistral: "Prompt contains X tokens ... too large for model with Y maximum context length" * - z.ai: Does NOT error, accepts overflow silently - handled via usage.input > contextWindow + * - Xiaomi MiMo: Truncates input to fill contextWindow exactly, then returns finish_reason "length" + * with output=0 (no room left to generate). Detected via stopReason "length" + zero output + + * input filling the context window. * - Ollama: Some deployments truncate silently, others return errors like "prompt too long; exceeded max context length by X tokens" */ const OVERFLOW_PATTERNS = [ @@ -90,6 +93,8 @@ const NON_OVERFLOW_PATTERNS = [ * **Unreliable detection:** * - z.ai: Sometimes accepts overflow silently (detectable via usage.input > contextWindow), * sometimes returns rate limit errors. Pass contextWindow param to detect silent overflow. + * - Xiaomi MiMo: Truncates input to fit contextWindow then returns stopReason "length" with + * output=0. Pass contextWindow param to detect via the "filled context + zero output" signal. * - Ollama: May truncate input silently for some setups, but may also return explicit * overflow errors that match the patterns above. Silent truncation still cannot be * detected here because we do not know the expected token count. @@ -127,6 +132,16 @@ export function isContextOverflow(message: AssistantMessage, contextWindow?: num } } + // Case 3: Length-stop overflow (Xiaomi MiMo style) - server truncates oversized input + // to fit the context window, leaving no room for output. Returns stopReason "length" + // with output=0 and input+cacheRead filling the context window. + if (contextWindow && message.stopReason === "length" && message.usage.output === 0) { + const inputTokens = message.usage.input + message.usage.cacheRead; + if (inputTokens >= contextWindow * 0.99) { + return true; + } + } + return false; } diff --git a/packages/ai/test/abort.test.ts b/packages/ai/test/abort.test.ts index 8a7fac4b..8d0311f1 100644 --- a/packages/ai/test/abort.test.ts +++ b/packages/ai/test/abort.test.ts @@ -190,6 +190,18 @@ describe("AI Providers Abort Tests", () => { }); }); + describe.skipIf(!process.env.XIAOMI_API_KEY)("Xiaomi MiMo Provider Abort", () => { + const llm = getModel("xiaomi", "mimo-v2.5-pro"); + + it("should abort mid-stream", { retry: 3 }, async () => { + await testAbortSignal(llm); + }); + + it("should handle immediate abort", { retry: 3 }, async () => { + await testImmediateAbort(llm); + }); + }); + describe.skipIf(!process.env.KIMI_API_KEY)("Kimi For Coding Provider Abort", () => { const llm = getModel("kimi-coding", "kimi-k2-thinking"); diff --git a/packages/ai/test/context-overflow.test.ts b/packages/ai/test/context-overflow.test.ts index bd977e79..9db4ac50 100644 --- a/packages/ai/test/context-overflow.test.ts +++ b/packages/ai/test/context-overflow.test.ts @@ -389,6 +389,25 @@ describe("Context overflow error handling", () => { }, 120000); }); + // ============================================================================= + // Xiaomi MiMo + // ============================================================================= + + describe.skipIf(!process.env.XIAOMI_API_KEY)("Xiaomi MiMo", () => { + // Xiaomi silently truncates oversized input to fill the context window exactly, + // then returns finish_reason "length" with output=0 (no room left to generate). + // This is a detectable overflow signal but uses stopReason "length" rather than "error". + it("mimo-v2.5-pro - should detect overflow via isContextOverflow", async () => { + const model = getModel("xiaomi", "mimo-v2.5-pro"); + const result = await testContextOverflow(model, process.env.XIAOMI_API_KEY!); + logResult(result); + + expect(result.stopReason).toBe("length"); + expect(result.usage.output).toBe(0); + expect(isContextOverflow(result.response, model.contextWindow)).toBe(true); + }, 120000); + }); + // ============================================================================= // Kimi For Coding // ============================================================================= diff --git a/packages/ai/test/cross-provider-handoff.test.ts b/packages/ai/test/cross-provider-handoff.test.ts index f1c98080..d376725c 100644 --- a/packages/ai/test/cross-provider-handoff.test.ts +++ b/packages/ai/test/cross-provider-handoff.test.ts @@ -113,6 +113,7 @@ const PROVIDER_MODEL_PAIRS: ProviderModelPair[] = [ { provider: "mistral", model: "devstral-medium-latest", label: "mistral-devstral-medium" }, // MiniMax { provider: "minimax", model: "MiniMax-M2.7", label: "minimax-m2.7" }, + { provider: "minimax-cn", model: "MiniMax-M2.7", label: "minimax-m2.7" }, // OpenCode Zen { provider: "opencode", model: "big-pickle", label: "zen-big-pickle" }, { provider: "opencode", model: "claude-sonnet-4-5", label: "zen-claude-sonnet-4-5" }, @@ -123,6 +124,8 @@ const PROVIDER_MODEL_PAIRS: ProviderModelPair[] = [ // OpenCode Go { provider: "opencode-go", model: "kimi-k2.5", label: "go-kimi-k2.5" }, { provider: "opencode-go", model: "minimax-m2.5", label: "go-minimax-m2.5" }, + // Xiaomi MiMo + { provider: "xiaomi", model: "mimo-v2.5-pro", label: "xiaomi-mimo-v2.5-pro" }, ]; // Cached context structure diff --git a/packages/ai/test/empty.test.ts b/packages/ai/test/empty.test.ts index c451eddf..d34c14b3 100644 --- a/packages/ai/test/empty.test.ts +++ b/packages/ai/test/empty.test.ts @@ -427,6 +427,26 @@ describe("AI Providers Empty Message Tests", () => { }); }); + describe.skipIf(!process.env.XIAOMI_API_KEY)("Xiaomi MiMo Provider Empty Messages", () => { + const llm = getModel("xiaomi", "mimo-v2.5-pro"); + + it("should handle empty content array", { retry: 3, timeout: 30000 }, async () => { + await testEmptyMessage(llm); + }); + + it("should handle empty string content", { retry: 3, timeout: 30000 }, async () => { + await testEmptyStringMessage(llm); + }); + + it("should handle whitespace-only content", { retry: 3, timeout: 30000 }, async () => { + await testWhitespaceOnlyMessage(llm); + }); + + it("should handle empty assistant message in conversation", { retry: 3, timeout: 30000 }, async () => { + await testEmptyAssistantMessage(llm); + }); + }); + describe.skipIf(!process.env.KIMI_API_KEY)("Kimi For Coding Provider Empty Messages", () => { const llm = getModel("kimi-coding", "kimi-k2-thinking"); diff --git a/packages/ai/test/image-tool-result.test.ts b/packages/ai/test/image-tool-result.test.ts index 74910c35..b4dfb7cc 100644 --- a/packages/ai/test/image-tool-result.test.ts +++ b/packages/ai/test/image-tool-result.test.ts @@ -298,6 +298,18 @@ describe("Tool Results with Images", () => { }); }); + describe.skipIf(!process.env.XIAOMI_API_KEY)("Xiaomi MiMo Provider (mimo-v2.5-pro)", () => { + const llm = getModel("xiaomi", "mimo-v2.5-pro"); + + it("should handle tool result with only image", { retry: 3, timeout: 30000 }, async () => { + await handleToolWithImageResult(llm); + }); + + it("should handle tool result with text and image", { retry: 3, timeout: 30000 }, async () => { + await handleToolWithTextAndImageResult(llm); + }); + }); + describe.skipIf(!process.env.KIMI_API_KEY)("Kimi For Coding Provider (kimi-for-coding)", () => { const llm = getModel("kimi-coding", "kimi-for-coding"); diff --git a/packages/ai/test/overflow.test.ts b/packages/ai/test/overflow.test.ts index d1aac258..633ba663 100644 --- a/packages/ai/test/overflow.test.ts +++ b/packages/ai/test/overflow.test.ts @@ -61,4 +61,39 @@ describe("isContextOverflow", () => { const message = createErrorMessage("Too many requests. Please slow down."); expect(isContextOverflow(message, 200000)).toBe(false); }); + + function createLengthStopMessage(input: number, cacheRead: number, output: number): AssistantMessage { + return { + role: "assistant", + content: [], + api: "openai-completions", + provider: "xiaomi", + model: "mimo-v2.5-pro", + usage: { + input, + output, + cacheRead, + cacheWrite: 0, + totalTokens: input + cacheRead + output, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + stopReason: "length", + timestamp: Date.now(), + }; + } + + it("detects Xiaomi-style overflow (length stop with zero output and filled context)", () => { + const message = createLengthStopMessage(58, 1048512, 0); + expect(isContextOverflow(message, 1048576)).toBe(true); + }); + + it("does not treat normal length stops with output as overflow", () => { + const message = createLengthStopMessage(1000, 0, 4096); + expect(isContextOverflow(message, 200000)).toBe(false); + }); + + it("does not treat length stops far below context as overflow", () => { + const message = createLengthStopMessage(100, 0, 0); + expect(isContextOverflow(message, 200000)).toBe(false); + }); }); diff --git a/packages/ai/test/stream.test.ts b/packages/ai/test/stream.test.ts index abc91dca..d91fc7c5 100644 --- a/packages/ai/test/stream.test.ts +++ b/packages/ai/test/stream.test.ts @@ -993,6 +993,24 @@ describe("Generate E2E Tests", () => { }, ); + describe.skipIf(!process.env.XIAOMI_API_KEY)( + "Xiaomi MiMo Provider (Xiaomi MiMo-V2.5-Pro via OpenAI Completions)", + () => { + const llm = getModel("xiaomi", "mimo-v2.5-pro"); + + it("should complete basic text generation", { retry: 3 }, async () => { + await basicTextGeneration(llm); + }); + + it("should handle tool calling", { retry: 3 }, async () => { + await handleToolCall(llm); + }); + + it("should handle streaming", { retry: 3 }, async () => { + await handleStreaming(llm); + }); + }, + ); // ========================================================================= // OAuth-based providers (credentials from ~/.pi/agent/oauth.json) // Tokens are resolved at module level (see oauthTokens above) diff --git a/packages/ai/test/tokens.test.ts b/packages/ai/test/tokens.test.ts index ff3fc7a3..fe550dae 100644 --- a/packages/ai/test/tokens.test.ts +++ b/packages/ai/test/tokens.test.ts @@ -220,6 +220,14 @@ describe("Token Statistics on Abort", () => { }); }); + describe.skipIf(!process.env.XIAOMI_API_KEY)("Xiaomi MiMo Provider", () => { + const llm = getModel("xiaomi", "mimo-v2.5-pro"); + + it("should include token stats when aborted mid-stream", { retry: 3, timeout: 30000 }, async () => { + await testTokensOnAbort(llm); + }); + }); + // ========================================================================= // OAuth-based providers (credentials from ~/.pi/agent/oauth.json) // ========================================================================= diff --git a/packages/ai/test/tool-call-without-result.test.ts b/packages/ai/test/tool-call-without-result.test.ts index 514a6fd1..e65b2c05 100644 --- a/packages/ai/test/tool-call-without-result.test.ts +++ b/packages/ai/test/tool-call-without-result.test.ts @@ -215,6 +215,14 @@ describe("Tool Call Without Result Tests", () => { }); }); + describe.skipIf(!process.env.XIAOMI_API_KEY)("Xiaomi MiMo Provider", () => { + const model = getModel("xiaomi", "mimo-v2.5-pro"); + + it("should filter out tool calls without corresponding tool results", { retry: 3, timeout: 30000 }, async () => { + await testToolCallWithoutResult(model); + }); + }); + describe.skipIf(!process.env.KIMI_API_KEY)("Kimi For Coding Provider", () => { const model = getModel("kimi-coding", "kimi-k2-thinking"); diff --git a/packages/ai/test/total-tokens.test.ts b/packages/ai/test/total-tokens.test.ts index c5eda15c..be02bfd6 100644 --- a/packages/ai/test/total-tokens.test.ts +++ b/packages/ai/test/total-tokens.test.ts @@ -443,6 +443,29 @@ describe("totalTokens field", () => { ); }); + // ========================================================================= + // Xiaomi MiMo + // ========================================================================= + + describe.skipIf(!process.env.XIAOMI_API_KEY)("Xiaomi MiMo", () => { + it( + "mimo-v2.5-pro - should return totalTokens equal to sum of components", + { retry: 3, timeout: 60000 }, + async () => { + const llm = getModel("xiaomi", "mimo-v2.5-pro"); + + console.log(`\nXiaomi MiMo / ${llm.id}:`); + const { first, second } = await testTotalTokensWithCache(llm, { apiKey: process.env.XIAOMI_API_KEY }); + + logUsage("First request", first); + logUsage("Second request", second); + + assertTotalTokensEqualsComponents(first); + assertTotalTokensEqualsComponents(second); + }, + ); + }); + // ========================================================================= // Kimi For Coding // ========================================================================= diff --git a/packages/ai/test/unicode-surrogate.test.ts b/packages/ai/test/unicode-surrogate.test.ts index 6b102c39..5bd7229e 100644 --- a/packages/ai/test/unicode-surrogate.test.ts +++ b/packages/ai/test/unicode-surrogate.test.ts @@ -594,6 +594,22 @@ describe("AI Providers Unicode Surrogate Pair Tests", () => { }); }); + describe.skipIf(!process.env.XIAOMI_API_KEY)("Xiaomi MiMo Provider Unicode Handling", () => { + const llm = getModel("xiaomi", "mimo-v2.5-pro"); + + it("should handle emoji in tool results", { retry: 3, timeout: 30000 }, async () => { + await testEmojiInToolResults(llm); + }); + + it("should handle real-world LinkedIn comment data with emoji", { retry: 3, timeout: 30000 }, async () => { + await testRealWorldLinkedInData(llm); + }); + + it("should handle unpaired high surrogate (0xD83D) in tool results", { retry: 3, timeout: 30000 }, async () => { + await testUnpairedHighSurrogate(llm); + }); + }); + describe.skipIf(!process.env.KIMI_API_KEY)("Kimi For Coding Provider Unicode Handling", () => { const llm = getModel("kimi-coding", "kimi-k2-thinking"); diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md index 10aec34d..21ebf1ea 100644 --- a/packages/coding-agent/README.md +++ b/packages/coding-agent/README.md @@ -128,6 +128,7 @@ For each built-in provider, pi maintains a list of tool-capable models, updated - Fireworks - Kimi For Coding - MiniMax +- Xiaomi MiMo See [docs/providers.md](docs/providers.md) for detailed setup instructions. diff --git a/packages/coding-agent/docs/providers.md b/packages/coding-agent/docs/providers.md index 214a4d30..6a0756ae 100644 --- a/packages/coding-agent/docs/providers.md +++ b/packages/coding-agent/docs/providers.md @@ -69,6 +69,7 @@ pi | Kimi For Coding | `KIMI_API_KEY` | `kimi-coding` | | MiniMax | `MINIMAX_API_KEY` | `minimax` | | MiniMax (China) | `MINIMAX_CN_API_KEY` | `minimax-cn` | +| Xiaomi MiMo | `XIAOMI_API_KEY` | `xiaomi` | Reference for environment variables and `auth.json` keys: [`const envMap`](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/env-api-keys.ts) in [`packages/ai/src/env-api-keys.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/env-api-keys.ts). @@ -83,7 +84,8 @@ Store credentials in `~/.pi/agent/auth.json`: "deepseek": { "type": "api_key", "key": "sk-..." }, "google": { "type": "api_key", "key": "..." }, "opencode": { "type": "api_key", "key": "..." }, - "opencode-go": { "type": "api_key", "key": "..." } + "opencode-go": { "type": "api_key", "key": "..." }, + "xiaomi": { "type": "api_key", "key": "..." } } ``` diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index 5d3a7c16..0517bceb 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -320,6 +320,7 @@ ${chalk.bold("Environment Variables:")} CLOUDFLARE_API_KEY - Cloudflare API token (Workers AI and AI Gateway) CLOUDFLARE_ACCOUNT_ID - Cloudflare account id (required for both) CLOUDFLARE_GATEWAY_ID - Cloudflare AI Gateway slug (required for AI Gateway) + XIAOMI_API_KEY - Xiaomi MiMo API key AWS_PROFILE - AWS profile for Amazon Bedrock AWS_ACCESS_KEY_ID - AWS access key for Amazon Bedrock AWS_SECRET_ACCESS_KEY - AWS secret key for Amazon Bedrock diff --git a/packages/coding-agent/src/core/model-resolver.ts b/packages/coding-agent/src/core/model-resolver.ts index 80464e3e..3bbfc461 100644 --- a/packages/coding-agent/src/core/model-resolver.ts +++ b/packages/coding-agent/src/core/model-resolver.ts @@ -39,6 +39,7 @@ export const defaultModelPerProvider: Record = { "kimi-coding": "kimi-for-coding", "cloudflare-workers-ai": "@cf/moonshotai/kimi-k2.6", "cloudflare-ai-gateway": "workers-ai/@cf/moonshotai/kimi-k2.6", + xiaomi: "mimo-v2.5-pro", }; export interface ScopedModel { diff --git a/packages/coding-agent/src/core/provider-display-names.ts b/packages/coding-agent/src/core/provider-display-names.ts index 63f888ac..172e4e36 100644 --- a/packages/coding-agent/src/core/provider-display-names.ts +++ b/packages/coding-agent/src/core/provider-display-names.ts @@ -24,4 +24,5 @@ export const BUILT_IN_PROVIDER_DISPLAY_NAMES: Record = { "vercel-ai-gateway": "Vercel AI Gateway", xai: "xAI", zai: "ZAI", + xiaomi: "Xiaomi MiMo", };