From 6374c2baf432419ccb4541a99ce4958ecd03dae0 Mon Sep 17 00:00:00 2001 From: David Brailovsky Date: Wed, 14 Oct 2026 13:13:16 +0200 Subject: [PATCH] fix(ai): send only thinking on/off toggle for Xiaomi MiMo models MiMo Chat Completions rejects reasoning_effort "minimal" with a 400. Xiaomi documents only thinking.type enabled/disabled and treats all effort levels identically, so stop sending reasoning_effort and expose only the off and high thinking levels. fixes #10818 --- packages/ai/CHANGELOG.md | 1 + packages/ai/scripts/generate-models.ts | 15 +++++++++- packages/ai/test/xiaomi-models.test.ts | 39 +++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index fcad3ef8f..6763906fe 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -15,6 +15,7 @@ - Fixed automatic retry not triggering for gateway `Unable to forward upstream stream` errors ([#10920](https://github.com/earendil-works/pi/issues/10920)) - Fixed OpenAI-compatible responses showing thinking after the answer when a proxy such as LiteLLM sends reasoning and content in the same stream chunk ([#10926](https://github.com/earendil-works/pi/issues/10926)) - Fixed Gemini 4 Argon requests on Google Vertex failing with "Thinking budget is not supported", and its thinking level being ignored on the Gemini API, by sending `thinkingLevel` for Gemini 4 and later models ([#10890](https://github.com/earendil-works/pi/issues/10890)) +- Fixed Xiaomi MiMo models failing with a 400 at the `minimal` thinking level by sending only the documented thinking on/off toggle and exposing only the `off` and `high` levels ([#10818](https://github.com/earendil-works/pi/issues/10818)) ## [1.2.0] - 2026-10-12 diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index d6b998e12..41d6e27f6 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -2462,10 +2462,23 @@ async function loadModelsDevData(): Promise[]> { // Built-in `xiaomi` targets the API billing endpoint (single stable URL, // keys from platform.xiaomimimo.com). The three `xiaomi-token-plan-*` // providers cover prepaid Token Plan endpoints in cn / ams / sgp. + // MiMo Chat Completions only documents thinking.type enabled/disabled and + // rejects some reasoning_effort values (e.g. "minimal"). Xiaomi documents + // all effort levels as identical, so expose only off and high. const xiaomiCompat: OpenAICompletionsCompat = { + supportsReasoningEffort: false, requiresReasoningContentOnAssistantMessages: true, thinkingFormat: "deepseek", }; + const xiaomiThinkingLevelMap = { + off: "off", + minimal: null, + low: null, + medium: null, + high: "high", + xhigh: null, + max: null, + } as const; const xiaomiVariants = [ { source: "xiaomi", provider: "xiaomi", baseUrl: "https://api.xiaomimimo.com/v1" }, { @@ -2502,12 +2515,12 @@ async function loadModelsDevData(): Promise[]> { baseUrl, compat: xiaomiCompat, reasoning: m.reasoning === true, + ...(m.reasoning === true ? { thinkingLevelMap: xiaomiThinkingLevelMap } : {}), input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: getModelsDevCost(m.cost), contextWindow: m.limit?.context || 4096, maxTokens: m.limit?.output || 4096, }); - recordModelsDevReasoningOptions(provider, modelId, m); } } diff --git a/packages/ai/test/xiaomi-models.test.ts b/packages/ai/test/xiaomi-models.test.ts index 992a15c43..d1b098bad 100644 --- a/packages/ai/test/xiaomi-models.test.ts +++ b/packages/ai/test/xiaomi-models.test.ts @@ -1,5 +1,7 @@ import { describe, expect, it } from "vitest"; -import { getModels } from "../src/compat.ts"; +import { getModels, streamSimple } from "../src/compat.ts"; +import { getSupportedThinkingLevels } from "../src/models.ts"; +import type { ThinkingLevel } from "../src/types.ts"; const XIAOMI_PROVIDERS = ["xiaomi", "xiaomi-token-plan-cn", "xiaomi-token-plan-ams", "xiaomi-token-plan-sgp"] as const; const DEPRECATED_MODEL_IDS = ["mimo-v2-flash", "mimo-v2-omni", "mimo-v2-pro"] as const; @@ -15,4 +17,39 @@ describe("Xiaomi MiMo models", () => { const modelIds = getModels(provider).map((model) => model.id); for (const modelId of REPLACEMENT_MODEL_IDS) expect(modelIds).toContain(modelId); }); + + // #10818: MiMo Chat Completions only supports a thinking on/off toggle. + it.each(XIAOMI_PROVIDERS)("models %s reasoning as an off/on toggle", async (provider) => { + const models = getModels(provider).filter((model) => model.reasoning); + expect(models.length).toBeGreaterThan(0); + + for (const model of models) { + expect(getSupportedThinkingLevels(model)).toEqual(["off", "high"]); + + const capturePayload = async (reasoning: ThinkingLevel | undefined) => { + let payload: Record | undefined; + await streamSimple( + model, + { messages: [{ role: "user", content: "test", timestamp: 0 }] }, + { + apiKey: "test-xiaomi-key", + reasoning, + onPayload: (value) => { + payload = value as Record; + throw new Error("payload captured"); + }, + }, + ).result(); + return payload; + }; + + const enabled = await capturePayload("high"); + expect(enabled?.thinking).toEqual({ type: "enabled" }); + expect(enabled?.reasoning_effort).toBeUndefined(); + + const disabled = await capturePayload(undefined); + expect(disabled?.thinking).toEqual({ type: "disabled" }); + expect(disabled?.reasoning_effort).toBeUndefined(); + } + }); });