diff --git a/core/schemas/utils.go b/core/schemas/utils.go index 0f9c6f90a0..473b115284 100644 --- a/core/schemas/utils.go +++ b/core/schemas/utils.go @@ -1223,11 +1223,26 @@ func DeepCopyResponsesMessage(original ResponsesMessage) ResponsesMessage { copy.Status = ©Status } + if original.Phase != nil { + copy.Phase = new(string) + *copy.Phase = *original.Phase + } + if original.Role != nil { copyRole := *original.Role copy.Role = ©Role } + // Deep copy Author and Recipient (multi-agent collab_tool_call items). + // json.RawMessage is a []byte slice; copy the bytes so callers don't share + // (and mutate) the underlying array. + if original.Author != nil { + copy.Author = append(json.RawMessage(nil), original.Author...) + } + if original.Recipient != nil { + copy.Recipient = append(json.RawMessage(nil), original.Recipient...) + } + if original.Content != nil { copy.Content = &ResponsesMessageContent{} @@ -1377,6 +1392,17 @@ func deepCopyResponsesMessageContentBlock(original ResponsesMessageContentBlock) copy.Text = ©Text } + // Reasoning replay fields: Signature and EncryptedContent are echoed back + // verbatim to the provider, so they must survive the deep copy. + if original.Signature != nil { + copy.Signature = new(string) + *copy.Signature = *original.Signature + } + if original.EncryptedContent != nil { + copy.EncryptedContent = new(string) + *copy.EncryptedContent = *original.EncryptedContent + } + // Deep copy ResponsesInputMessageContentBlockImage if original.ResponsesInputMessageContentBlockImage != nil { copyImage := &ResponsesInputMessageContentBlockImage{} diff --git a/framework/streaming/responses.go b/framework/streaming/responses.go index 63c05c10dd..e6644d6fa6 100644 --- a/framework/streaming/responses.go +++ b/framework/streaming/responses.go @@ -1,6 +1,7 @@ package streaming import ( + "encoding/json" "fmt" "sort" "strings" @@ -219,6 +220,16 @@ func deepCopyResponsesMessage(original schemas.ResponsesMessage) schemas.Respons } } + // Deep copy Author and Recipient (multi-agent collab_tool_call items). + // json.RawMessage is a []byte slice; copy the bytes so accumulators don't + // share (and mutate) the underlying array. + if original.Author != nil { + copy.Author = append(json.RawMessage(nil), original.Author...) + } + if original.Recipient != nil { + copy.Recipient = append(json.RawMessage(nil), original.Recipient...) + } + // Deep copy ResponsesReasoning if present if original.ResponsesReasoning != nil { copy.ResponsesReasoning = &schemas.ResponsesReasoning{} @@ -443,6 +454,17 @@ func deepCopyResponsesMessageContentBlock(original schemas.ResponsesMessageConte copy.Text = ©Text } + // Reasoning replay fields: Signature and EncryptedContent are echoed back + // verbatim to the provider, so they must survive the deep copy. + if original.Signature != nil { + copy.Signature = new(string) + *copy.Signature = *original.Signature + } + if original.EncryptedContent != nil { + copy.EncryptedContent = new(string) + *copy.EncryptedContent = *original.EncryptedContent + } + // Copy other specific content type fields as needed if original.ResponsesOutputMessageContentText != nil { t := *original.ResponsesOutputMessageContentText