From e4b8975e4ebd371a6039dff21fc8a6fb93b11639 Mon Sep 17 00:00:00 2001 From: PratikDhanave Date: Fri, 18 Sep 2026 16:49:01 +0530 Subject: [PATCH] Skip empty text blocks when building Anthropic messages Anthropic rejects empty text blocks. The system prompt path already guards against forwarding a TextContent with empty text, but the message path did not, so an assistant (or user) turn carrying an empty TextContent alongside real content produced an invalid empty text block in the request. Guard the message-path TextContent case the same way, matching the Python client which skips empty text blocks. --- provider/anthropicprovider/agent.go | 6 +++++- provider/anthropicprovider/agent_test.go | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/provider/anthropicprovider/agent.go b/provider/anthropicprovider/agent.go index 42cf84d1..1256ee89 100644 --- a/provider/anthropicprovider/agent.go +++ b/provider/anthropicprovider/agent.go @@ -876,7 +876,11 @@ func buildMessageParam(msg *message.Message) (anthropic.MessageParam, error) { for _, c := range msg.Contents { switch c := c.(type) { case *message.TextContent: - content = append(content, anthropic.NewTextBlock(c.Text)) + // Anthropic rejects empty text blocks, so skip them (the system + // path applies the same guard). + if c.Text != "" { + content = append(content, anthropic.NewTextBlock(c.Text)) + } case *message.TextReasoningContent: // Replay a prior assistant thinking block so its signature travels // back with the request (Anthropic emits reasoning before the rest of diff --git a/provider/anthropicprovider/agent_test.go b/provider/anthropicprovider/agent_test.go index a76e81fa..5f1db2a8 100644 --- a/provider/anthropicprovider/agent_test.go +++ b/provider/anthropicprovider/agent_test.go @@ -1168,6 +1168,27 @@ func TestAssistantUnsignedReasoningIsSkipped(t *testing.T) { } } +// Anthropic rejects empty text blocks, so a TextContent with no text must not be +// forwarded. The system path already guards this; the message path must too, +// matching the Python client which skips empty text blocks. +func TestEmptyTextContentIsSkipped(t *testing.T) { + msgs := []*message.Message{ + {Role: message.RoleUser, Contents: message.Contents{&message.TextContent{Text: "hi"}}}, + {Role: message.RoleAssistant, Contents: message.Contents{ + &message.TextContent{Text: ""}, + &message.TextContent{Text: "hello"}, + }}, + } + + blocks := assistantBlocksFromRequest(t, msgs) + if len(blocks) != 1 { + t.Fatalf("assistant content blocks = %d, want 1 (empty text block should be skipped) (%#v)", len(blocks), blocks) + } + if blocks[0]["type"] != "text" || blocks[0]["text"] != "hello" { + t.Errorf("block = %#v, want a text block %q", blocks[0], "hello") + } +} + // The tool's input_schema sent to Anthropic must carry the additionalProperties // keyword emitted by functool's strict schema. functool.Call validates decoded // arguments against the resolved schema (additionalProperties:false), so if the