From 26ed49ccb3bccb7ae1d39fc31af5c408c2852425 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Tue, 18 Nov 2025 16:22:56 -0800 Subject: [PATCH 1/2] [ai] Make `writable` property be required in `DurableAgent#stream()` --- .changeset/itchy-memes-attend.md | 5 +++++ .../workflow-ai/durable-agent.mdx | 18 ++++++++++++++++-- packages/ai/src/agent/durable-agent.ts | 12 +++++------- 3 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 .changeset/itchy-memes-attend.md diff --git a/.changeset/itchy-memes-attend.md b/.changeset/itchy-memes-attend.md new file mode 100644 index 000000000..8bd748d8d --- /dev/null +++ b/.changeset/itchy-memes-attend.md @@ -0,0 +1,5 @@ +--- +"@workflow/ai": patch +--- + +Make `writable` property be required in `DurableAgent#stream()` diff --git a/docs/content/docs/api-reference/workflow-ai/durable-agent.mdx b/docs/content/docs/api-reference/workflow-ai/durable-agent.mdx index 2fc4812c8..21f0f402f 100644 --- a/docs/content/docs/api-reference/workflow-ai/durable-agent.mdx +++ b/docs/content/docs/api-reference/workflow-ai/durable-agent.mdx @@ -15,7 +15,9 @@ Tool calls can be implemented as workflow steps for automatic retries, or as reg ```typescript lineNumbers import { DurableAgent } from '@workflow/ai/agent'; +import { getWritable } from 'workflow'; import { z } from 'zod'; +import type { UIMessageChunk } from 'ai'; async function getWeather({ city }: { city: string }) { "use step"; @@ -38,8 +40,13 @@ async function myAgent() { }, }); + // The agent will stream its output to the workflow + // run's default output stream + const writable = getWritable(); + await agent.stream({ messages: [{ role: 'user', content: 'How is the weather in San Francisco?' }], + writable, }); } ``` @@ -88,7 +95,6 @@ export default DurableAgentStreamOptions;` - Tools can be implemented as workflow steps (using `"use step"` for automatic retries), or as regular workflow-level logic - Tools can use core library features like `sleep()` and Hooks within their `execute` functions - The agent processes tool calls iteratively until completion -- When no custom `writable` stream is provided, the agent uses the workflow's default writable stream ## Examples @@ -96,7 +102,9 @@ export default DurableAgentStreamOptions;` ```typescript import { DurableAgent } from '@workflow/ai/agent'; +import { getWritable } from 'workflow'; import { z } from 'zod'; +import type { UIMessageChunk } from 'ai'; async function getWeather({ location }: { location: string }) { "use step"; @@ -127,6 +135,7 @@ async function weatherAgentWorkflow(userQuery: string) { content: userQuery, }, ], + writable: getWritable(), }); } ``` @@ -135,7 +144,9 @@ async function weatherAgentWorkflow(userQuery: string) { ```typescript import { DurableAgent } from '@workflow/ai/agent'; +import { getWritable } from 'workflow'; import { z } from 'zod'; +import type { UIMessageChunk } from 'ai'; async function getWeather({ location }: { location: string }) { "use step"; @@ -173,6 +184,7 @@ async function multiToolAgentWorkflow(userQuery: string) { content: userQuery, }, ], + writable: getWritable(), }); } ``` @@ -181,8 +193,9 @@ async function multiToolAgentWorkflow(userQuery: string) { ```typescript import { DurableAgent } from '@workflow/ai/agent'; -import { sleep, defineHook } from 'workflow'; +import { sleep, defineHook, getWritable } from 'workflow'; import { z } from 'zod'; +import type { UIMessageChunk } from 'ai'; // Define a reusable hook type const approvalHook = defineHook<{ approved: boolean; reason: string }>(); @@ -238,6 +251,7 @@ async function agentWithLibraryFeaturesWorkflow(userRequest: string) { await agent.stream({ messages: [{ role: 'user', content: userRequest }], + writable: getWritable(), }); } ``` diff --git a/packages/ai/src/agent/durable-agent.ts b/packages/ai/src/agent/durable-agent.ts index 871db9b51..af08eee56 100644 --- a/packages/ai/src/agent/durable-agent.ts +++ b/packages/ai/src/agent/durable-agent.ts @@ -11,7 +11,6 @@ import { type UIMessageChunk, } from 'ai'; import { convertToLanguageModelPrompt, standardizePrompt } from 'ai/internal'; -import { getWritable } from 'workflow'; import { streamTextIterator } from './stream-text-iterator.js'; /** @@ -54,9 +53,9 @@ export interface DurableAgentStreamOptions { system?: string; /** - * Optional custom writable stream for handling message chunks. If not provided, a default writable stream will be created using getWritable(). + * The writable stream for handling message chunks. Use `getWritable()` to get the workflow's default output stream. */ - writable?: WritableStream; + writable: WritableStream; /** * If true, prevents the writable stream from being closed after streaming completes. @@ -97,6 +96,7 @@ export interface DurableAgentStreamOptions { * * await agent.stream({ * messages: [{ role: 'user', content: 'What is the weather?' }], + * writable: getWritable(), * }); * ``` */ @@ -127,12 +127,10 @@ export class DurableAgent { download: undefined, }); - const writable = options.writable || getWritable(); - const iterator = streamTextIterator({ model: this.model, tools: this.tools, - writable, + writable: options.writable, prompt: modelPrompt, stopConditions: options.stopWhen, }); @@ -150,7 +148,7 @@ export class DurableAgent { } if (!options.preventClose) { - await closeStream(writable); + await closeStream(options.writable); } } } From aa318ce8cfefa68a431faea821939b9960749828 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Thu, 20 Nov 2025 00:18:16 -0800 Subject: [PATCH 2/2] Update packages/ai/src/agent/durable-agent.ts Co-authored-by: Pranay Prakash --- packages/ai/src/agent/durable-agent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ai/src/agent/durable-agent.ts b/packages/ai/src/agent/durable-agent.ts index af08eee56..3ea32bdb8 100644 --- a/packages/ai/src/agent/durable-agent.ts +++ b/packages/ai/src/agent/durable-agent.ts @@ -53,7 +53,7 @@ export interface DurableAgentStreamOptions { system?: string; /** - * The writable stream for handling message chunks. Use `getWritable()` to get the workflow's default output stream. + * The stream to which the agent writes message chunks. For example, use `getWritable()` to write to the workflow's default output stream. */ writable: WritableStream;