Bug Description
When using DeepSeek's Anthropic-compatible Messages API (https://api.deepseek.com/anthropic) with reasoning_effort enabled, multi-turn conversations fail with HTTP 400:
HTTP 400: The `content[].thinking` in the thinking mode must be passed back to the API.
Root Cause
In agent/anthropic_adapter.py, convert_messages_to_anthropic() classifies any non-anthropic.com endpoint as third-party and strips ALL thinking / redacted_thinking content blocks from every assistant message (lines ~1484–1492).
DeepSeek requires these thinking blocks in the message history when reasoning mode is active. Stripping them causes the API to reject the request on the second+ turn.
Kimi's /coding endpoint has the same requirement and already has a dedicated handler (_is_kimi branch at line ~1466). DeepSeek needs similar treatment.
Steps to Reproduce
- Configure DeepSeek Anthropic-compatible endpoint:
model:
default: deepseek-v4-pro
provider: deepseek
base_url: https://api.deepseek.com/anthropic
agent:
reasoning_effort: medium
- Start any multi-turn conversation that triggers 2+ assistant turns with tool calls
- Second API call fails with HTTP 400
Proposed Fix
Add _is_deepseek_anthropic_endpoint detection and a dedicated branch that preserves unsigned thinking blocks (DeepSeek-native) while stripping signed Anthropic blocks (which DeepSeek can't validate). Same pattern as the existing Kimi handler.
diff --git a/agent/anthropic_adapter.py b/agent/anthropic_adapter.py
index af358a2d..a8936a47 100644
--- a/agent/anthropic_adapter.py
+++ b/agent/anthropic_adapter.py
@@ -336,6 +336,22 @@ def _is_kimi_coding_endpoint(base_url: str | None) -> bool:
return normalized.rstrip("/").lower().startswith("https://api.kimi.com/coding")
+def _is_deepseek_anthropic_endpoint(base_url: str | None) -> bool:
+ """Return True for DeepSeek's Anthropic-compatible Messages API.
+
+ DeepSeek requires thinking blocks to be preserved in the message
+ history when reasoning_effort is enabled — if they are stripped the
+ API returns HTTP 400. Unlike Anthropic, DeepSeek does not sign its
+ thinking blocks, so signed (Anthropic) blocks are stripped while
+ unsigned (DeepSeek-native) blocks are kept.
+ """
+ normalized = _normalize_base_url_text(base_url)
+ if not normalized:
+ return False
+ normalized = normalized.rstrip("/").lower()
+ return "api.deepseek.com" in normalized and "anthropic" in normalized
+
+
def _requires_bearer_auth(base_url: str | None) -> bool:
"""Return True for Anthropic-compatible providers that require Bearer auth.
@@ -1435,6 +1451,7 @@ def convert_messages_to_anthropic(
_THINKING_TYPES = frozenset(("thinking", "redacted_thinking"))
_is_third_party = _is_third_party_anthropic_endpoint(base_url)
_is_kimi = _is_kimi_coding_endpoint(base_url)
+ _is_deepseek = _is_deepseek_anthropic_endpoint(base_url)
last_assistant_idx = None
for i in range(len(result) - 1, -1, -1):
@@ -1464,6 +1481,21 @@ def convert_messages_to_anthropic(
# keep it: Kimi needs it for message-history validation.
new_content.append(b)
m["content"] = new_content or [{"type": "text", "text": "(empty)"}]
+ elif _is_deepseek:
+ # DeepSeek Anthropic-compatible API requires thinking blocks
+ # on replayed assistant messages for multi-turn reasoning
+ # continuity. Strip signed Anthropic blocks (DeepSeek can't
+ # validate external signatures) but preserve unsigned blocks
+ # that DeepSeek itself generated on prior turns.
+ new_content = []
+ for b in m["content"]:
+ if not isinstance(b, dict) or b.get("type") not in _THINKING_TYPES:
+ new_content.append(b)
+ continue
+ if b.get("signature") or b.get("data"):
+ continue
+ new_content.append(b)
+ m["content"] = new_content or [{"type": "text", "text": "(empty)"}]
elif _is_third_party or idx != last_assistant_idx:
# Third-party endpoint: strip ALL thinking blocks from every
# assistant message — signatures are Anthropic-proprietary.
Workaround
Set reasoning_effort: '' in config to disable thinking mode when using DeepSeek's Anthropic endpoint.
Bug Description
When using DeepSeek's Anthropic-compatible Messages API (
https://api.deepseek.com/anthropic) withreasoning_effortenabled, multi-turn conversations fail with HTTP 400:Root Cause
In
agent/anthropic_adapter.py,convert_messages_to_anthropic()classifies any non-anthropic.comendpoint as third-party and strips ALLthinking/redacted_thinkingcontent blocks from every assistant message (lines ~1484–1492).DeepSeek requires these thinking blocks in the message history when reasoning mode is active. Stripping them causes the API to reject the request on the second+ turn.
Kimi's
/codingendpoint has the same requirement and already has a dedicated handler (_is_kimibranch at line ~1466). DeepSeek needs similar treatment.Steps to Reproduce
Proposed Fix
Add
_is_deepseek_anthropic_endpointdetection and a dedicated branch that preserves unsigned thinking blocks (DeepSeek-native) while stripping signed Anthropic blocks (which DeepSeek can't validate). Same pattern as the existing Kimi handler.Workaround
Set
reasoning_effort: ''in config to disable thinking mode when using DeepSeek's Anthropic endpoint.