From ffc3f07725c5ea09d4db0e3f8e3bcc970bf399d5 Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:35:59 +0200 Subject: [PATCH] agent: emit 'call' events during streaming In the streaming path all tool calls were being added to skippedCalls, causing the post-stream Act loop to suppress every 'call' emit. The '-> tool(args)' lines therefore never appeared in the UI. Fix: emit the 'call' OutputMsg inside runStreamStep once the stream reports Done and all tool-call arguments are fully accumulated. The Act loop still skips re-emitting them (correct), while the UI now sees one 'call' event per tool invocation. --- agent/loop.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/agent/loop.go b/agent/loop.go index c6d3413..9c9c594 100644 --- a/agent/loop.go +++ b/agent/loop.go @@ -78,6 +78,9 @@ func (l *Loop) Run(ctx context.Context, state State) error { // Act: execute tool calls. var results []ToolResult for _, tc := range resp.Message.ToolCalls { + // In the non-streaming path, emit the call announcement here. + // In the streaming path this was already emitted inside + // runStreamStep (once all arguments were accumulated), so skip it. if !l.skippedCalls[tc.Name] { l.emit(OutputMsg{Role: "call", Name: tc.Name, Content: string(tc.Arguments)}) } @@ -164,7 +167,6 @@ func (l *Loop) runStreamStep( } for _, tc := range ev.ToolCalls { - l.skippedCalls[tc.Name] = true accumulatedTcs = append(accumulatedTcs, tc) } @@ -173,6 +175,12 @@ func (l *Loop) runStreamStep( msg.Content = content.String() msg.Role = "assistant" msg.ToolCalls = accumulatedTcs + // Emit 'call' now that arguments are fully accumulated, and + // mark each as skipped so the Act loop does not re-emit it. + for _, tc := range accumulatedTcs { + l.emit(OutputMsg{Role: "call", Name: tc.Name, Content: string(tc.Arguments)}) + l.skippedCalls[tc.Name] = true + } return msg, nil } }