feat(ai): add tool calling support to Stream()#1445
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1445 +/- ##
==========================================
+ Coverage 69.06% 69.15% +0.08%
==========================================
Files 362 362
Lines 28191 28315 +124
==========================================
+ Hits 19470 19581 +111
- Misses 7861 7870 +9
- Partials 860 864 +4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR adds end-to-end tool-calling support to Conversation.Stream(), aligning it with the existing Prompt() tool-call loop and exposing tool invocations to streaming consumers.
Changes:
- Add
tool_callstream events and include tool-call data inStreamEvent. - Implement a tool-call/re-prompt loop inside
Conversation.Stream()and emit tool-call events. - Enhance the OpenAI streaming provider to pass tools through and accumulate tool-call argument fragments across chunks into
Response.ToolCalls().
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
contracts/ai/stream.go |
Adds StreamEventTypeToolCall and StreamEvent.ToolCalls to support streaming tool-call notifications. |
ai/streamable_response.go |
Extends default SSE JSON payload to include tool_calls for tool-call events. |
ai/openai/provider.go |
Passes tools into streaming requests and accumulates tool-call deltas into Response.ToolCalls(). |
ai/conversation.go |
Implements a streaming tool-call loop (execute tools + re-stream until final text). |
ai/conversation_test.go |
Updates stream behavior expectations and adds coverage for the streaming tool-call loop. |
ai/console/stubs.go |
Updates console agent stub to satisfy the Tools() method. |
AGENTS.md |
Adds planning rules to contributor/agent guidance. |
| r.mu.Unlock() | ||
| } | ||
|
|
||
| initialCtx, cancelInitial := context.WithCancel(r.ctx) |
There was a problem hiding this comment.
initialCtx is only canceled on the early error path or when streamCtx.Done() fires. On the normal success path this child context is left alive after the first stream finishes, which leaks cancellation resources across repeated Stream() calls. It would be safer to cancel it as soon as the initial stream is fully consumed, and to add a test that exercises successful completion/cleanup.
Summary
Stream()now runs a full agentic tool-call loop (up toMaxToolCallIterations), mirroring the existingPrompt()tool-calling behaviorStreamEventTypeToolCallevents so callers can observe them in real timeResponse.ToolCalls()Why
Before this change,
Stream()stripped tools from the agent prompt and returned plain text only. Callers who needed tool-calling had to fall back toPrompt()and lose the streaming UX.Now
Stream()passes the agent's tools to the provider, accumulates tool-call deltas across streaming chunks, executes the tools, and re-prompts in a loop — exactly likePrompt()does — while forwarding text deltas to the caller in real time. Intermediatedoneevents from inner iterations are suppressed; only the finaldoneis forwarded.