From c5b947fff36ce977fb3a0e2ca3534d803abd13ef Mon Sep 17 00:00:00 2001 From: cmclark00 Date: Mon, 16 Mar 2026 20:30:55 -0400 Subject: [PATCH] fix(providers): populate Function field in ToolCall responses to survive session serialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ToolCall.Name and ToolCall.Arguments are both json:"-" — they are not written to or read from JSONL session storage. The Function field (json:"function,omitempty") is the only carrier that survives a serialize/deserialize round-trip; NormalizeToolCall already knows how to restore Name and Arguments from it. Three provider parseResponse paths were creating ToolCall without setting Function, so after the first session save every tool call in history was unrecoverable: - pkg/providers/anthropic/provider.go (SDK-based Anthropic provider) - pkg/providers/common/common.go (OpenAI-compat / Azure / etc.) - pkg/providers/codex_provider.go (Codex / o-series) Fix: set Function.Name and Function.Arguments (JSON-encoded) alongside Name and Arguments in each of these parseResponse paths, matching the pattern already used by anthropic_messages, antigravity, and tool_call_extract. Co-Authored-By: Claude Sonnet 4.6 --- pkg/providers/anthropic/provider.go | 8 ++++++++ pkg/providers/codex_provider.go | 8 ++++++++ pkg/providers/common/common.go | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/pkg/providers/anthropic/provider.go b/pkg/providers/anthropic/provider.go index 242ded175..87beae99a 100644 --- a/pkg/providers/anthropic/provider.go +++ b/pkg/providers/anthropic/provider.go @@ -351,10 +351,18 @@ func parseResponse(resp *anthropic.Message) *LLMResponse { log.Printf("anthropic: failed to decode tool call input for %q: %v", tu.Name, err) args = map[string]any{"raw": string(tu.Input)} } + argsJSON, _ := json.Marshal(args) toolCalls = append(toolCalls, ToolCall{ ID: tu.ID, Name: tu.Name, Arguments: args, + // Function must be populated so Name and Arguments survive + // JSON serialization to session storage (both fields are + // json:"-" on ToolCall; Function carries them through). + Function: &FunctionCall{ + Name: tu.Name, + Arguments: string(argsJSON), + }, }) } } diff --git a/pkg/providers/codex_provider.go b/pkg/providers/codex_provider.go index cf5c2d876..a88535ded 100644 --- a/pkg/providers/codex_provider.go +++ b/pkg/providers/codex_provider.go @@ -378,10 +378,18 @@ func parseCodexResponse(resp *responses.Response) *LLMResponse { if err := json.Unmarshal([]byte(item.Arguments), &args); err != nil { args = map[string]any{"raw": item.Arguments} } + argsJSON, _ := json.Marshal(args) toolCalls = append(toolCalls, ToolCall{ ID: item.CallID, Name: item.Name, Arguments: args, + // Function must be populated so Name and Arguments survive + // JSON serialization to session storage (both fields are + // json:"-" on ToolCall; Function carries them through). + Function: &FunctionCall{ + Name: item.Name, + Arguments: string(argsJSON), + }, }) } } diff --git a/pkg/providers/common/common.go b/pkg/providers/common/common.go index 23680a1bf..04b011cb0 100644 --- a/pkg/providers/common/common.go +++ b/pkg/providers/common/common.go @@ -190,11 +190,19 @@ func ParseResponse(body io.Reader) (*LLMResponse, error) { arguments = DecodeToolCallArguments(tc.Function.Arguments, name) } + argsJSON, _ := json.Marshal(arguments) toolCall := ToolCall{ ID: tc.ID, Name: name, Arguments: arguments, ThoughtSignature: thoughtSignature, + // Function must be populated so Name and Arguments survive + // JSON serialization to session storage (both fields are + // json:"-" on ToolCall; Function carries them through). + Function: &FunctionCall{ + Name: name, + Arguments: string(argsJSON), + }, } if thoughtSignature != "" {