Skip to content

Modified content blocks in Message are ignored when calling ToParam #243

@cchalm

Description

@cchalm

Go version: go version go1.25.0 linux/amd64
SDK version: github.com/anthropics/anthropic-sdk-go v1.13.0

Steps to reproduce

  1. Construct a Message by unmarshalling an API response payload
  2. Modify the content of a text content block
  3. Call ToParam to convert Message to MessageParam
  4. Expected: text block modifications are reflected in MessageParam
  5. Actual: text block modifications are not reflected in MessageParam

Code to reproduce

package main

import (
	"encoding/json"
	"fmt"

	"github.com/anthropics/anthropic-sdk-go"
)

func main() {
	// Response content copied from https://docs.claude.com/en/api/messages
	respJSON := `{"content":[{"citations":null,"text":"Hi! My name is Claude.","type":"text"}],"id":"msg_013Zva2CMHLNnXjNJJKqJ2EF","model":"claude-sonnet-4-20250514","role":"assistant","stop_reason":"end_turn","stop_sequence":null,"type":"message","usage":{"input_tokens":2095,"output_tokens":503}}`
	// Simulate receiving this response
	var message anthropic.Message
	err := json.Unmarshal([]byte(respJSON), &message)
	if err != nil {
		panic(err)
	}

	// Modify content blocks, e.g. to manipulate conversation history for next exchange
	message.Content[0].Text = "Yarrr! Me name be Claude."

	// Call ToParam to create MessageParam in preparation for next call to Messages.New or Messages.NewStreaming
	messageParam := message.ToParam()

	// Output: "Hi! My name is Claude."
	// Expected: "Yarrr! Me name be Claude."
	fmt.Println(messageParam.Content[0].OfText.Text)
}

The change to message.Content[0].Text is not effective because AsText() on ContentBlockUnion unmarshals TextBlock from u.JSON.raw, ignoring the unmarshalled fields on TextBlock. Stack for reference:

anthropic-sdk-go.ContentBlockUnion.AsText (...\github.com\anthropics\[email protected]\message.go:726)
anthropic-sdk-go.ContentBlockUnion.AsAny (...\github.com\anthropics\[email protected]\message.go:710)
anthropic-sdk-go.ContentBlockUnion.ToParam (...\github.com\anthropics\[email protected]\message.go:665)
anthropic-sdk-go.Message.ToParam (...\github.com\anthropics\[email protected]\message.go:1790)

Impact

It's convenient to store assistant messages in conversation history as Message objects because they include useful information like Usage that is not available in MessageParam. Some use cases require manipulating conversation history, and if Message is immutable it's difficult to modify conversation history stored this way.

Suggested fix

  • If Message, ContentBlockUnion, etc are intended to be immutable, this should be documented, and maybe enforced with getters over unexported fields.
  • If they are not intended to be immutable, ContentBlockUnion's As*() functions should copy over the values of relevant fields after unmarshalling the raw content.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions