diff --git a/pkg/app/app.go b/pkg/app/app.go index db899d9e2..a6fb92be5 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -1,6 +1,7 @@ package app import ( + "cmp" "context" "encoding/base64" "errors" @@ -997,12 +998,24 @@ func (a *App) mergeEvents(events []tea.Msg) []tea.Msg { result = append(result, merged) case *runtime.PartialToolCallEvent: - // For PartialToolCallEvent, keep only the latest one per tool call ID - // Only merge consecutive events with the same ID + // For PartialToolCallEvent, merge consecutive events with the same tool call ID + // by concatenating argument deltas latest := ev for i+1 < len(events) { if next, ok := events[i+1].(*runtime.PartialToolCallEvent); ok && next.ToolCall.ID == ev.ToolCall.ID { - latest = next + latest = &runtime.PartialToolCallEvent{ + Type: ev.Type, + ToolCall: tools.ToolCall{ + ID: ev.ToolCall.ID, + Type: ev.ToolCall.Type, + Function: tools.FunctionCall{ + Name: cmp.Or(next.ToolCall.Function.Name, latest.ToolCall.Function.Name), + Arguments: latest.ToolCall.Function.Arguments + next.ToolCall.Function.Arguments, + }, + }, + ToolDefinition: next.ToolDefinition, + AgentContext: ev.AgentContext, + } i++ } else { break diff --git a/pkg/runtime/streaming.go b/pkg/runtime/streaming.go index 62b132076..eb80b1d70 100644 --- a/pkg/runtime/streaming.go +++ b/pkg/runtime/streaming.go @@ -152,10 +152,21 @@ func (r *LocalRuntime) handleStream(ctx context.Context, stream chat.MessageStre tc.Function.Arguments += delta.Function.Arguments } - // Emit PartialToolCall once we have a name, and on subsequent argument deltas + // Emit PartialToolCall once we have a name, and on subsequent argument deltas. + // Only the current token (delta.Function.Arguments) is sent, not the + // full accumulated arguments, to avoid re-transmitting the entire + // payload on every token. if tc.Function.Name != "" && (learningName || delta.Function.Arguments != "") { if !emittedPartial[delta.ID] || delta.Function.Arguments != "" { - events <- PartialToolCall(*tc, toolDefMap[tc.Function.Name], a.Name()) + partial := tools.ToolCall{ + ID: tc.ID, + Type: tc.Type, + Function: tools.FunctionCall{ + Name: tc.Function.Name, + Arguments: delta.Function.Arguments, + }, + } + events <- PartialToolCall(partial, toolDefMap[tc.Function.Name], a.Name()) emittedPartial[delta.ID] = true } } diff --git a/pkg/tui/components/messages/messages.go b/pkg/tui/components/messages/messages.go index 810ef4a66..531113bab 100644 --- a/pkg/tui/components/messages/messages.go +++ b/pkg/tui/components/messages/messages.go @@ -1301,7 +1301,11 @@ func (m *model) AddOrUpdateToolCall(agentName string, toolCall tools.ToolCall, t if msg.Type == types.MessageTypeToolCall && msg.ToolCall.ID == toolCall.ID { msg.ToolStatus = status if toolCall.Function.Arguments != "" { - msg.ToolCall.Function.Arguments = toolCall.Function.Arguments + if status == types.ToolStatusPending { + msg.ToolCall.Function.Arguments += toolCall.Function.Arguments + } else { + msg.ToolCall.Function.Arguments = toolCall.Function.Arguments + } } m.invalidateItem(i) return nil diff --git a/pkg/tui/components/reasoningblock/reasoningblock.go b/pkg/tui/components/reasoningblock/reasoningblock.go index c53a50cfc..cd4778df5 100644 --- a/pkg/tui/components/reasoningblock/reasoningblock.go +++ b/pkg/tui/components/reasoningblock/reasoningblock.go @@ -206,7 +206,11 @@ func (m *Model) UpdateToolCall(toolCallID string, status types.ToolStatus, args } entry.msg.ToolStatus = status if args != "" { - entry.msg.ToolCall.Function.Arguments = args + if status == types.ToolStatusPending { + entry.msg.ToolCall.Function.Arguments += args + } else { + entry.msg.ToolCall.Function.Arguments = args + } } m.toolEntries[i] = entry return