Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions betamessage.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/anthropics/anthropic-sdk-go/packages/respjson"
"github.com/anthropics/anthropic-sdk-go/packages/ssestream"
"github.com/anthropics/anthropic-sdk-go/shared/constant"
"github.com/tidwall/gjson"
)

// BetaMessageService contains methods and other services that help with
Expand Down Expand Up @@ -7943,16 +7944,61 @@ type BetaToolResultBlockParam struct {
// Create a cache control breakpoint at this content block.
CacheControl BetaCacheControlEphemeralParam `json:"cache_control,omitzero"`
Content []BetaToolResultBlockParamContentUnion `json:"content,omitzero"`
// ContentString is an alternative to Content that allows setting a plain string
// value for the content field. Per the API docs, tool_result content can be
// either a string (e.g. "content": "15 degrees") or an array of content blocks.
// When ContentString is non-empty and Content is empty, it will be serialized as
// a plain string. This field is not serialized directly via struct tags.
ContentString string `json:"-"`
// This field can be elided, and will marshal its zero value as "tool_result".
Type constant.ToolResult `json:"type" api:"required"`
paramObj
}

func (r BetaToolResultBlockParam) MarshalJSON() (data []byte, err error) {
if r.ContentString != "" && len(r.Content) == 0 {
type shadow struct {
ToolUseID string `json:"tool_use_id"`
IsError param.Opt[bool] `json:"is_error,omitzero"`
CacheControl BetaCacheControlEphemeralParam `json:"cache_control,omitzero"`
Content string `json:"content"`
Type constant.ToolResult `json:"type"`
}
return json.Marshal(shadow{
ToolUseID: r.ToolUseID,
IsError: r.IsError,
CacheControl: r.CacheControl,
Content: r.ContentString,
Type: r.Type,
})
}
type shadow BetaToolResultBlockParam
return param.MarshalObject(r, (*shadow)(&r))
}
func (r *BetaToolResultBlockParam) UnmarshalJSON(data []byte) error {
// Check if the content field is a string (the API supports both string and array).
contentField := gjson.GetBytes(data, "content")
if contentField.Exists() && contentField.Type == gjson.String {
type stringContent struct {
ToolUseID string `json:"tool_use_id"`
IsError param.Opt[bool] `json:"is_error,omitzero"`
CacheControl BetaCacheControlEphemeralParam `json:"cache_control,omitzero"`
Content string `json:"content"`
Type constant.ToolResult `json:"type"`
}
var sc stringContent
if err := json.Unmarshal(data, &sc); err != nil {
return err
}
r.ToolUseID = sc.ToolUseID
r.IsError = sc.IsError
r.CacheControl = sc.CacheControl
r.Content = []BetaToolResultBlockParamContentUnion{
{OfText: &BetaTextBlockParam{Text: sc.Content}},
}
r.Type = sc.Type
return nil
}
return apijson.UnmarshalRoot(data, r)
}

Expand All @@ -7966,6 +8012,16 @@ func NewBetaToolResultTextBlockParam(toolUseID string, text string, isError bool
return p
}

// NewBetaToolResultStringBlockParam creates a tool result block with content as a plain string.
// Per the API docs, tool_result content can be either a string or an array of content blocks.
func NewBetaToolResultStringBlockParam(toolUseID string, text string, isError bool) BetaToolResultBlockParam {
var p BetaToolResultBlockParam
p.ToolUseID = toolUseID
p.IsError = param.Opt[bool]{Value: isError}
p.ContentString = text
return p
}

// Only one field can be non-zero.
//
// Use [param.IsOmitted] to confirm if a field is set.
Expand Down
66 changes: 62 additions & 4 deletions message.go
Original file line number Diff line number Diff line change
Expand Up @@ -1830,12 +1830,21 @@ func NewToolUseBlock(id string, input any, name string) ContentBlockParamUnion {
}

func NewToolResultBlock(toolUseID string, content string, isError bool) ContentBlockParamUnion {
toolBlock := ToolResultBlockParam{
ToolUseID: toolUseID,
ContentString: content,
IsError: Bool(isError),
}
return ContentBlockParamUnion{OfToolResult: &toolBlock}
}

// NewToolResultBlockFromArray creates a tool result block with content as an array of content
// blocks. Use this when you need to pass structured content (e.g., text + images).
func NewToolResultBlockFromArray(toolUseID string, content []ToolResultBlockParamContentUnion, isError bool) ContentBlockParamUnion {
toolBlock := ToolResultBlockParam{
ToolUseID: toolUseID,
Content: []ToolResultBlockParamContentUnion{
{OfText: &TextBlockParam{Text: content}},
},
IsError: Bool(isError),
Content: content,
IsError: Bool(isError),
}
return ContentBlockParamUnion{OfToolResult: &toolBlock}
}
Expand Down Expand Up @@ -6725,16 +6734,65 @@ type ToolResultBlockParam struct {
// Create a cache control breakpoint at this content block.
CacheControl CacheControlEphemeralParam `json:"cache_control,omitzero"`
Content []ToolResultBlockParamContentUnion `json:"content,omitzero"`
// ContentString is an alternative to Content that allows setting a plain string
// value for the content field. Per the API docs, tool_result content can be
// either a string (e.g. "content": "15 degrees") or an array of content blocks.
// When ContentString is non-empty and Content is empty, it will be serialized as
// a plain string. This field is not serialized directly via struct tags.
ContentString string `json:"-"`
// This field can be elided, and will marshal its zero value as "tool_result".
Type constant.ToolResult `json:"type" api:"required"`
paramObj
}

func (r ToolResultBlockParam) MarshalJSON() (data []byte, err error) {
// If ContentString is set and Content array is empty, serialize content as a
// plain string instead of an array, matching the API's support for both formats.
if r.ContentString != "" && len(r.Content) == 0 {
type shadow struct {
ToolUseID string `json:"tool_use_id"`
IsError param.Opt[bool] `json:"is_error,omitzero"`
CacheControl CacheControlEphemeralParam `json:"cache_control,omitzero"`
Content string `json:"content"`
Type constant.ToolResult `json:"type"`
}
return json.Marshal(shadow{
ToolUseID: r.ToolUseID,
IsError: r.IsError,
CacheControl: r.CacheControl,
Content: r.ContentString,
Type: r.Type,
})
}
type shadow ToolResultBlockParam
return param.MarshalObject(r, (*shadow)(&r))
}
func (r *ToolResultBlockParam) UnmarshalJSON(data []byte) error {
// Check if the content field is a string (the API supports both string and array).
contentField := gjson.GetBytes(data, "content")
if contentField.Exists() && contentField.Type == gjson.String {
// Content is a plain string — deserialize other fields normally, then
// normalize the string into a Content array with a single TextBlock.
type stringContent struct {
ToolUseID string `json:"tool_use_id"`
IsError param.Opt[bool] `json:"is_error,omitzero"`
CacheControl CacheControlEphemeralParam `json:"cache_control,omitzero"`
Content string `json:"content"`
Type constant.ToolResult `json:"type"`
}
var sc stringContent
if err := json.Unmarshal(data, &sc); err != nil {
return err
}
r.ToolUseID = sc.ToolUseID
r.IsError = sc.IsError
r.CacheControl = sc.CacheControl
r.Content = []ToolResultBlockParamContentUnion{
{OfText: &TextBlockParam{Text: sc.Content}},
}
r.Type = sc.Type
return nil
}
return apijson.UnmarshalRoot(data, r)
}

Expand Down
Loading