diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 1c0916a..59dc93e 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -205,6 +205,19 @@ func (l *Loop) RunStream(ctx context.Context, input string, history ...llm.Messa Content: result.Content, }) } + + // Surface which tools were actually called, and with + // what arguments, to the caller — a dedicated chunk, + // separate from the content-streaming gate below, since + // that gate exists to hide raw provider deltas during a + // tool-call round, not to hide the fact that a call + // happened at all. Without this, callers (e.g. a UI + // wanting to show "used tool X" or track which files a + // write/edit touched) have no way to observe tool + // calls unless they also happen to be the Approver. + if !yield(llm.StreamChunk{ToolCalls: chunk.ToolCalls}, nil) { + return + } } // A trailing usage-only chunk (no Delta/ReasoningDelta, per diff --git a/pkg/agent/loop_test.go b/pkg/agent/loop_test.go index 4c59bcf..ccf5931 100644 --- a/pkg/agent/loop_test.go +++ b/pkg/agent/loop_test.go @@ -669,11 +669,17 @@ func TestRun_Stream_WithToolCalls(t *testing.T) { chunks = append(chunks, chunk) } - if len(chunks) != 1 { - t.Errorf("expected 1 chunk (only the 'done' chunk), got %d", len(chunks)) + // One chunk surfacing the tool call itself (so callers can observe + // which tools ran and with what arguments), then the final "done" + // content chunk. + if len(chunks) != 2 { + t.Fatalf("expected 2 chunks (tool call + 'done'), got %d: %+v", len(chunks), chunks) } - if chunks[0].Delta != "done" { - t.Errorf("expected 'done', got %q", chunks[0].Delta) + if len(chunks[0].ToolCalls) != 1 || chunks[0].ToolCalls[0].Name != "greet" { + t.Errorf("expected the first chunk to surface the 'greet' tool call, got %+v", chunks[0].ToolCalls) + } + if chunks[1].Delta != "done" { + t.Errorf("expected 'done', got %q", chunks[1].Delta) } }