fix(agent): drop the empty assistant turn from an approved-action replay (#6124)

The approved-action replay appends the sealed tool result with no assistant
prose for that round, which produced an assistant message with content "".
Anthropic's Messages API rejects a non-final assistant message with empty
content, so a resumed turn after a tool approval failed before the model saw
the result. A turn carrying neither prose nor reasoning has nothing to say to
any provider, so it is no longer appended. A round with prose, and a
reasoning-only round that DeepSeek thinking mode needs, both still append.
This commit is contained in:
Joeseph Grey
2026-08-20 13:06:22 +02:00
committed by GitHub
parent 85297cee44
commit b4d12932a9
2 changed files with 111 additions and 4 deletions
+11 -4
View File
@@ -3081,10 +3081,17 @@ def _append_tool_results(
messages.append(result_message)
else:
tool_output_text = "\n\n".join(tool_results)
msg = {"role": "assistant", "content": round_response}
if round_reasoning:
msg["reasoning_content"] = round_reasoning
messages.append(msg)
# An approved-action replay injects the sealed tool result with no
# assistant prose for that round, which used to append an assistant turn
# whose content was "". Anthropic's Messages API rejects a non-final
# assistant message with empty content (HTTP 400), so the resumed turn
# died before the model saw the result. A turn carrying neither prose nor
# reasoning has nothing to say to any provider, so skip it entirely.
if round_response.strip() or round_reasoning:
msg = {"role": "assistant", "content": round_response}
if round_reasoning:
msg["reasoning_content"] = round_reasoning
messages.append(msg)
# Tool output (shell/python stdout, file reads, fetched pages, email
# bodies, MCP results) is sourced from outside the server. Wrap it as
# untrusted data so prompt-injection inside a tool result is treated as