Bug: Cross-provider reasoning promotion leaks stale content to DeepSeek/Kimi thinking mode
Background
DeepSeek V4 and Kimi/Moonshot thinking modes require reasoning_content="" on every assistant tool-call message. If the field is missing on replay, the API returns HTTP 400:
The reasoning_content in the thinking mode must be passed back to the API.
When a session switches providers mid-conversation (e.g. MiniMax → DeepSeek), the internal reasoning field from the prior provider can leak into the reasoning_content field sent to the new provider, causing HTTP 400.
Root Cause
In _copy_reasoning_content_for_api (run_agent.py), the normalized_reasoning promotion path returns before the DeepSeek/Kimi empty-string guard can execute:
# Current (buggy) ordering in origin/main:
explicit_reasoning = source_msg.get("reasoning_content")
if isinstance(explicit_reasoning, str): # False when switching from MiniMax
api_msg["reasoning_content"] = explicit_reasoning
return
normalized_reasoning = source_msg.get("reasoning") # "MiniMax chain of thought..."
if isinstance(normalized_reasoning, str) and normalized_reasoning:
api_msg["reasoning_content"] = normalized_reasoning # ← LEAKED
return # ← DeepSeek/Kimi guard never reached
# This guard is unreachable for cross-provider histories
if source_msg.get("tool_calls") and (self._needs_kimi...() or self._needs_deepseek...()):
api_msg["reasoning_content"] = ""
When the session history contains a tool-call message from MiniMax with reasoning="MiniMax thinking..." and no reasoning_content, switching to DeepSeek causes:
reasoning_content key absent → first check returns False
reasoning key present → promoted to reasoning_content
- DeepSeek receives MiniMax's reasoning → HTTP 400
Fix
Reorder the logic so that:
reasoning_content already set → preserve verbatim (including DeepSeek's own "" placeholder written at creation time)
- Tool-call turns with neither
reasoning_content nor reasoning → inject "" for DeepSeek/Kimi (poisoned history from prior provider)
reasoning present → promote to reasoning_content (healthy same-provider path)
The key addition is not has_reasoning in the guard, which distinguishes "this provider has reasoning to promote" from "a prior provider left reasoning that should not be forwarded".
Affected Scenarios
| Scenario |
Before fix |
After fix |
DeepSeek tool_call with reasoning_content="" set |
preserved |
preserved |
DeepSeek tool_call with reasoning="DeepSeek reasoning" |
promoted |
promoted |
DeepSeek tool_call after MiniMax (MiniMax reasoning present) |
MiniMax content sent → 400 |
"" injected |
| DeepSeek tool_call after MiniMax (both fields absent) |
"" injected |
"" injected |
Related Issues
Test Coverage
All 21 existing tests in tests/run_agent/test_deepseek_reasoning_content_echo.py pass with this fix.
Bug: Cross-provider reasoning promotion leaks stale content to DeepSeek/Kimi thinking mode
Background
DeepSeek V4 and Kimi/Moonshot thinking modes require
reasoning_content=""on every assistant tool-call message. If the field is missing on replay, the API returns HTTP 400:When a session switches providers mid-conversation (e.g. MiniMax → DeepSeek), the internal
reasoningfield from the prior provider can leak into thereasoning_contentfield sent to the new provider, causing HTTP 400.Root Cause
In
_copy_reasoning_content_for_api(run_agent.py), thenormalized_reasoningpromotion path returns before the DeepSeek/Kimi empty-string guard can execute:When the session history contains a tool-call message from MiniMax with
reasoning="MiniMax thinking..."and noreasoning_content, switching to DeepSeek causes:reasoning_contentkey absent → first check returns Falsereasoningkey present → promoted toreasoning_contentFix
Reorder the logic so that:
reasoning_contentalready set → preserve verbatim (including DeepSeek's own""placeholder written at creation time)reasoning_contentnorreasoning→ inject""for DeepSeek/Kimi (poisoned history from prior provider)reasoningpresent → promote toreasoning_content(healthy same-provider path)The key addition is
not has_reasoningin the guard, which distinguishes "this provider has reasoning to promote" from "a prior provider left reasoning that should not be forwarded".Affected Scenarios
reasoning_content=""setreasoning="DeepSeek reasoning"reasoningpresent)""injected""injected""injectedRelated Issues
Test Coverage
All 21 existing tests in
tests/run_agent/test_deepseek_reasoning_content_echo.pypass with this fix.