workflow 5.0.0-beta.2 → 5.0.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/dist/api-workflow.d.ts +1 -1
  2. package/dist/api-workflow.d.ts.map +1 -1
  3. package/dist/api-workflow.js +2 -2
  4. package/dist/api.d.ts +5 -1
  5. package/dist/api.d.ts.map +1 -1
  6. package/dist/api.js +14 -2
  7. package/dist/internal/builtins.d.ts +17 -0
  8. package/dist/internal/builtins.d.ts.map +1 -1
  9. package/dist/internal/builtins.js +65 -1
  10. package/dist/observability.d.ts +1 -1
  11. package/dist/observability.js +2 -2
  12. package/dist/runtime.d.ts +1 -1
  13. package/dist/runtime.d.ts.map +1 -1
  14. package/dist/runtime.js +2 -2
  15. package/docs/ai/index.mdx +27 -23
  16. package/docs/api-reference/index.mdx +24 -0
  17. package/docs/api-reference/meta.json +8 -0
  18. package/docs/api-reference/vitest/index.mdx +28 -7
  19. package/docs/api-reference/workflow/create-hook.mdx +38 -0
  20. package/docs/api-reference/workflow/create-webhook.mdx +1 -0
  21. package/docs/api-reference/workflow/experimental-set-attributes.mdx +65 -0
  22. package/docs/api-reference/workflow/fetch.mdx +5 -0
  23. package/docs/api-reference/workflow/index.mdx +3 -0
  24. package/docs/api-reference/workflow-ai/durable-agent.mdx +7 -45
  25. package/docs/api-reference/workflow-api/get-hook-by-token.mdx +7 -0
  26. package/docs/api-reference/workflow-api/get-run.mdx +6 -0
  27. package/docs/api-reference/workflow-api/index.mdx +6 -8
  28. package/docs/api-reference/workflow-api/resume-hook.mdx +57 -0
  29. package/docs/api-reference/workflow-api/start.mdx +13 -5
  30. package/docs/api-reference/workflow-astro/index.mdx +18 -0
  31. package/docs/api-reference/workflow-astro/meta.json +4 -0
  32. package/docs/api-reference/workflow-astro/workflow.mdx +45 -0
  33. package/docs/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
  34. package/docs/api-reference/workflow-errors/index.mdx +85 -0
  35. package/docs/api-reference/workflow-errors/meta.json +5 -0
  36. package/docs/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
  37. package/docs/api-reference/workflow-errors/workflow-error.mdx +52 -0
  38. package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +16 -6
  39. package/docs/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
  40. package/docs/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
  41. package/docs/api-reference/workflow-nest/configure-workflow-controller.mdx +33 -0
  42. package/docs/api-reference/workflow-nest/index.mdx +31 -0
  43. package/docs/api-reference/workflow-nest/meta.json +9 -0
  44. package/docs/api-reference/workflow-nest/nest-local-builder.mdx +64 -0
  45. package/docs/api-reference/workflow-nest/workflow-controller.mdx +40 -0
  46. package/docs/api-reference/workflow-nest/workflow-module.mdx +74 -0
  47. package/docs/api-reference/workflow-next/with-workflow.mdx +34 -2
  48. package/docs/api-reference/workflow-nitro/index.mdx +59 -0
  49. package/docs/api-reference/workflow-nuxt/index.mdx +47 -0
  50. package/docs/api-reference/workflow-observability/hydrate-data.mdx +35 -0
  51. package/docs/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
  52. package/docs/api-reference/workflow-observability/index.mdx +64 -0
  53. package/docs/api-reference/workflow-observability/meta.json +11 -0
  54. package/docs/api-reference/workflow-observability/observability-revivers.mdx +50 -0
  55. package/docs/api-reference/workflow-observability/parse-class-name.mdx +41 -0
  56. package/docs/api-reference/workflow-observability/parse-step-name.mdx +40 -0
  57. package/docs/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
  58. package/docs/api-reference/workflow-runtime/create-world.mdx +39 -0
  59. package/docs/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
  60. package/docs/api-reference/{workflow-api → workflow-runtime}/get-world.mdx +7 -10
  61. package/docs/api-reference/workflow-runtime/health-check.mdx +50 -0
  62. package/docs/api-reference/workflow-runtime/index.mdx +43 -0
  63. package/docs/api-reference/workflow-runtime/meta.json +12 -0
  64. package/docs/api-reference/workflow-runtime/set-world.mdx +49 -0
  65. package/docs/api-reference/workflow-runtime/workflow-entrypoint.mdx +42 -0
  66. package/docs/api-reference/{workflow-api → workflow-runtime}/world/index.mdx +5 -8
  67. package/docs/api-reference/workflow-runtime/world/meta.json +4 -0
  68. package/docs/api-reference/{workflow-api → workflow-runtime}/world/queue.mdx +2 -2
  69. package/docs/api-reference/{workflow-api → workflow-runtime}/world/storage.mdx +11 -4
  70. package/docs/api-reference/{workflow-api → workflow-runtime}/world/streams.mdx +2 -2
  71. package/docs/api-reference/workflow-serde/index.mdx +0 -1
  72. package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +1 -2
  73. package/docs/api-reference/workflow-serde/workflow-serialize.mdx +1 -2
  74. package/docs/api-reference/workflow-sveltekit/index.mdx +18 -0
  75. package/docs/api-reference/workflow-sveltekit/meta.json +4 -0
  76. package/docs/api-reference/workflow-sveltekit/workflow-plugin.mdx +42 -0
  77. package/docs/api-reference/workflow-vite/index.mdx +18 -0
  78. package/docs/api-reference/workflow-vite/meta.json +4 -0
  79. package/docs/api-reference/workflow-vite/workflow.mdx +48 -0
  80. package/docs/changelog/attributes-mvp.mdx +380 -0
  81. package/docs/changelog/eager-processing.mdx +269 -0
  82. package/docs/changelog/index.mdx +2 -1
  83. package/docs/changelog/lazy-event-creation.md +127 -0
  84. package/docs/changelog/meta.json +6 -1
  85. package/docs/changelog/resilient-start.mdx +31 -283
  86. package/docs/cookbook/advanced/child-workflows.mdx +315 -0
  87. package/docs/cookbook/advanced/meta.json +2 -3
  88. package/docs/cookbook/advanced/publishing-libraries.mdx +87 -29
  89. package/docs/cookbook/advanced/serializable-steps.mdx +17 -5
  90. package/docs/cookbook/advanced/upgrading-workflows.mdx +195 -0
  91. package/docs/cookbook/agent-patterns/agent-cancellation.mdx +156 -0
  92. package/docs/cookbook/agent-patterns/durable-agent.mdx +11 -184
  93. package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +150 -173
  94. package/docs/cookbook/agent-patterns/meta.json +1 -7
  95. package/docs/cookbook/common-patterns/batching.mdx +44 -118
  96. package/docs/cookbook/common-patterns/idempotency.mdx +36 -52
  97. package/docs/cookbook/common-patterns/meta.json +4 -4
  98. package/docs/cookbook/common-patterns/rate-limiting.mdx +1 -1
  99. package/docs/cookbook/common-patterns/saga.mdx +128 -33
  100. package/docs/cookbook/common-patterns/scheduling.mdx +77 -193
  101. package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +155 -0
  102. package/docs/cookbook/common-patterns/timeouts.mdx +100 -0
  103. package/docs/cookbook/common-patterns/workflow-composition.mdx +117 -0
  104. package/docs/cookbook/index.mdx +14 -17
  105. package/docs/cookbook/integrations/ai-sdk.mdx +330 -142
  106. package/docs/cookbook/integrations/chat-sdk.mdx +264 -151
  107. package/docs/cookbook/integrations/sandbox.mdx +482 -81
  108. package/docs/cookbook/meta.json +1 -1
  109. package/docs/deploying/building-a-world.mdx +1 -1
  110. package/docs/deploying/world/postgres-world.mdx +5 -3
  111. package/docs/deploying/world/vercel-world.mdx +2 -0
  112. package/docs/errors/abort-signal-timeout-in-workflow.mdx +80 -0
  113. package/docs/errors/corrupted-event-log.mdx +5 -5
  114. package/docs/errors/hook-conflict.mdx +56 -4
  115. package/docs/errors/index.mdx +9 -0
  116. package/docs/errors/replay-divergence.mdx +27 -0
  117. package/docs/errors/runtime-decryption-failed.mdx +77 -0
  118. package/docs/errors/step-executed-multiple-times.mdx +23 -0
  119. package/docs/errors/step-not-registered.mdx +1 -1
  120. package/docs/foundations/cancellation.mdx +459 -0
  121. package/docs/foundations/errors-and-retries.mdx +7 -3
  122. package/docs/foundations/hooks.mdx +29 -0
  123. package/docs/foundations/idempotency.mdx +236 -11
  124. package/docs/foundations/index.mdx +3 -3
  125. package/docs/foundations/meta.json +3 -2
  126. package/docs/foundations/serialization.mdx +78 -42
  127. package/docs/foundations/starting-workflows.mdx +6 -2
  128. package/docs/foundations/streaming.mdx +14 -23
  129. package/docs/foundations/versioning.mdx +263 -0
  130. package/docs/getting-started/astro.mdx +6 -0
  131. package/docs/getting-started/index.mdx +6 -7
  132. package/docs/getting-started/meta.json +1 -0
  133. package/docs/getting-started/nestjs.mdx +9 -0
  134. package/docs/getting-started/next.mdx +5 -3
  135. package/docs/getting-started/nitro.mdx +22 -0
  136. package/docs/getting-started/sveltekit.mdx +6 -0
  137. package/docs/getting-started/tanstack-start.mdx +241 -0
  138. package/docs/how-it-works/cancellation.mdx +287 -0
  139. package/docs/how-it-works/code-transform.mdx +2 -2
  140. package/docs/how-it-works/encryption.mdx +2 -2
  141. package/docs/how-it-works/event-sourcing.mdx +2 -2
  142. package/docs/how-it-works/meta.json +2 -1
  143. package/docs/internal/index.mdx +21 -0
  144. package/docs/internal/meta.json +10 -0
  145. package/docs/internal/nitro-native-build.mdx +38 -0
  146. package/docs/internal/nitro-web-ui.mdx +24 -0
  147. package/docs/internal/serializable-abort-controller.mdx +148 -0
  148. package/docs/migration-guides/migrating-from-aws-step-functions.mdx +63 -16
  149. package/docs/migration-guides/migrating-from-inngest.mdx +44 -22
  150. package/docs/migration-guides/migrating-from-temporal.mdx +43 -14
  151. package/docs/migration-guides/migrating-from-trigger-dev.mdx +59 -27
  152. package/docs/observability/attributes.mdx +87 -0
  153. package/docs/observability/index.mdx +25 -1
  154. package/docs/observability/meta.json +1 -1
  155. package/docs/observability/tracing.mdx +106 -0
  156. package/docs/testing/index.mdx +2 -2
  157. package/package.json +14 -13
  158. package/docs/api-reference/workflow-api/world/meta.json +0 -4
  159. package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
  160. package/docs/cookbook/advanced/custom-serialization.mdx +0 -168
  161. package/docs/cookbook/advanced/durable-objects.mdx +0 -148
  162. package/docs/cookbook/advanced/isomorphic-packages.mdx +0 -145
  163. package/docs/cookbook/agent-patterns/stop-workflow.mdx +0 -216
  164. package/docs/cookbook/agent-patterns/tool-orchestration.mdx +0 -255
  165. package/docs/cookbook/agent-patterns/tool-streaming.mdx +0 -181
  166. package/docs/cookbook/common-patterns/child-workflows.mdx +0 -372
  167. package/docs/cookbook/common-patterns/content-router.mdx +0 -207
  168. package/docs/cookbook/common-patterns/fan-out.mdx +0 -208
  169. package/docs/foundations/common-patterns.mdx +0 -265
@@ -1,216 +0,0 @@
1
- ---
2
- title: Stop Workflow
3
- description: Gracefully cancel a running agent workflow using a hook signal.
4
- type: guide
5
- summary: Use a hook as a stop signal to break out of an agent loop and close the stream cleanly.
6
- ---
7
-
8
- Use this pattern when you need to gracefully stop a running agent from the outside — for example, a "Stop" button in a chat UI or an admin cancellation endpoint. The workflow listens for a stop signal via a hook while the agent runs, and breaks out of the loop when the signal arrives.
9
-
10
- ## Pattern
11
-
12
- Create a hook with a known token (the run ID). Listen for a stop signal in a non-blocking `.then()`. In the `prepareStep` callback, check the flag and return `{ toolChoice: "none" }` to prevent further tool calls, causing the agent to generate a final response and exit the loop cleanly.
13
-
14
- ### Simplified
15
-
16
- ```typescript lineNumbers
17
- import { DurableAgent } from "@workflow/ai/agent";
18
- import { defineHook, getWritable, getWorkflowMetadata } from "workflow";
19
- import { z } from "zod";
20
- import type { ModelMessage, UIMessageChunk } from "ai";
21
-
22
- export const stopHook = defineHook({
23
- schema: z.object({ reason: z.string().optional() }),
24
- });
25
-
26
- declare function processQuery(args: { query: string }): Promise<string>; // @setup
27
-
28
- export async function stoppableAgent(messages: ModelMessage[]) {
29
- "use workflow";
30
-
31
- const { workflowRunId } = getWorkflowMetadata();
32
- let stopRequested = false;
33
-
34
- const hook = stopHook.create({ token: `stop:${workflowRunId}` }); // [!code highlight]
35
- hook.then(() => { stopRequested = true; }); // [!code highlight]
36
-
37
- const agent = new DurableAgent({
38
- model: "anthropic/claude-haiku-4.5",
39
- tools: {
40
- processQuery: {
41
- description: "Process a query",
42
- inputSchema: z.object({ query: z.string() }),
43
- execute: processQuery,
44
- },
45
- },
46
- });
47
-
48
- const result = await agent.stream({
49
- messages,
50
- writable: getWritable<UIMessageChunk>(),
51
- prepareStep: () => { // [!code highlight]
52
- if (stopRequested) return { toolChoice: "none" }; // [!code highlight]
53
- return {};
54
- },
55
- });
56
-
57
- return { messages: result.messages, stopped: stopRequested };
58
- }
59
- ```
60
-
61
- ### Full Implementation
62
-
63
- ```typescript lineNumbers
64
- import { DurableAgent } from "@workflow/ai/agent";
65
- import { defineHook, getWritable, getWorkflowMetadata } from "workflow";
66
- import { z } from "zod";
67
- import type { ModelMessage, UIMessageChunk } from "ai";
68
-
69
- // Hook to signal the workflow to stop
70
- export const stopHook = defineHook({
71
- schema: z.object({
72
- reason: z.string().optional(),
73
- }),
74
- });
75
-
76
- // Step: Search the web
77
- async function searchWeb({ query }: { query: string }) {
78
- "use step";
79
-
80
- await new Promise((resolve) => setTimeout(resolve, 1000));
81
- return { results: [`Result for "${query}"`] };
82
- }
83
-
84
- // Step: Analyze data
85
- async function analyzeData({ data }: { data: string }) {
86
- "use step";
87
-
88
- await new Promise((resolve) => setTimeout(resolve, 800));
89
- return { analysis: `Analysis of: ${data}` };
90
- }
91
-
92
- // Step: Write the final close marker to the stream
93
- async function closeStream() {
94
- "use step";
95
-
96
- const writable = getWritable<UIMessageChunk>();
97
- const writer = writable.getWriter();
98
- try {
99
- await writer.write({ type: "finish" } as UIMessageChunk);
100
- } finally {
101
- writer.releaseLock();
102
- }
103
- await writable.close();
104
- }
105
-
106
- export async function stoppableAgent(messages: ModelMessage[]) {
107
- "use workflow";
108
-
109
- const { workflowRunId } = getWorkflowMetadata();
110
- const writable = getWritable<UIMessageChunk>();
111
-
112
- // Listen for stop signal using a non-blocking hook
113
- let stopRequested = false;
114
- let stopReason: string | undefined;
115
-
116
- const hook = stopHook.create({ token: `stop:${workflowRunId}` }); // [!code highlight]
117
- hook.then(({ reason }) => { // [!code highlight]
118
- stopRequested = true;
119
- stopReason = reason;
120
- });
121
-
122
- const agent = new DurableAgent({
123
- model: "anthropic/claude-haiku-4.5",
124
- instructions: "You are a research assistant. Search and analyze data as needed.",
125
- tools: {
126
- searchWeb: {
127
- description: "Search the web for information",
128
- inputSchema: z.object({ query: z.string() }),
129
- execute: searchWeb,
130
- },
131
- analyzeData: {
132
- description: "Analyze a piece of data",
133
- inputSchema: z.object({ data: z.string() }),
134
- execute: analyzeData,
135
- },
136
- },
137
- });
138
-
139
- const result = await agent.stream({
140
- messages,
141
- writable,
142
- preventClose: true,
143
- maxSteps: 20,
144
- prepareStep: ({ stepNumber }) => { // [!code highlight]
145
- // Check stop flag before each agent step.
146
- // Setting toolChoice to "none" prevents tool calls,
147
- // causing the agent to generate a final response and exit.
148
- if (stopRequested) {
149
- return { toolChoice: "none" }; // [!code highlight]
150
- }
151
- return {};
152
- },
153
- });
154
-
155
- // Clean up: close the stream
156
- await closeStream();
157
-
158
- return {
159
- messages: result.messages,
160
- stopped: stopRequested,
161
- stopReason,
162
- stepsCompleted: result.steps.length,
163
- };
164
- }
165
- ```
166
-
167
- ### API Route to Trigger Stop
168
-
169
- ```typescript lineNumbers
170
- import { stopHook } from "@/workflows/stoppable-agent";
171
-
172
- export async function POST(
173
- request: Request,
174
- { params }: { params: Promise<{ runId: string }> }
175
- ) {
176
- const { runId } = await params;
177
- const { reason } = await request.json();
178
-
179
- await stopHook.resume(`stop:${runId}`, { // [!code highlight]
180
- reason: reason || "User requested stop",
181
- });
182
-
183
- return Response.json({ success: true });
184
- }
185
- ```
186
-
187
- ### Client Stop Button
188
-
189
- ```tsx lineNumbers
190
- "use client";
191
-
192
- export function StopButton({ runId }: { runId: string }) {
193
- const handleStop = async () => {
194
- await fetch(`/api/chat/${runId}/stop`, {
195
- method: "POST",
196
- headers: { "Content-Type": "application/json" },
197
- body: JSON.stringify({ reason: "User clicked stop" }),
198
- });
199
- };
200
-
201
- return (
202
- <button type="button" onClick={handleStop}>
203
- Stop Agent
204
- </button>
205
- );
206
- }
207
- ```
208
-
209
- ## Key APIs
210
-
211
- - [`"use workflow"`](/docs/api-reference/workflow/use-workflow) — declares the orchestrator function
212
- - [`"use step"`](/docs/api-reference/workflow/use-step) — declares step functions with retries
213
- - [`defineHook()`](/docs/api-reference/workflow/define-hook) — type-safe hook for the stop signal
214
- - [`getWorkflowMetadata()`](/docs/api-reference/workflow/get-workflow-metadata) — access the run ID for deterministic hook tokens
215
- - [`getWritable()`](/docs/api-reference/workflow/get-writable) — stream output and close cleanly on stop
216
- - [`DurableAgent`](/docs/api-reference/workflow-ai/durable-agent) — `prepareStep` callback to check stop flag before each step
@@ -1,255 +0,0 @@
1
- ---
2
- title: Tool Orchestration
3
- description: Choose between step-level and workflow-level tools, or combine both for complex tool implementations.
4
- type: guide
5
- summary: Implement tools as steps for retries and I/O, at the workflow level for sleep and hooks, or combine both.
6
- ---
7
-
8
- Use this pattern to understand when to implement a tool as a step, at the workflow level, or as a combination. The choice depends on whether the tool needs Node.js I/O (step), workflow primitives like `sleep()` and hooks (workflow level), or both.
9
-
10
- ## Pattern
11
-
12
- Tools marked with `"use step"` get automatic retries and full Node.js access but cannot use `sleep()` or hooks. Tools without `"use step"` run in the workflow context and can use workflow primitives but cannot perform side effects directly. Combine both by having a workflow-level tool call into steps for I/O.
13
-
14
- ### Step-Level vs Workflow-Level
15
-
16
- | Capability | Step (`"use step"`) | Workflow Level |
17
- |------------|---------------------|----------------|
18
- | `getWritable()` | Yes | Yes |
19
- | Automatic retries | Yes | No |
20
- | Side effects (fetch, DB) | Yes | No |
21
- | `sleep()` | No | Yes |
22
- | `createHook()` / `createWebhook()` | No | Yes |
23
-
24
- ### Simplified
25
-
26
- ```typescript lineNumbers
27
- import { DurableAgent } from "@workflow/ai/agent";
28
- import { sleep, getWritable } from "workflow";
29
- import { z } from "zod";
30
- import type { UIMessageChunk } from "ai";
31
-
32
- // Step-level tool: I/O with retries
33
- async function fetchWeather({ city }: { city: string }) {
34
- "use step";
35
- const res = await fetch(`https://api.weather.com?city=${city}`);
36
- return res.json();
37
- }
38
-
39
- // Workflow-level tool: uses sleep()
40
- async function scheduleReminder({ delayMs }: { delayMs: number }) {
41
- // No "use step" — sleep() requires workflow context
42
- await sleep(delayMs); // [!code highlight]
43
- return { message: `Reminder fired after ${delayMs}ms` };
44
- }
45
-
46
- // Combined: workflow-level orchestration calling into steps
47
- async function fetchWithDelay({ url, delayMs }: { url: string; delayMs: number }) {
48
- const result = await doFetch(url); // Step handles I/O // [!code highlight]
49
- await sleep(delayMs); // Workflow handles sleep // [!code highlight]
50
- return result;
51
- }
52
-
53
- async function doFetch(url: string) {
54
- "use step";
55
- const res = await fetch(url);
56
- return res.json();
57
- }
58
-
59
- export async function assistantAgent(userMessage: string) {
60
- "use workflow";
61
-
62
- const agent = new DurableAgent({
63
- model: "anthropic/claude-haiku-4.5",
64
- tools: {
65
- fetchWeather: {
66
- description: "Get weather for a city",
67
- inputSchema: z.object({ city: z.string() }),
68
- execute: fetchWeather,
69
- },
70
- scheduleReminder: {
71
- description: "Set a reminder after a delay",
72
- inputSchema: z.object({ delayMs: z.number() }),
73
- execute: scheduleReminder,
74
- },
75
- fetchWithDelay: {
76
- description: "Fetch a URL then wait before returning",
77
- inputSchema: z.object({ url: z.string(), delayMs: z.number() }),
78
- execute: fetchWithDelay,
79
- },
80
- },
81
- });
82
-
83
- await agent.stream({ // [!code highlight]
84
- messages: [{ role: "user", content: userMessage }],
85
- writable: getWritable<UIMessageChunk>(),
86
- });
87
- }
88
- ```
89
-
90
- ### Full Implementation
91
-
92
- ```typescript lineNumbers
93
- import { DurableAgent } from "@workflow/ai/agent";
94
- import { sleep, createWebhook, getWritable } from "workflow";
95
- import { z } from "zod";
96
- import type { UIMessageChunk } from "ai";
97
-
98
- // --- Step-level tools: I/O with retries ---
99
-
100
- async function searchDatabase({ query }: { query: string }) {
101
- "use step";
102
-
103
- const response = await fetch(`https://api.example.com/search?q=${query}`);
104
- if (!response.ok) throw new Error(`Search failed: ${response.status}`);
105
- return response.json();
106
- }
107
-
108
- async function sendNotification({
109
- userId,
110
- message,
111
- }: {
112
- userId: string;
113
- message: string;
114
- }) {
115
- "use step";
116
-
117
- await fetch("https://api.example.com/notifications", {
118
- method: "POST",
119
- headers: { "Content-Type": "application/json" },
120
- body: JSON.stringify({ userId, message }),
121
- });
122
- return { sent: true };
123
- }
124
-
125
- // --- Workflow-level tool: uses sleep ---
126
-
127
- async function waitThenCheck({
128
- delayMs,
129
- endpoint,
130
- }: {
131
- delayMs: number;
132
- endpoint: string;
133
- }) {
134
- // No "use step" — workflow context needed for sleep()
135
- await sleep(delayMs); // [!code highlight]
136
- // Delegate I/O to a step
137
- return pollEndpoint(endpoint);
138
- }
139
-
140
- async function pollEndpoint(endpoint: string) {
141
- "use step";
142
- const res = await fetch(endpoint);
143
- return res.json();
144
- }
145
-
146
- // --- Workflow-level tool: uses webhook ---
147
-
148
- async function waitForCallback({ description }: { description: string }) {
149
- // No "use step" — webhooks are workflow primitives
150
- const webhook = createWebhook(); // [!code highlight]
151
- // Log the URL so external systems can call it
152
- console.log(`Waiting for callback at: ${webhook.url}`);
153
-
154
- const result = await Promise.race([ // [!code highlight]
155
- webhook.then((req) => req.json()),
156
- sleep("1h").then(() => ({ status: "timeout" })),
157
- ]);
158
-
159
- return result;
160
- }
161
-
162
- // --- Combined tool: step I/O + workflow sleep + step I/O ---
163
-
164
- async function retryWithCooldown({
165
- url,
166
- maxAttempts,
167
- }: {
168
- url: string;
169
- maxAttempts: number;
170
- }) {
171
- for (let i = 0; i < maxAttempts; i++) {
172
- const result = await attemptFetch(url);
173
- if (result.success) return result;
174
- if (i < maxAttempts - 1) {
175
- await sleep(`${(i + 1) * 5}s`); // Increasing cooldown between attempts // [!code highlight]
176
- }
177
- }
178
- return { success: false, error: "All attempts failed" };
179
- }
180
-
181
- async function attemptFetch(url: string) {
182
- "use step";
183
- try {
184
- const res = await fetch(url);
185
- if (!res.ok) return { success: false, status: res.status };
186
- return { success: true, data: await res.json() };
187
- } catch {
188
- return { success: false, error: "Network error" };
189
- }
190
- }
191
-
192
- export async function orchestrationAgent(userMessage: string) {
193
- "use workflow";
194
-
195
- const writable = getWritable<UIMessageChunk>();
196
-
197
- const agent = new DurableAgent({
198
- model: "anthropic/claude-haiku-4.5",
199
- instructions:
200
- "You are an assistant with access to search, notifications, polling, callbacks, and retry tools.",
201
- tools: {
202
- searchDatabase: {
203
- description: "Search the database",
204
- inputSchema: z.object({ query: z.string() }),
205
- execute: searchDatabase,
206
- },
207
- sendNotification: {
208
- description: "Send a notification to a user",
209
- inputSchema: z.object({
210
- userId: z.string(),
211
- message: z.string(),
212
- }),
213
- execute: sendNotification,
214
- },
215
- waitThenCheck: {
216
- description: "Wait for a duration then check an endpoint",
217
- inputSchema: z.object({
218
- delayMs: z.number().describe("Milliseconds to wait"),
219
- endpoint: z.string().describe("URL to check after waiting"),
220
- }),
221
- execute: waitThenCheck,
222
- },
223
- waitForCallback: {
224
- description: "Create a webhook and wait for an external system to call it",
225
- inputSchema: z.object({
226
- description: z.string().describe("What the callback is for"),
227
- }),
228
- execute: waitForCallback,
229
- },
230
- retryWithCooldown: {
231
- description: "Fetch a URL with retries and increasing cooldown between attempts",
232
- inputSchema: z.object({
233
- url: z.string(),
234
- maxAttempts: z.number().default(3),
235
- }),
236
- execute: retryWithCooldown,
237
- },
238
- },
239
- });
240
-
241
- await agent.stream({ // [!code highlight]
242
- messages: [{ role: "user", content: userMessage }],
243
- writable,
244
- });
245
- }
246
- ```
247
-
248
- ## Key APIs
249
-
250
- - [`"use workflow"`](/docs/api-reference/workflow/use-workflow) — declares the orchestrator function
251
- - [`"use step"`](/docs/api-reference/workflow/use-step) — declares step functions with retries and Node.js access
252
- - [`sleep()`](/docs/api-reference/workflow/sleep) — durable pause (only in workflow context)
253
- - [`createWebhook()`](/docs/api-reference/workflow/create-webhook) — wait for external HTTP callbacks (only in workflow context)
254
- - [`getWritable()`](/docs/api-reference/workflow/get-writable) — stream data from steps
255
- - [`DurableAgent`](/docs/api-reference/workflow-ai/durable-agent) — agent with mixed step/workflow-level tools
@@ -1,181 +0,0 @@
1
- ---
2
- title: Tool Streaming
3
- description: Stream real-time progress updates from tools to the UI while they execute.
4
- type: guide
5
- summary: Emit custom data parts from step functions to show incremental results during long-running tool calls.
6
- ---
7
-
8
- Use this pattern when tools take a long time to execute and you want to show progress updates, intermediate results, or status messages in the UI while the tool is still running.
9
-
10
- ## Pattern
11
-
12
- Inside a step function, call `getWritable<UIMessageChunk>()` to write custom data parts to the same stream the agent uses. These appear as typed data parts in the client's message parts array.
13
-
14
- ### Simplified
15
-
16
- ```typescript lineNumbers
17
- import { DurableAgent } from "@workflow/ai/agent";
18
- import { getWritable } from "workflow";
19
- import { z } from "zod";
20
- import type { UIMessageChunk } from "ai";
21
-
22
- declare function performSearch(query: string): Promise<{ id: string; title: string }[]>; // @setup
23
- declare function searchWithProgress(args: { query: string }): Promise<any>; // @setup
24
-
25
- export async function searchAgent(userMessage: string) {
26
- "use workflow";
27
-
28
- const agent = new DurableAgent({
29
- model: "anthropic/claude-haiku-4.5",
30
- tools: {
31
- search: {
32
- description: "Search for items",
33
- inputSchema: z.object({ query: z.string() }),
34
- execute: searchWithProgress,
35
- },
36
- },
37
- });
38
-
39
- await agent.stream({ // [!code highlight]
40
- messages: [{ role: "user", content: userMessage }],
41
- writable: getWritable<UIMessageChunk>(),
42
- });
43
- }
44
- ```
45
-
46
- ### Full Implementation
47
-
48
- ```typescript lineNumbers
49
- import { DurableAgent } from "@workflow/ai/agent";
50
- import { getWritable } from "workflow";
51
- import { z } from "zod";
52
- import type { UIMessageChunk } from "ai";
53
-
54
- // Custom data part type for the client to render
55
- interface FoundItemDataPart {
56
- type: "data-found-item";
57
- id: string;
58
- data: {
59
- title: string;
60
- score: number;
61
- };
62
- }
63
-
64
- // Step: Search with streaming progress updates
65
- async function searchWithProgress(
66
- { query }: { query: string },
67
- { toolCallId }: { toolCallId: string }
68
- ) {
69
- "use step";
70
-
71
- const writable = getWritable<UIMessageChunk>(); // [!code highlight]
72
- const writer = writable.getWriter();
73
-
74
- try {
75
- // Simulate finding items one at a time
76
- const items = [
77
- { title: "Result A", score: 95 },
78
- { title: "Result B", score: 87 },
79
- { title: "Result C", score: 72 },
80
- ];
81
-
82
- for (const item of items) {
83
- // Simulate search latency
84
- await new Promise((resolve) => setTimeout(resolve, 800));
85
-
86
- // Stream each result to the UI as it's found
87
- await writer.write({ // [!code highlight]
88
- type: "data-found-item",
89
- id: `${toolCallId}-${item.title}`,
90
- data: item,
91
- } as UIMessageChunk);
92
- }
93
-
94
- return {
95
- message: `Found ${items.length} results for "${query}"`,
96
- items,
97
- };
98
- } finally {
99
- writer.releaseLock();
100
- }
101
- }
102
-
103
- // Step: Fetch details for a specific item
104
- async function getItemDetails({ itemId }: { itemId: string }) {
105
- "use step";
106
-
107
- const writable = getWritable<UIMessageChunk>();
108
- const writer = writable.getWriter();
109
-
110
- try {
111
- // Emit a transient progress message
112
- await writer.write({ // [!code highlight]
113
- type: "data-progress",
114
- data: { message: `Loading details for ${itemId}...` },
115
- transient: true,
116
- } as UIMessageChunk);
117
-
118
- await new Promise((resolve) => setTimeout(resolve, 1000));
119
-
120
- return { itemId, description: "Detailed information", available: true };
121
- } finally {
122
- writer.releaseLock();
123
- }
124
- }
125
-
126
- export async function searchAgent(userMessage: string) {
127
- "use workflow";
128
-
129
- const writable = getWritable<UIMessageChunk>();
130
-
131
- const agent = new DurableAgent({
132
- model: "anthropic/claude-haiku-4.5",
133
- instructions: "You help users search for items. Use the search tool first, then get details if asked.",
134
- tools: {
135
- search: {
136
- description: "Search for items matching a query",
137
- inputSchema: z.object({
138
- query: z.string().describe("Search query"),
139
- }),
140
- execute: searchWithProgress,
141
- },
142
- getDetails: {
143
- description: "Get detailed information about a specific item",
144
- inputSchema: z.object({
145
- itemId: z.string().describe("Item ID from search results"),
146
- }),
147
- execute: getItemDetails,
148
- },
149
- },
150
- });
151
-
152
- await agent.stream({ // [!code highlight]
153
- messages: [{ role: "user", content: userMessage }],
154
- writable,
155
- });
156
- }
157
- ```
158
-
159
- ### Client Rendering
160
-
161
- ```tsx lineNumbers
162
- // In your chat component's message rendering:
163
- {message.parts.map((part, i) => {
164
- if (part.type === "data-found-item") {
165
- const item = part.data as { title: string; score: number };
166
- return (
167
- <div key={part.id} className="p-3 bg-muted rounded-md">
168
- <div className="font-medium">{item.title}</div>
169
- <div className="text-muted-foreground">Score: {item.score}</div>
170
- </div>
171
- );
172
- }
173
- // ... other part types
174
- })}
175
- ```
176
-
177
- ## Key APIs
178
-
179
- - [`"use step"`](/docs/api-reference/workflow/use-step) — step functions can write to the stream
180
- - [`getWritable()`](/docs/api-reference/workflow/get-writable) — access the run's output stream from inside a step
181
- - [`DurableAgent`](/docs/api-reference/workflow-ai/durable-agent) — agent streams LLM output to the same writable