MessageNewParams accepts Anthropic’s documented shorthand where a message content field may be a string, and unmarshals it into a one-element text content block array. BetaMessageNewParams appears to silently drop the same string content, leaving the message content slice nil.
This seems to be because stable messages register a custom decoder for []ContentBlockParamUnion, but beta messages only register the beta content block union and do not register the analogous custom decoder for []BetaContentBlockParamUnion.
Expected behavior
BetaMessageNewParams should handle:
{"role": "user", "content": "hello"}
the same way as MessageNewParams, producing one text block with text "hello".
Actual behavior
json.Unmarshal into BetaMessageNewParams succeeds, but Messages[0].Content is nil.
Repro
I attached a minimal unit test that compares MessageNewParams and BetaMessageNewParams unmarshalling behavior on the same payload. It fails on github.com/anthropics/anthropic-sdk-go v1.45.0
package anthropic_test
import (
"encoding/json"
"testing"
"github.com/anthropics/anthropic-sdk-go"
)
func TestBetaMessageNewParamsUnmarshalStringContentMatchesStableMessages(t *testing.T) {
// Stable messages register a custom decoder for string content:
// https://github.com/anthropics/anthropic-sdk-go/blob/v1.45.0/message.go#L2768-L2779
// Beta messages register the content block union, but not the analogous string-content decoder:
// https://github.com/anthropics/anthropic-sdk-go/blob/v1.45.0/betamessage.go#L3868-L3891
//
// Current output with github.com/anthropics/anthropic-sdk-go v1.45.0:
//
// --- FAIL: TestBetaMessageNewParamsUnmarshalStringContentMatchesStableMessages (0.00s)
// beta_message_string_content_test.go:37: BetaMessageNewParams content blocks = []anthropic.BetaMessageParam{anthropic.BetaMessageParam{Content:[]anthropic.BetaContentBlockParamUnion(nil), Role:"user", paramObj:param.APIObject{metadata:param.metadata{any:interface {}(nil)}}}}, want one text block
// FAIL
body := []byte(`{
"model": "claude-sonnet-4-5",
"max_tokens": 1,
"messages": [{"role": "user", "content": "hello"}]
}`)
var stable anthropic.MessageNewParams
if err := json.Unmarshal(body, &stable); err != nil {
t.Fatalf("MessageNewParams should accept string content: %v", err)
}
if len(stable.Messages) != 1 || len(stable.Messages[0].Content) != 1 {
t.Fatalf("MessageNewParams content blocks = %#v, want one text block", stable.Messages)
}
if got := stable.Messages[0].Content[0].OfText.Text; got != "hello" {
t.Fatalf("MessageNewParams content = %q, want %q", got, "hello")
}
var beta anthropic.BetaMessageNewParams
if err := json.Unmarshal(body, &beta); err != nil {
t.Fatalf("BetaMessageNewParams should accept string content like MessageNewParams: %v", err)
}
if len(beta.Messages) != 1 || len(beta.Messages[0].Content) != 1 {
t.Fatalf("BetaMessageNewParams content blocks = %#v, want one text block", beta.Messages)
}
if got := beta.Messages[0].Content[0].OfText.Text; got != "hello" {
t.Fatalf("BetaMessageNewParams content = %q, want %q", got, "hello")
}
}
MessageNewParams accepts Anthropic’s documented shorthand where a message content field may be a string, and unmarshals it into a one-element text content block array. BetaMessageNewParams appears to silently drop the same string content, leaving the message content slice nil.
This seems to be because stable messages register a custom decoder for []ContentBlockParamUnion, but beta messages only register the beta content block union and do not register the analogous custom decoder for []BetaContentBlockParamUnion.
Expected behavior
BetaMessageNewParams should handle:
{"role": "user", "content": "hello"}
the same way as MessageNewParams, producing one text block with text "hello".
Actual behavior
json.Unmarshal into BetaMessageNewParams succeeds, but Messages[0].Content is nil.
Repro
I attached a minimal unit test that compares MessageNewParams and BetaMessageNewParams unmarshalling behavior on the same payload. It fails on github.com/anthropics/anthropic-sdk-go v1.45.0