Skip to content
Closed
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
6 changes: 5 additions & 1 deletion framework/streaming/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,10 @@ func deepCopyResponsesMessage(original schemas.ResponsesMessage) schemas.Respons
copyAction := *original.ResponsesToolMessage.Action.ResponsesMCPApprovalRequestAction
copy.ResponsesToolMessage.Action.ResponsesMCPApprovalRequestAction = &copyAction
}

// Bare-string actions (e.g. image_generation_call's "generate") live in a
// field the Framework's pinned Core release may predate; copy it by name.
copyOptionalStringFieldByName(copy.ResponsesToolMessage.Action, original.ResponsesToolMessage.Action, "ResponsesToolCallActionStr")
}

if original.ResponsesToolMessage.Caller != nil {
Expand Down Expand Up @@ -502,7 +506,7 @@ func copyRawMessageFieldByName(dst *schemas.ResponsesMessage, src schemas.Respon
}
}

func copyOptionalStringFieldByName(dst *schemas.ResponsesToolMessage, src *schemas.ResponsesToolMessage, fieldName string) {
func copyOptionalStringFieldByName(dst, src any, fieldName string) {
srcField := reflect.ValueOf(src).Elem().FieldByName(fieldName)
if !srcField.IsValid() || srcField.IsNil() {
return
Expand Down
29 changes: 29 additions & 0 deletions framework/streaming/responses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package streaming

import (
"fmt"
"reflect"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -154,6 +155,34 @@ func TestDeepCopyResponsesStreamResponseCopiesToolCaller(t *testing.T) {
}
}

// TestDeepCopyResponsesMessagePreservesBareStringAction verifies that a
// bare-string action (e.g. image_generation_call's "generate") survives the
// accumulator's deep copy instead of being dropped as an empty action struct.
func TestDeepCopyResponsesMessagePreservesBareStringAction(t *testing.T) {
action := &schemas.ResponsesToolMessageActionStruct{}
actionField := reflect.ValueOf(action).Elem().FieldByName("ResponsesToolCallActionStr")
if !actionField.IsValid() {
t.Skip("released core dependency predates bare-string tool call actions")
}
actionField.Set(reflect.ValueOf(schemas.Ptr("generate")))

copied := deepCopyResponsesMessage(schemas.ResponsesMessage{
ResponsesToolMessage: &schemas.ResponsesToolMessage{
Action: action,
},
})
if copied.ResponsesToolMessage == nil || copied.ResponsesToolMessage.Action == nil {
t.Fatalf("unexpected copied message: %#v", copied)
}
copiedField := reflect.ValueOf(copied.ResponsesToolMessage.Action).Elem().FieldByName("ResponsesToolCallActionStr")
if copiedField.IsNil() || copiedField.Elem().String() != "generate" {
t.Fatal("bare-string action was not preserved")
}
if copiedField.Pointer() == actionField.Pointer() {
t.Fatal("bare-string action pointer was aliased")
}
}

// TestBuildResponsesMessageAccumulatesReasoningSummary verifies reasoning
// summary deltas (no content index) concatenate into a single summary entry.
func TestBuildResponsesMessageAccumulatesReasoningSummary(t *testing.T) {
Expand Down