From bc73fa248181da3034ff2841f429231a71c04a84 Mon Sep 17 00:00:00 2001 From: OmniRoute Ops Date: Tue, 12 May 2026 14:58:09 +0000 Subject: [PATCH] fix(reasoning-cache): include xiaomi-mimo in replay provider/model detection MiMo (Xiaomi) enforces the same "echo reasoning_content on subsequent turns" contract as DeepSeek and Kimi-thinking. Without replay, the upstream returns 400: data:{"error":{"code":"400","message":"Param Incorrect", "param":"The reasoning_content in the thinking mode must be passed back to the API.","type":""}} Repro: client sends a multi-turn /v1/messages body where the assistant history has tool_use blocks but no thinking blocks (Capy and most BYOK clients strip thinking on the wire). MiMo refuses without the reasoning_content from the previous assistant turn. The reasoning replay cache (issue #1628) already captures reasoning_content from non-streaming responses with tool_calls and re-injects it on the request side. But the gate `requiresReasoningReplay(provider, model)` did not include MiMo: REASONING_REPLAY_PROVIDERS missed "xiaomi-mimo" REASONING_REPLAY_MODEL_PATTERNS had no /mimo/ entry So the captured reasoning was discarded on the next turn instead of replayed. Fix: - Add "xiaomi-mimo" to REASONING_REPLAY_PROVIDERS - Add /^mimo[-.]?v\d/i to REASONING_REPLAY_MODEL_PATTERNS (defensive match if a wildcard route assigns a non-xiaomi-mimo provider ID to a mimo-* model alias) Tests: 4 new cases (40/40 green) covering both provider-id and model- pattern detection paths, including XIAOMI-MIMO uppercase normalization. --- open-sse/services/reasoningCache.ts | 8 ++++++++ tests/unit/reasoning-cache.test.ts | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/open-sse/services/reasoningCache.ts b/open-sse/services/reasoningCache.ts index 2b5f7788e37..95da644d27a 100644 --- a/open-sse/services/reasoningCache.ts +++ b/open-sse/services/reasoningCache.ts @@ -34,6 +34,11 @@ const REASONING_REPLAY_PROVIDERS = new Set([ "sambanova", "fireworks", "together", + // Xiaomi MiMo enforces the same "pass back reasoning_content on subsequent + // turns" contract as DeepSeek/Kimi-thinking. Without replay the upstream + // 400s with "Param Incorrect: The reasoning_content in the thinking mode + // must be passed back to the API." + "xiaomi-mimo", ]); const REASONING_REPLAY_MODEL_PATTERNS = [ @@ -44,6 +49,9 @@ const REASONING_REPLAY_MODEL_PATTERNS = [ /qwq/i, /qwen.*think/i, /glm.*think/i, + // MiMo (Xiaomi) thinking models — defensive match if a wildcard route + // assigns a non-`xiaomi-mimo` provider ID to a mimo-* model alias. + /^mimo[-.]?v\d/i, ]; /** diff --git a/tests/unit/reasoning-cache.test.ts b/tests/unit/reasoning-cache.test.ts index b2dcf6d496a..7fa835239f9 100644 --- a/tests/unit/reasoning-cache.test.ts +++ b/tests/unit/reasoning-cache.test.ts @@ -346,6 +346,20 @@ describe("Reasoning Replay Cache — Provider Detection", () => { assert.equal(requiresReasoningReplay("glm", "glm-5-thinking"), true); }); + it("should detect xiaomi-mimo provider", () => { + // MiMo enforces reasoning_content echo on subsequent turns; without + // replay the upstream returns 400 "Param Incorrect: The reasoning_content + // in the thinking mode must be passed back to the API." + assert.equal(requiresReasoningReplay("xiaomi-mimo", "mimo-v2.5-pro"), true); + assert.equal(requiresReasoningReplay("XIAOMI-MIMO", "mimo-v2.5"), true); + }); + + it("should detect mimo-v* model pattern under any provider id", () => { + assert.equal(requiresReasoningReplay("unknown-provider", "mimo-v2.5-pro"), true); + assert.equal(requiresReasoningReplay("unknown-provider", "mimo-v3"), true); + assert.equal(requiresReasoningReplay("unknown-provider", "MimoV2.5-pro"), true); + }); + it("should NOT detect a generic openai model", () => { assert.equal(requiresReasoningReplay("openai", "gpt-4o"), false); });