feat(agents): add stream response conversion methods#21
Conversation
Add `toTextStreamResponse()` and `toUIMessageStreamResponse()` to `StreamResult`, delegating to the underlying AI SDK `streamText` result. This enables direct HTTP response conversion for API frameworks like Hono, Express, and Bun. Flow agents throw a descriptive error since they build streams from step execution rather than a single `streamText` call. Re-exports `UIMessage` and `UIMessageStreamOptions` from `ai` for consumer type access. Co-Authored-By: Claude <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: e997e1c 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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughAdds two response helper methods, toTextStreamResponse() and toUIMessageStreamResponse(), to StreamResult. Regular agents delegate to the AI SDK; Flow agents use a new buildStreamResponseMethods factory to convert ReadableStream into HTTP Responses. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant FlowAgent
participant StreamParts as ReadableStream<StreamPart>
participant Transformer as TransformStream/TextEncoder
participant UIStream as createUIMessageStream
participant HTTP as Response
Client->>FlowAgent: call agent.stream()
FlowAgent->>StreamParts: provide readable (via getStream)
Client->>FlowAgent: call toTextStreamResponse()/toUIMessageStreamResponse()
FlowAgent->>StreamParts: obtain stream (lazy)
alt toTextStreamResponse
StreamParts->>Transformer: filter text-delta events
Transformer->>HTTP: produce text/plain Response body
else toUIMessageStreamResponse
StreamParts->>UIStream: create UI message stream
UIStream->>HTTP: produce SSE-like Response body
end
FlowAgent->>Client: return Response
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 📝 Coding Plan
Comment |
Replace error-throwing stubs with working implementations using a shared `buildStreamResponseMethods` utility. Flow agents now build `toTextStreamResponse` by filtering text-delta events from the stream and `toUIMessageStreamResponse` via the AI SDK's `createUIMessageStream` and `createUIMessageStreamResponse` utilities. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/stream-response-methods.md:
- Line 5: The changeset and docs are incorrect: update the changeset text to
state that StreamResult exposes toTextStreamResponse() and
toUIMessageStreamResponse() (delegating to the underlying Vercel AI SDK
streamText result) and that flow agents also support these methods (remove the
claim about throwing an error); then update the streaming documentation to add
entries for StreamResult.toTextStreamResponse,
StreamResult.toUIMessageStreamResponse and the re-exported
UIMessageStreamOptions type, showing usage examples for base agents vs flow
agents and clarifying any behavioral differences; ensure the public API
re-export of UIMessageStreamOptions is documented along with the method
signatures.
In `@packages/agents/src/core/agents/base/agent.ts`:
- Around line 539-540: The helpers toTextStreamResponse and
toUIMessageStreamResponse currently delegate to aiResult which consumes
aiResult.fullStream that's already being read by the background consumer,
causing double-consumption errors; fix by tee()-ing fullStream before any
consumption: call aiResult.fullStream.tee() and use one branch for the
background task (replace the background consumer to read that branch) and return
the other branch (or create new tees per helper call) from
toTextStreamResponse/toUIMessageStreamResponse so each consumer gets its own
readable stream branch; alternatively remove the delegations and document that
callers must obtain their own tee branch from aiResult.fullStream.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: f1cdaa35-9220-475b-82c0-3a1d037273eb
📒 Files selected for processing (6)
.changeset/stream-response-methods.mdpackages/agents/src/core/agents/base/agent.test.tspackages/agents/src/core/agents/base/agent.tspackages/agents/src/core/agents/base/types.tspackages/agents/src/core/agents/flow/flow-agent.tspackages/agents/src/index.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/agents/src/core/agents/base/stream-response.ts`:
- Around line 21-34: The JSDoc for the exported function
buildStreamResponseMethods is missing an `@example`; update the block above
buildStreamResponseMethods to include a concise `@example` that demonstrates
calling the thunk getStream and consuming one of the returned methods (e.g.,
await response.toTextStreamResponse() or await
response.toUIMessageStreamResponse()), showing that only one of fullStream,
toTextStreamResponse, or toUIMessageStreamResponse should be consumed; reference
the symbols getStream, fullStream, toTextStreamResponse, and
toUIMessageStreamResponse in the example so the usage is clear.
- Around line 51-56: toTextStreamResponse currently spreads init?.headers into
the Response headers which fails when headers is a Headers instance or tuple
array; update to normalize and merge via the Headers API instead: create a
Headers object from init?.headers (e.g. new Headers(init?.headers)), set or
overwrite the "Content-Type" header to "text/plain; charset=utf-8" on that
Headers instance, then pass that Headers instance as the Response headers when
constructing the new Response(textStream, { ...init, headers }). Ensure you
reference the toTextStreamResponse function, the textStream and init variables,
and replace the object-spread merge with this Headers-based merge to preserve
all header types.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c1eda86b-0c33-4f73-9b77-a78d68ad409d
📒 Files selected for processing (2)
packages/agents/src/core/agents/base/stream-response.tspackages/agents/src/core/agents/flow/flow-agent.ts
…ests Move `buildStreamResponseMethods` from base to flow directory since only flow agents use it (base agents delegate to aiResult directly). Add unit tests for the utility (stream-response.test.ts) covering text stream filtering, content-type headers, custom init, and UI message stream response creation. Add integration tests in flow-agent.test.ts verifying both response methods work end-to-end through the flow agent's stream() path. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/agents/src/core/agents/flow/stream-response.ts`:
- Around line 67-73: The identity TransformStream wrapping `source` (created as
`uiStream` via new TransformStream<StreamPart>({...})`) is unnecessary—remove
the `uiStream` variable and the TransformStream creation and pass `source`
directly into `writer.merge(...)` (or wherever `uiStream` is consumed). Update
any references from `uiStream` to `source` and delete the redundant
TransformStream/transform function to avoid the extra passthrough overhead.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: e36cca5f-4e78-4956-9b56-b3b2b5d56a77
📒 Files selected for processing (4)
packages/agents/src/core/agents/flow/flow-agent.test.tspackages/agents/src/core/agents/flow/flow-agent.tspackages/agents/src/core/agents/flow/stream-response.test.tspackages/agents/src/core/agents/flow/stream-response.ts
Co-Authored-By: Claude <noreply@anthropic.com>
- Add comment explaining AI SDK internal tee() safety for base agent response delegation (thread PRRT_kwDORkqTG851Brsl) - Add @example to buildStreamResponseMethods JSDoc (thread PRRT_kwDORkqTG851BuQt) - Fix unsafe header merge using Headers API instead of object spread, respect caller-provided Content-Type (thread PRRT_kwDORkqTG851BuQv) - Remove unnecessary identity TransformStream in toUIMessageStreamResponse, pass source directly to writer.merge (thread PRRT_kwDORkqTG851BwbT) - Add tests for Headers instance input and Content-Type preservation Co-Authored-By: Claude <noreply@anthropic.com>
Summary
toTextStreamResponse()andtoUIMessageStreamResponse()toStreamResult, delegating to the underlying AI SDKstreamTextresult for direct HTTP response conversion (Hono, Express, Bun)UIMessageandUIMessageStreamOptionsfromaifor consumer type accessTest plan
pnpm typecheck --filter=@funkai/agents)pnpm build --filter=@funkai/agents)pnpm test --filter=@funkai/agents)