From a75ab0bc80349e75b03d98ab84d5efe049da2325 Mon Sep 17 00:00:00 2001 From: Carson Farmer Date: Fri, 29 May 2026 15:35:14 -0700 Subject: [PATCH 1/2] feat: add ClientMetadata to ToolResultPart Carries tool-level client metadata through the message round-trip so downstream consumers can use it for display reconstruction without polluting the LLM conversation context. - ToolResultPart.ClientMetadata (string, omitempty) - toResponseMessages copies from ToolResultContent.ClientMetadata - JSON marshal/unmarshal includes the new field --- agent.go | 1 + content.go | 1 + content_json.go | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/agent.go b/agent.go index 62e219457..eb4ed2c86 100644 --- a/agent.go +++ b/agent.go @@ -642,6 +642,7 @@ func toResponseMessages(content []Content) []Message { Output: result.Result, ProviderExecuted: result.ProviderExecuted, ProviderOptions: ProviderOptions(result.ProviderMetadata), + ClientMetadata: result.ClientMetadata, } if result.ProviderExecuted { // Provider-executed tool results (e.g. web search) diff --git a/content.go b/content.go index f2a122ec4..1318d08c8 100644 --- a/content.go +++ b/content.go @@ -259,6 +259,7 @@ type ToolResultPart struct { Output ToolResultOutputContent `json:"output"` ProviderExecuted bool `json:"provider_executed"` ProviderOptions ProviderOptions `json:"provider_options"` + ClientMetadata string `json:"client_metadata,omitempty"` } // GetType returns the type of the tool result part. diff --git a/content_json.go b/content_json.go index ee1cc8212..8295eea1e 100644 --- a/content_json.go +++ b/content_json.go @@ -715,11 +715,13 @@ func (t ToolResultPart) MarshalJSON() ([]byte, error) { Output ToolResultOutputContent `json:"output"` ProviderExecuted bool `json:"provider_executed"` ProviderOptions ProviderOptions `json:"provider_options,omitempty"` + ClientMetadata string `json:"client_metadata,omitempty"` }{ ToolCallID: t.ToolCallID, Output: t.Output, ProviderExecuted: t.ProviderExecuted, ProviderOptions: t.ProviderOptions, + ClientMetadata: t.ClientMetadata, }) if err != nil { return nil, err @@ -743,6 +745,7 @@ func (t *ToolResultPart) UnmarshalJSON(data []byte) error { Output json.RawMessage `json:"output"` ProviderExecuted bool `json:"provider_executed"` ProviderOptions map[string]json.RawMessage `json:"provider_options,omitempty"` + ClientMetadata string `json:"client_metadata,omitempty"` } if err := json.Unmarshal(mpj.Data, &aux); err != nil { @@ -751,6 +754,7 @@ func (t *ToolResultPart) UnmarshalJSON(data []byte) error { t.ToolCallID = aux.ToolCallID t.ProviderExecuted = aux.ProviderExecuted + t.ClientMetadata = aux.ClientMetadata // Unmarshal the Output field output, err := UnmarshalToolResultOutputContent(aux.Output) From e1ea4763bb202c18d2b87f00b98feb65996df432 Mon Sep 17 00:00:00 2001 From: Carson Farmer Date: Fri, 29 May 2026 15:45:02 -0700 Subject: [PATCH 2/2] test: add ClientMetadata round-trip coverage for ToolResultPart - JSON marshal/unmarshal test case includes ClientMetadata - compareMessageParts checks ClientMetadata equality - toResponseMessages test verifies ClientMetadata survives conversion --- agent_test.go | 7 +++++-- json_test.go | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/agent_test.go b/agent_test.go index da10747cd..97963de40 100644 --- a/agent_test.go +++ b/agent_test.go @@ -1918,8 +1918,9 @@ func TestToResponseMessages_ProviderExecutedRouting(t *testing.T) { }, // Regular tool result. &ToolResultContent{ - ToolCallID: "toolu_02", - Result: ToolResultOutputContentText{Text: "2"}, + ToolCallID: "toolu_02", + Result: ToolResultOutputContentText{Text: "2"}, + ClientMetadata: `{"precision":"high"}`, }, // Some trailing text. &TextContent{Text: "Done."}, @@ -1967,9 +1968,11 @@ func TestToResponseMessages_ProviderExecutedRouting(t *testing.T) { require.Equal(t, MessageRoleTool, toolMsg.Role) require.Len(t, toolMsg.Content, 1) + // Verify regular tool result is in tool message and client metadata survived. tr2, ok := AsMessagePart[ToolResultPart](toolMsg.Content[0]) require.True(t, ok) require.Equal(t, "toolu_02", tr2.ToolCallID) + require.Equal(t, `{"precision":"high"}`, tr2.ClientMetadata) require.False(t, tr2.ProviderExecuted) } diff --git a/json_test.go b/json_test.go index b1088434a..23958d97b 100644 --- a/json_test.go +++ b/json_test.go @@ -80,6 +80,7 @@ func TestMessageJSONSerialization(t *testing.T) { Output: ToolResultOutputContentText{ Text: "The weather is sunny, 72°F", }, + ClientMetadata: `{"units":"imperial"}`, }, }, }, @@ -245,6 +246,9 @@ func compareMessagePart(t *testing.T, index int, original, decoded MessagePart) if orig.ToolCallID != dec.ToolCallID { t.Errorf("content[%d] tool result call id mismatch: got %q, want %q", index, dec.ToolCallID, orig.ToolCallID) } + if orig.ClientMetadata != dec.ClientMetadata { + t.Errorf("content[%d] tool result client metadata mismatch: got %q, want %q", index, dec.ClientMetadata, orig.ClientMetadata) + } compareToolResultOutput(t, index, orig.Output, dec.Output) } }