feat: add MiMo reasoning_content support for Claude Code proxy (#2990)

* feat: add MiMo reasoning_content support for Claude Code proxy

MiMo requires reasoning_content to be passed back in multi-turn
conversations with tool calls. Add "mimo" and "xiaomimimo" to the
preserve_reasoning_content whitelist so thinking blocks are converted
to reasoning_content when proxying Claude Code → MiMo.

Also handle redacted_thinking blocks (encrypted by Claude Code across
turns) by injecting a placeholder to prevent MiMo 400 errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: support MiMo reasoning histories

- Fix the PR CI clippy failure for redacted thinking handling.
- Preserve MiMo reasoning content on the OpenAI Chat proxy path.
- Normalize Claude Desktop Anthropic thinking history for MiMo local routes.
- Reject unsupported Claude Desktop direct model remaps.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
This commit is contained in:
zhangyapu1
2026-05-26 22:55:36 +08:00
committed by GitHub
parent 48473a5ca3
commit 707a5593e5
3 changed files with 333 additions and 1 deletions
+40 -1
View File
@@ -84,7 +84,11 @@ pub fn claude_api_format_needs_transform(api_format: &str) -> bool {
fn is_reasoning_content_compatible_identifier(value: &str) -> bool {
let value = value.to_ascii_lowercase();
value.contains("moonshot") || value.contains("kimi") || value.contains("deepseek")
value.contains("moonshot")
|| value.contains("kimi")
|| value.contains("deepseek")
|| value.contains("mimo")
|| value.contains("xiaomimimo")
}
fn should_preserve_reasoning_content_for_openai_chat(
@@ -1801,4 +1805,39 @@ mod tests {
assert_eq!(msg["reasoning_content"], "I should call the tool.");
assert!(msg.get("tool_calls").is_some());
}
#[test]
fn test_transform_openai_chat_preserves_reasoning_content_for_mimo_provider() {
let provider = create_provider_with_meta(
json!({
"env": {
"ANTHROPIC_BASE_URL": "https://api.xiaomimimo.com/v1",
"ANTHROPIC_API_KEY": "test-key"
}
}),
ProviderMeta {
api_format: Some("openai_chat".to_string()),
..Default::default()
},
);
let body = json!({
"model": "mimo-v2.5-pro",
"max_tokens": 64,
"messages": [{
"role": "assistant",
"content": [
{"type": "thinking", "thinking": "I should call the tool."},
{"type": "tool_use", "id": "call_123", "name": "get_weather", "input": {"location": "Tokyo"}}
]
}]
});
let transformed =
transform_claude_request_for_api_format(body, &provider, "openai_chat", None, None)
.unwrap();
let msg = &transformed["messages"][0];
assert_eq!(msg["reasoning_content"], "I should call the tool.");
assert!(msg.get("tool_calls").is_some());
}
}
@@ -438,6 +438,14 @@ fn convert_message_to_openai(
}
}
}
"redacted_thinking" if preserve_reasoning_content => {
// Claude Code encrypts historical thinking into redacted_thinking blocks.
// MiMo/DeepSeek require non-empty reasoning_content on assistant tool-call
// messages, so inject a minimal placeholder when the real content is
// unavailable. Skip when preserve_reasoning_content is off (generic
// OpenAI-compatible path).
reasoning_parts.push("[redacted thinking]".to_string());
}
_ => {}
}
}
@@ -950,6 +958,26 @@ mod tests {
assert_eq!(msg["tool_calls"][0]["id"], "call_123");
}
#[test]
fn test_anthropic_to_openai_tool_use_uses_redacted_thinking_placeholder() {
let input = json!({
"model": "mimo-v2.5-pro",
"max_tokens": 1024,
"messages": [{
"role": "assistant",
"content": [
{"type": "redacted_thinking", "data": "opaque"},
{"type": "tool_use", "id": "call_123", "name": "get_weather", "input": {"location": "Tokyo"}}
]
}]
});
let result = anthropic_to_openai_with_reasoning_content(input, true).unwrap();
let msg = &result["messages"][0];
assert_eq!(msg["reasoning_content"], "[redacted thinking]");
assert_eq!(msg["tool_calls"][0]["id"], "call_123");
}
#[test]
fn test_anthropic_to_openai_does_not_emit_reasoning_content_by_default() {
let input = json!({