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