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
58 changes: 15 additions & 43 deletions betamessageutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ func (r BetaTextEditorCodeExecutionToolResultBlock) ToParam() BetaTextEditorCode
ErrorMessage: paramutil.ToOpt(r.Content.ErrorMessage, r.Content.JSON.ErrorMessage),
}
} else {
p.Content = param.Override[BetaTextEditorCodeExecutionToolResultBlockParamContentUnion](r.Content.RawJSON())
p.Content = param.Override[BetaTextEditorCodeExecutionToolResultBlockParamContentUnion](json.RawMessage(r.Content.RawJSON()))
}
return p
}
Expand All @@ -393,21 +393,10 @@ func (r BetaBashCodeExecutionToolResultBlock) ToParam() BetaBashCodeExecutionToo
p.Type = r.Type
p.ToolUseID = r.ToolUseID

if r.Content.JSON.ErrorCode.Valid() {
p.Content.OfRequestBashCodeExecutionToolResultError = &BetaBashCodeExecutionToolResultErrorParam{
ErrorCode: BetaBashCodeExecutionToolResultErrorParamErrorCode(r.Content.ErrorCode),
}
} else {
requestBashContentResult := &BetaBashCodeExecutionResultBlockParam{
ReturnCode: r.Content.ReturnCode,
Stderr: r.Content.Stderr,
Stdout: r.Content.Stdout,
}
for _, block := range r.Content.Content {
requestBashContentResult.Content = append(requestBashContentResult.Content, block.ToParam())
}
p.Content.OfRequestBashCodeExecutionResultBlock = requestBashContentResult
}
// Use raw JSON passthrough to preserve fields that would otherwise be
// dropped by `omitzero` (e.g. zero ReturnCode, empty Stderr/Stdout, or
// empty ErrorCode), which the API requires on the next turn. See #322.
p.Content = param.Override[BetaBashCodeExecutionToolResultBlockParamContentUnion](json.RawMessage(r.Content.RawJSON()))

return p
}
Expand All @@ -423,20 +412,11 @@ func (r BetaCodeExecutionToolResultBlock) ToParam() BetaCodeExecutionToolResultB
var p BetaCodeExecutionToolResultBlockParam
p.Type = r.Type
p.ToolUseID = r.ToolUseID
if r.Content.JSON.ErrorCode.Valid() {
p.Content.OfError = &BetaCodeExecutionToolResultErrorParam{
ErrorCode: r.Content.ErrorCode,
}
} else {
p.Content.OfResultBlock = &BetaCodeExecutionResultBlockParam{
ReturnCode: r.Content.ReturnCode,
Stderr: r.Content.Stderr,
Stdout: r.Content.Stdout,
}
for _, block := range r.Content.Content {
p.Content.OfResultBlock.Content = append(p.Content.OfResultBlock.Content, block.ToParam())
}
}

// Use raw JSON passthrough to preserve fields that would otherwise be
// dropped by `omitzero` (e.g. zero ReturnCode, empty Stderr/Stdout, or
// empty ErrorCode), which the API requires on the next turn. See #322.
p.Content = param.Override[BetaCodeExecutionToolResultBlockParamContentUnion](json.RawMessage(r.Content.RawJSON()))
return p
}

Expand All @@ -451,19 +431,11 @@ func (r BetaToolSearchToolResultBlock) ToParam() BetaToolSearchToolResultBlockPa
var p BetaToolSearchToolResultBlockParam
p.Type = r.Type
p.ToolUseID = r.ToolUseID
if r.Content.JSON.ErrorCode.Valid() {
p.Content.OfRequestToolSearchToolResultError = &BetaToolSearchToolResultErrorParam{
ErrorCode: BetaToolSearchToolResultErrorParamErrorCode(r.Content.ErrorCode),
}
} else {
p.Content.OfRequestToolSearchToolSearchResultBlock = &BetaToolSearchToolSearchResultBlockParam{}
for _, block := range r.Content.ToolReferences {
p.Content.OfRequestToolSearchToolSearchResultBlock.ToolReferences = append(
p.Content.OfRequestToolSearchToolSearchResultBlock.ToolReferences,
block.ToParam(),
)
}
}

// Use raw JSON passthrough to preserve the required `error_code` field on
// the error variant (the typed field is tagged `omitzero`, so an empty
// ErrorCode would be silently dropped). See #317.
p.Content = param.Override[BetaToolSearchToolResultBlockParamContentUnion](json.RawMessage(r.Content.RawJSON()))
return p
}

Expand Down
58 changes: 15 additions & 43 deletions messageutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,21 +316,10 @@ func (r BashCodeExecutionToolResultBlock) ToParam() BashCodeExecutionToolResultB
p.Type = r.Type
p.ToolUseID = r.ToolUseID

if r.Content.JSON.ErrorCode.Valid() {
p.Content.OfRequestBashCodeExecutionToolResultError = &BashCodeExecutionToolResultErrorParam{
ErrorCode: BashCodeExecutionToolResultErrorCode(r.Content.ErrorCode),
}
} else {
requestBashContentResult := &BashCodeExecutionResultBlockParam{
ReturnCode: r.Content.ReturnCode,
Stderr: r.Content.Stderr,
Stdout: r.Content.Stdout,
}
for _, block := range r.Content.Content {
requestBashContentResult.Content = append(requestBashContentResult.Content, block.ToParam())
}
p.Content.OfRequestBashCodeExecutionResultBlock = requestBashContentResult
}
// Use raw JSON passthrough to preserve fields that would otherwise be
// dropped by `omitzero` (e.g. zero ReturnCode, empty Stderr/Stdout, or
// empty ErrorCode), which the API requires on the next turn. See #322.
p.Content = param.Override[BashCodeExecutionToolResultBlockParamContentUnion](json.RawMessage(r.Content.RawJSON()))
Comment on lines +319 to +322

return p
}
Expand All @@ -346,20 +335,11 @@ func (r CodeExecutionToolResultBlock) ToParam() CodeExecutionToolResultBlockPara
var p CodeExecutionToolResultBlockParam
p.Type = r.Type
p.ToolUseID = r.ToolUseID
if r.Content.JSON.ErrorCode.Valid() {
p.Content.OfRequestCodeExecutionToolResultError = &CodeExecutionToolResultErrorParam{
ErrorCode: r.Content.ErrorCode,
}
} else {
p.Content.OfRequestCodeExecutionResultBlock = &CodeExecutionResultBlockParam{
ReturnCode: r.Content.ReturnCode,
Stderr: r.Content.Stderr,
Stdout: r.Content.Stdout,
}
for _, block := range r.Content.Content {
p.Content.OfRequestCodeExecutionResultBlock.Content = append(p.Content.OfRequestCodeExecutionResultBlock.Content, block.ToParam())
}
}

// Use raw JSON passthrough to preserve fields that would otherwise be
// dropped by `omitzero` (e.g. zero ReturnCode, empty Stderr/Stdout, or
// empty ErrorCode), which the API requires on the next turn. See #322.
p.Content = param.Override[CodeExecutionToolResultBlockParamContentUnion](json.RawMessage(r.Content.RawJSON()))
return p
}

Expand All @@ -380,7 +360,7 @@ func (r TextEditorCodeExecutionToolResultBlock) ToParam() TextEditorCodeExecutio
ErrorMessage: paramutil.ToOpt(r.Content.ErrorMessage, r.Content.JSON.ErrorMessage),
}
} else {
p.Content = param.Override[TextEditorCodeExecutionToolResultBlockParamContentUnion](r.Content.RawJSON())
p.Content = param.Override[TextEditorCodeExecutionToolResultBlockParamContentUnion](json.RawMessage(r.Content.RawJSON()))
}
return p
}
Expand All @@ -389,19 +369,11 @@ func (r ToolSearchToolResultBlock) ToParam() ToolSearchToolResultBlockParam {
var p ToolSearchToolResultBlockParam
p.Type = r.Type
p.ToolUseID = r.ToolUseID
if r.Content.JSON.ErrorCode.Valid() {
p.Content.OfRequestToolSearchToolResultError = &ToolSearchToolResultErrorParam{
ErrorCode: ToolSearchToolResultErrorCode(r.Content.ErrorCode),
}
} else {
p.Content.OfRequestToolSearchToolSearchResultBlock = &ToolSearchToolSearchResultBlockParam{}
for _, block := range r.Content.ToolReferences {
p.Content.OfRequestToolSearchToolSearchResultBlock.ToolReferences = append(
p.Content.OfRequestToolSearchToolSearchResultBlock.ToolReferences,
block.ToParam(),
)
}
}

// Use raw JSON passthrough to preserve the required `error_code` field on
// the error variant (the typed field is tagged `omitzero`, so an empty
// ErrorCode would be silently dropped). See #317.
p.Content = param.Override[ToolSearchToolResultBlockParamContentUnion](json.RawMessage(r.Content.RawJSON()))
return p
}

Expand Down
40 changes: 40 additions & 0 deletions messageutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package anthropic_test

import (
"encoding/json"
"strings"
"testing"

"github.com/anthropics/anthropic-sdk-go"
Expand Down Expand Up @@ -32,6 +33,45 @@ func TestContentBlockUnionToParam(t *testing.T) {
}
})

t.Run("CodeExecutionToolResultBlock preserves zero-valued stdout/stderr/return_code (regression for #322)", func(t *testing.T) {
// A successful code execution where return_code is 0 and stdout/stderr
// are empty would previously be marshalled as
// {"type":"code_execution_result"} (all fields dropped via omitzero),
// which the API rejects on the next turn.
raw := `{"type":"code_execution_tool_result","tool_use_id":"srvtoolu_1","content":{"type":"code_execution_result","return_code":0,"stdout":"","stderr":"","content":[]}}`
result := unmarshalContentBlockParam(t, raw)
if result.OfCodeExecutionToolResult == nil {
t.Fatal("Expected OfCodeExecutionToolResult to be non-nil")
}
marshalled, err := json.Marshal(result.OfCodeExecutionToolResult.Content)
if err != nil {
t.Fatalf("failed to marshal content: %v", err)
}
got := string(marshalled)
for _, key := range []string{`"return_code"`, `"stdout"`, `"stderr"`} {
if !strings.Contains(got, key) {
t.Errorf("expected %s to be present in marshalled content (got %s)", key, got)
}
}
Comment on lines +51 to +55
})

t.Run("ToolSearchToolResultBlock preserves empty error_code (regression for #317)", func(t *testing.T) {
// An empty error_code on a tool_search_tool_result_error would
// previously be dropped, producing {"type":"tool_search_tool_result_error"}.
raw := `{"type":"tool_search_tool_result","tool_use_id":"srvtoolu_2","content":{"type":"tool_search_tool_result_error","error_code":""}}`
result := unmarshalContentBlockParam(t, raw)
if result.OfToolSearchToolResult == nil {
t.Fatal("Expected OfToolSearchToolResult to be non-nil")
}
marshalled, err := json.Marshal(result.OfToolSearchToolResult.Content)
if err != nil {
t.Fatalf("failed to marshal content: %v", err)
}
if !strings.Contains(string(marshalled), `"error_code"`) {
t.Errorf("expected error_code to be present in marshalled content (got %s)", string(marshalled))
}
Comment on lines +70 to +72
})

t.Run("WebSearchToolResultBlock with search results", func(t *testing.T) {
result := unmarshalContentBlockParam(t, `{"type":"web_search_tool_result","tool_use_id":"test123","content":[{"type":"web_search_result","title":"Test Web Title","url":"https://test.com","encrypted_content":"abc123","page_age":"1 day ago"}]}`)
var block anthropic.ContentBlockUnion
Expand Down