From 6a9d2cc652721bce9b8205a5b88b1dcb04cc2918 Mon Sep 17 00:00:00 2001 From: songxujiangyang Date: Thu, 23 Jul 2026 23:39:18 +0800 Subject: [PATCH] fix(anthropic): non-whitespace placeholder for synthetic leading user turn _ensure_leading_user_turn prepends a synthetic user message when the first message is assistant (happens after a second context compaction on the auto path, where the summary is emitted as role=assistant). The placeholder text was a single space " ". Anthropic's own API tolerates whitespace-only text blocks, but stricter Anthropic-compatible endpoints (e.g. zenmux.ai) reject them with HTTP 400 invalid_params: messages: text content blocks must contain non-whitespace text which is non-retryable and bricks every subsequent turn in any conversation that has compacted twice. Use a minimal non-blank placeholder "." instead. Tests updated from freezing the exact placeholder byte to asserting the behavior contract (leading turn is role=user with non-whitespace text). --- agent/anthropic_adapter.py | 5 ++++- tests/agent/test_anthropic_adapter.py | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/agent/anthropic_adapter.py b/agent/anthropic_adapter.py index fd7596e7e3bf..ca86c3ebe266 100644 --- a/agent/anthropic_adapter.py +++ b/agent/anthropic_adapter.py @@ -2427,7 +2427,10 @@ def _ensure_leading_user_turn(result: List[Dict[str, Any]]) -> None: (convert_messages_to_converse). """ if result and result[0].get("role") != "user": - result.insert(0, {"role": "user", "content": [{"type": "text", "text": " "}]}) + # ponytail: some Anthropic-compatible endpoints (e.g. zenmux) reject a + # whitespace-only text block with HTTP 400 "text content blocks must + # contain non-whitespace text"; use a minimal non-blank placeholder. + result.insert(0, {"role": "user", "content": [{"type": "text", "text": "."}]}) def convert_messages_to_anthropic( diff --git a/tests/agent/test_anthropic_adapter.py b/tests/agent/test_anthropic_adapter.py index ae99f950f739..c2166522edf4 100644 --- a/tests/agent/test_anthropic_adapter.py +++ b/tests/agent/test_anthropic_adapter.py @@ -1394,7 +1394,9 @@ def test_leading_assistant_after_compaction_gets_user_turn_prepended(self): assert system == "You are helpful." assert result[0]["role"] == "user" - assert result[0]["content"] == [{"type": "text", "text": " "}] + # Placeholder text must be non-whitespace: strict Anthropic-compatible + # endpoints (zenmux) 400 on whitespace-only text blocks. + assert result[0]["content"][0]["text"].strip() assert result[1]["role"] == "assistant" assert any( m["role"] == "assistant" and "Context compaction summary" in str(m["content"]) @@ -1418,7 +1420,7 @@ def test_double_compaction_no_system_in_messages_leads_with_user(self): assert system is None assert result[0]["role"] == "user" - assert result[0]["content"] == [{"type": "text", "text": " "}] + assert result[0]["content"][0]["text"].strip() assert result[1]["role"] == "assistant" assert "Context compaction summary" in str(result[1]["content"])