Skip to content

BetaMessageNewParams does not unmarshal string message content like MessageNewParams #343

@harshit-gupta-glean

Description

@harshit-gupta-glean

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")
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions