feat: support streaming tool outputs by returning an AsyncIterable fr…#949
Conversation
🦋 Changeset detectedLatest commit: 0319ac7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
📝 WalkthroughWalkthroughAdds streaming tool execution: tools' execute can now return AsyncIterable (yielding intermediate results) in addition to Promise/sync values; agent and tooling adapted to iterate, validate, and surface progress before final result. Changes
Sequence Diagram(s)sequenceDiagram
participant Agent
participant Tool
participant Validation
participant Hooks
Agent->>Tool: execute(params)
alt Tool returns AsyncIterable / async generator
Tool-->>Agent: yield {status: "loading", ...}
Agent->>Validation: validate(intermediate)
Validation-->>Agent: valid
Agent->>Hooks: onToolProgress(intermediate)
Tool-->>Agent: yield {status: "success", data}
Agent->>Validation: validate(final)
Validation-->>Agent: valid
else Tool returns PromiseLike / value
Tool-->>Agent: resolve result
Agent->>Validation: validate(result)
Validation-->>Agent: valid
end
Agent->>Hooks: onToolEnd(finalResult or error)
Agent->>Agent: span.end()
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@packages/core/src/agent/agent.ts`:
- Around line 3688-3747: The finally block in the async generator branch of the
execute wrapper redundantly calls oc.traceContext.endChildSpan(toolSpan,
"completed", {}) even though handleToolSuccess and handleToolError already end
the span; remove the endChildSpan call from the finally of the async-generator
path (the function created where execute && isAsyncGeneratorFunction(execute) is
handled) so the span is ended only in handleToolSuccess/handleToolError, or if
the tracing API is guaranteed idempotent leave the call but add a clear comment
next to oc.traceContext.endChildSpan referencing handleToolSuccess and
handleToolError to document why duplicate end calls are safe.
🧹 Nitpick comments (3)
packages/core/src/agent/test-utils.ts (1)
363-371: Consider importingToolExecutionResultfor type consistency.The inline type
PromiseLike<any> | AsyncIterable<any> | anyduplicates theToolExecutionResult<any>type. Using the canonical type would maintain consistency and reduce drift.♻️ Suggested change
+import type { ToolExecutionResult } from "../tool"; // ... existing imports ... export function createMockTool( name: string, - execute?: (params: any, options?: any) => PromiseLike<any> | AsyncIterable<any> | any, + execute?: (params: any, options?: any) => ToolExecutionResult<any>, options?: {examples/with-tools/src/tools/weather.ts (1)
4-22: Well-designed discriminated union for streaming states.The schema effectively models the loading/success states for streaming tool output. Minor note: on line 9,
z.undefined().optional()could be simplified to justz.undefined()since the field is already typed as undefined.♻️ Optional simplification
z.object({ status: z.literal("loading"), text: z.string(), - weather: z.undefined().optional(), + weather: z.undefined(), }),packages/core/src/tool/manager/index.spec.ts (1)
234-257: Consider adding tests forAsyncIterableexecution path.The type annotations have been updated to support
ToolExecutionResult<any>, but the tests only exercise thePromisepath viamockResolvedValue. Consider adding a test case that verifies the manager correctly handles an async generator function to ensure the new streaming capability is covered.Run the following script to check if async generator tests exist elsewhere:
#!/bin/bash # Search for async generator tests related to tool execution rg -n --type=ts -C3 'async\s*\*|AsyncIterable|yield.*status' packages/core/src
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
.changeset/upset-shrimps-guess.mdexamples/with-tools/src/tools/weather.tspackages/core/src/agent/agent.tspackages/core/src/agent/test-utils.tspackages/core/src/tool/index.tspackages/core/src/tool/manager/ToolManager.tspackages/core/src/tool/manager/index.spec.tswebsite/docs/agents/tools.md
🧰 Additional context used
📓 Path-based instructions (1)
**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
**/*.ts: Maintain type safety in TypeScript-first codebase
Never use JSON.stringify; use thesafeStringifyfunction instead, imported from@voltagent/internal
Files:
packages/core/src/agent/test-utils.tspackages/core/src/tool/index.tspackages/core/src/tool/manager/ToolManager.tspackages/core/src/agent/agent.tspackages/core/src/tool/manager/index.spec.tsexamples/with-tools/src/tools/weather.ts
🧬 Code graph analysis (4)
packages/core/src/agent/test-utils.ts (1)
examples/with-tools/src/tools/weather.ts (1)
execute(37-63)
packages/core/src/tool/index.ts (1)
packages/core/src/agent/providers/base/types.ts (2)
ToolExecuteOptions(266-287)ToolSchema(245-245)
packages/core/src/tool/manager/ToolManager.ts (1)
packages/core/src/tool/index.ts (2)
ToolExecutionResult(12-12)AgentTool(41-41)
packages/core/src/tool/manager/index.spec.ts (2)
packages/core/src/agent/providers/base/types.ts (1)
ToolExecuteOptions(266-287)packages/core/src/tool/index.ts (1)
ToolExecutionResult(12-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: Test core
- GitHub Check: Test voice
- GitHub Check: Build (Node 20)
- GitHub Check: Build (Node 24)
- GitHub Check: Build (Node 22)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (15)
packages/core/src/tool/index.ts (3)
12-13: Well-designed type for streaming support.The
ToolExecutionResult<T>union type elegantly supports all three execution patterns: synchronous return, promise-based, and async iterable streaming. This provides flexibility for tool implementations while maintaining type safety.
139-144: LGTM - Consistent signature update for ToolOptions.execute.The return type and JSDoc are properly updated to reflect the new streaming capability.
211-216: LGTM - Consistent signature update for Tool.execute.The Tool class's execute method signature mirrors the ToolOptions type, maintaining API consistency.
packages/core/src/tool/manager/ToolManager.ts (2)
5-5: LGTM - Correct import of ToolExecutionResult.
48-61: LGTM - Type signatures properly updated for streaming support.The
createToolExecuteFunctionandManagedTool.executetypes are correctly updated to useToolExecutionResult<any>, maintaining consistency with the core type definition.packages/core/src/agent/agent.ts (4)
323-337: LGTM - Utility functions for async generator detection.The
isAsyncGeneratorFunctionandisAsyncIterabletype guards are correctly implemented for detecting streaming tools.
3640-3686: Well-structured helper functions for tool lifecycle management.The
handleToolSuccessandhandleToolErrorhelpers centralize span management and hook invocation, improving code organization and ensuring consistent behavior across both generator and non-generator paths.
3766-3772: Good handling of AsyncIterable results in non-generator path.This correctly drains an async iterable to extract the final value when a tool returns an AsyncIterable but wasn't detected as an async generator function.
3749-3785: Verify span ending consistency in non-generator path.Similar to the generator path, the
finallyblock at line 3783 callsendChildSpanafterhandleToolSuccessorhandleToolErrorhave already ended the span. Ensure this is intentional and safe.#!/bin/bash # Description: Check if endChildSpan is idempotent or if double-ending causes issues # Search for the endChildSpan implementation to understand its behavior ast-grep --pattern $'endChildSpan($_, $_, $_) { $$$ }' # Also search for how spans are ended rg -n "endChildSpan|toolSpan.end" --type=ts -C2examples/with-tools/src/tools/weather.ts (1)
37-63: Good example of async generator tool implementation.This effectively demonstrates the streaming tool pattern - yielding a loading state immediately, then yielding the final success state after the async operation completes. This provides a clear reference implementation for users.
website/docs/agents/tools.md (1)
49-99: Well-documented streaming tool results section.The documentation clearly explains the streaming pattern with:
- Clear explanation that each
yieldemits a preliminary result and the last yield is the final result- Proper use of discriminated union for type-safe loading/success states
- Realistic example showing the async generator pattern
.changeset/upset-shrimps-guess.md (1)
1-29: LGTM - Clear changeset description.The changeset correctly describes the new streaming feature with a minimal working example. The simplified example (without
outputSchema) is appropriate for a changeset since it demonstrates the core async generator pattern concisely.packages/core/src/tool/manager/index.spec.ts (3)
3-10: Import updated correctly to includeToolExecutionResult.The import statement properly includes the new
ToolExecutionResulttype alongside existing exports.
241-257: Type annotation correctly updated forToolExecutionResult.The execute wrapper type is properly updated to
ToolExecutionResult<any>, aligning with the new union type that supportsPromiseLike<T> | AsyncIterable<T> | T.
269-279: Client-side tool type annotation consistent with server-side.The type annotation update is correctly applied to the client-side tool entry as well, maintaining consistency.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
Deploying voltagent with
|
| Latest commit: |
0319ac7
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://59eeea2f.voltagent.pages.dev |
| Branch Preview URL: | https://feat-support-streaming-tool.voltagent.pages.dev |
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/core/src/agent/agent.ts (1)
3747-3784: Duplicate span ending persists in non-generator path.The
finallyblock at line 3781 callsendChildSpan(toolSpan, "completed", {}), but the span is already ended withinhandleToolSuccess(line 3643) orhandleToolError(line 3670). This causes double span ending for the non-generator execution path.The PR's "fix: duplicate endSpan" commit addressed the async generator path by removing its
finallyblock, but the same pattern remains in this path.🐛 Suggested fix - remove redundant span ending
return result; } catch (e) { return await handleToolError(e); - } finally { - // End the span if it was created - oc.traceContext.endChildSpan(toolSpan, "completed", {}); } }); };
♻️ Duplicate comments (1)
packages/core/src/agent/agent.ts (1)
3688-3745: Duplicate span ending issue has been fixed.The
finallyblock that was causing duplicateendChildSpancalls (flagged in previous review) has been removed. The span is now correctly ended only withinhandleToolSuccessorhandleToolError.
🧹 Nitpick comments (1)
packages/core/src/agent/agent.ts (1)
323-337: Async generator detection may miss transpiled generators.The
isAsyncGeneratorFunctioncheck relies on constructor identity, which works for native async generators but may fail for transpiled code from older TypeScript targets or third-party libraries. However, the code has defensive fallback logic: if the check fails, it executes as a regular async function and then checksisAsyncIterable(result), which correctly handles transpiled async generators. The optimization of theasync function*wrapper would be bypassed, but functionality remains intact.Since this codebase targets ES2022, transpiled async generators are unlikely to be encountered. Consider this refactor only if external tools from legacy builds become a common pattern.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/core/src/agent/agent.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
**/*.ts: Maintain type safety in TypeScript-first codebase
Never use JSON.stringify; use thesafeStringifyfunction instead, imported from@voltagent/internal
Files:
packages/core/src/agent/agent.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: Test cli
- GitHub Check: Test server-core
- GitHub Check: Test core
- GitHub Check: Build (Node 24)
- GitHub Check: Build (Node 20)
- GitHub Check: Build (Node 22)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (3)
packages/core/src/agent/agent.ts (3)
53-53: LGTM!The
ToolExecutionResultimport is correctly added to support the new streaming tool execution return type.
3599-3639: LGTM!The signature update from
Promise<any>toToolExecutionResult<any>correctly reflects that tool execution can now return either a Promise or an AsyncGenerator for streaming results.
3640-3686: LGTM on helper extraction.The
handleToolSuccessandhandleToolErrorhelpers are well-structured and properly encapsulate span lifecycle management and hook invocation.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
…om
executePR Checklist
Please check if your PR fulfills the following requirements:
Bugs / Features
What is the current behavior?
What is the new behavior?
fixes (issue)
Notes for reviewers
Summary by cubic
Added support for streaming tool outputs. Tools can now return an AsyncIterable from execute to emit progress updates before the final result.
New Features
Migration
Written for commit 0319ac7. Summary will update on new commits.
Summary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.