workflow 5.0.0-beta.4 → 5.0.0-beta.41

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 (227) hide show
  1. package/README.md +63 -20
  2. package/dist/api-workflow.d.ts +1 -1
  3. package/dist/api-workflow.d.ts.map +1 -1
  4. package/dist/api-workflow.js +1 -1
  5. package/dist/api.d.ts +5 -1
  6. package/dist/api.d.ts.map +1 -1
  7. package/dist/api.js +12 -2
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +6 -1
  11. package/dist/internal/builtins.d.ts +17 -0
  12. package/dist/internal/builtins.d.ts.map +1 -1
  13. package/dist/internal/builtins.js +65 -1
  14. package/dist/internal/errors.d.ts +1 -1
  15. package/dist/internal/errors.d.ts.map +1 -1
  16. package/dist/internal/errors.js +2 -2
  17. package/dist/nest-builder.d.ts +2 -0
  18. package/dist/nest-builder.d.ts.map +1 -0
  19. package/dist/nest-builder.js +2 -0
  20. package/dist/nest-vercel-builder.d.ts +2 -0
  21. package/dist/nest-vercel-builder.d.ts.map +1 -0
  22. package/dist/nest-vercel-builder.js +2 -0
  23. package/dist/observability.d.ts +1 -1
  24. package/dist/observability.js +2 -2
  25. package/dist/runtime.d.ts +2 -1
  26. package/dist/runtime.d.ts.map +1 -1
  27. package/dist/runtime.js +5 -2
  28. package/docs/ai/chat-session-modeling.mdx +7 -3
  29. package/docs/ai/defining-tools.mdx +2 -2
  30. package/docs/ai/index.mdx +30 -25
  31. package/docs/ai/message-queueing.mdx +10 -10
  32. package/docs/ai/resumable-streams.mdx +9 -1
  33. package/docs/api-reference/index.mdx +24 -0
  34. package/docs/api-reference/meta.json +8 -0
  35. package/docs/api-reference/vitest/index.mdx +28 -7
  36. package/docs/api-reference/workflow/create-hook.mdx +79 -0
  37. package/docs/api-reference/workflow/create-webhook.mdx +1 -0
  38. package/docs/api-reference/workflow/define-hook.mdx +26 -24
  39. package/docs/api-reference/workflow/fatal-error.mdx +29 -7
  40. package/docs/api-reference/workflow/fetch.mdx +8 -4
  41. package/docs/api-reference/workflow/index.mdx +3 -0
  42. package/docs/api-reference/workflow/set-attributes.mdx +61 -0
  43. package/docs/api-reference/workflow/sleep.mdx +1 -1
  44. package/docs/api-reference/workflow-ai/durable-agent.mdx +7 -45
  45. package/docs/api-reference/workflow-ai/index.mdx +2 -2
  46. package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +44 -1
  47. package/docs/api-reference/workflow-api/get-hook-by-token.mdx +9 -0
  48. package/docs/api-reference/workflow-api/get-run.mdx +25 -0
  49. package/docs/api-reference/workflow-api/index.mdx +6 -8
  50. package/docs/api-reference/workflow-api/resume-hook.mdx +60 -1
  51. package/docs/api-reference/workflow-api/resume-webhook.mdx +6 -4
  52. package/docs/api-reference/workflow-api/start.mdx +39 -6
  53. package/docs/api-reference/workflow-astro/index.mdx +18 -0
  54. package/docs/api-reference/workflow-astro/meta.json +4 -0
  55. package/docs/api-reference/workflow-astro/workflow.mdx +45 -0
  56. package/docs/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
  57. package/docs/api-reference/workflow-errors/index.mdx +88 -0
  58. package/docs/api-reference/workflow-errors/meta.json +6 -0
  59. package/docs/api-reference/workflow-errors/precondition-failed-error.mdx +68 -0
  60. package/docs/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
  61. package/docs/api-reference/workflow-errors/workflow-error.mdx +52 -0
  62. package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +16 -6
  63. package/docs/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
  64. package/docs/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
  65. package/docs/api-reference/workflow-globals.mdx +4 -1
  66. package/docs/api-reference/workflow-nest/configure-workflow-controller.mdx +33 -0
  67. package/docs/api-reference/workflow-nest/index.mdx +31 -0
  68. package/docs/api-reference/workflow-nest/meta.json +9 -0
  69. package/docs/api-reference/workflow-nest/nest-local-builder.mdx +64 -0
  70. package/docs/api-reference/workflow-nest/workflow-controller.mdx +40 -0
  71. package/docs/api-reference/workflow-nest/workflow-module.mdx +74 -0
  72. package/docs/api-reference/workflow-next/with-workflow.mdx +56 -2
  73. package/docs/api-reference/workflow-nitro/index.mdx +60 -0
  74. package/docs/api-reference/workflow-nuxt/index.mdx +48 -0
  75. package/docs/api-reference/workflow-observability/hydrate-data.mdx +35 -0
  76. package/docs/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
  77. package/docs/api-reference/workflow-observability/index.mdx +64 -0
  78. package/docs/api-reference/workflow-observability/meta.json +11 -0
  79. package/docs/api-reference/workflow-observability/observability-revivers.mdx +50 -0
  80. package/docs/api-reference/workflow-observability/parse-class-name.mdx +41 -0
  81. package/docs/api-reference/workflow-observability/parse-step-name.mdx +40 -0
  82. package/docs/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
  83. package/docs/api-reference/workflow-runtime/create-world.mdx +39 -0
  84. package/docs/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
  85. package/docs/api-reference/{workflow-api → workflow-runtime}/get-world.mdx +7 -10
  86. package/docs/api-reference/workflow-runtime/health-check.mdx +50 -0
  87. package/docs/api-reference/workflow-runtime/index.mdx +43 -0
  88. package/docs/api-reference/workflow-runtime/meta.json +12 -0
  89. package/docs/api-reference/workflow-runtime/set-world.mdx +49 -0
  90. package/docs/api-reference/workflow-runtime/workflow-entrypoint.mdx +42 -0
  91. package/docs/api-reference/workflow-runtime/world/analytics.mdx +138 -0
  92. package/docs/api-reference/{workflow-api → workflow-runtime}/world/index.mdx +10 -10
  93. package/docs/api-reference/workflow-runtime/world/meta.json +4 -0
  94. package/docs/api-reference/{workflow-api → workflow-runtime}/world/queue.mdx +8 -6
  95. package/docs/api-reference/{workflow-api → workflow-runtime}/world/storage.mdx +24 -7
  96. package/docs/api-reference/{workflow-api → workflow-runtime}/world/streams.mdx +2 -2
  97. package/docs/api-reference/workflow-serde/index.mdx +0 -1
  98. package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +1 -2
  99. package/docs/api-reference/workflow-serde/workflow-serialize.mdx +2 -2
  100. package/docs/api-reference/workflow-sveltekit/index.mdx +18 -0
  101. package/docs/api-reference/workflow-sveltekit/meta.json +4 -0
  102. package/docs/api-reference/workflow-sveltekit/workflow-plugin.mdx +42 -0
  103. package/docs/api-reference/workflow-vite/index.mdx +18 -0
  104. package/docs/api-reference/workflow-vite/meta.json +4 -0
  105. package/docs/api-reference/workflow-vite/workflow.mdx +48 -0
  106. package/docs/changelog/attributes-mvp.mdx +380 -0
  107. package/docs/changelog/eager-processing.mdx +269 -0
  108. package/docs/changelog/index.mdx +3 -1
  109. package/docs/changelog/lazy-event-creation.md +127 -0
  110. package/docs/changelog/meta.json +9 -1
  111. package/docs/changelog/resilient-resume.mdx +22 -0
  112. package/docs/changelog/resilient-start.mdx +31 -283
  113. package/docs/changelog/step-message-ownership.mdx +360 -0
  114. package/docs/changelog/turbo-mode.md +87 -0
  115. package/docs/comparisons/index.mdx +66 -0
  116. package/docs/comparisons/meta.json +11 -0
  117. package/docs/comparisons/workflow-sdk-vs-aws-agentcore.mdx +55 -0
  118. package/docs/comparisons/workflow-sdk-vs-aws-step-functions.mdx +111 -0
  119. package/docs/comparisons/workflow-sdk-vs-cloudflare-workflows.mdx +71 -0
  120. package/docs/comparisons/workflow-sdk-vs-inngest.mdx +102 -0
  121. package/docs/comparisons/workflow-sdk-vs-temporal.mdx +123 -0
  122. package/docs/comparisons/workflow-sdk-vs-trigger-dev.mdx +103 -0
  123. package/docs/configuration/build-and-diagnostics.mdx +70 -0
  124. package/docs/configuration/cli-and-web-ui.mdx +182 -0
  125. package/docs/configuration/framework-options.mdx +165 -0
  126. package/docs/configuration/index.mdx +32 -0
  127. package/docs/configuration/meta.json +12 -0
  128. package/docs/configuration/runtime-tuning.mdx +281 -0
  129. package/docs/configuration/worlds.mdx +283 -0
  130. package/docs/cookbook/advanced/child-workflows.mdx +203 -256
  131. package/docs/cookbook/advanced/meta.json +1 -1
  132. package/docs/cookbook/advanced/publishing-libraries.mdx +33 -24
  133. package/docs/cookbook/advanced/serializable-steps.mdx +12 -4
  134. package/docs/cookbook/advanced/upgrading-workflows.mdx +199 -0
  135. package/docs/cookbook/agent-patterns/agent-cancellation.mdx +34 -75
  136. package/docs/cookbook/agent-patterns/durable-agent.mdx +14 -142
  137. package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +11 -3
  138. package/docs/cookbook/common-patterns/batching.mdx +4 -0
  139. package/docs/cookbook/common-patterns/idempotency.mdx +40 -52
  140. package/docs/cookbook/common-patterns/rate-limiting.mdx +5 -1
  141. package/docs/cookbook/common-patterns/saga.mdx +6 -2
  142. package/docs/cookbook/common-patterns/scheduling.mdx +12 -0
  143. package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +4 -0
  144. package/docs/cookbook/common-patterns/timeouts.mdx +6 -1
  145. package/docs/cookbook/common-patterns/webhooks.mdx +4 -0
  146. package/docs/cookbook/common-patterns/workflow-composition.mdx +19 -16
  147. package/docs/cookbook/index.mdx +3 -3
  148. package/docs/cookbook/integrations/ai-sdk.mdx +64 -28
  149. package/docs/cookbook/integrations/chat-sdk.mdx +17 -0
  150. package/docs/cookbook/integrations/sandbox.mdx +17 -0
  151. package/docs/{deploying/index.mdx → deploying.mdx} +7 -7
  152. package/docs/errors/abort-signal-timeout-in-workflow.mdx +84 -0
  153. package/docs/errors/corrupted-event-log.mdx +11 -8
  154. package/docs/errors/deployment-mismatch.mdx +71 -0
  155. package/docs/errors/fetch-in-workflow.mdx +4 -0
  156. package/docs/errors/hook-conflict.mdx +60 -4
  157. package/docs/errors/index.mdx +1 -35
  158. package/docs/errors/node-js-module-in-workflow.mdx +4 -0
  159. package/docs/errors/replay-divergence.mdx +27 -0
  160. package/docs/errors/runtime-decryption-failed.mdx +77 -0
  161. package/docs/errors/serialization-failed.mdx +4 -0
  162. package/docs/errors/start-invalid-workflow-function.mdx +4 -0
  163. package/docs/errors/step-executed-multiple-times.mdx +23 -0
  164. package/docs/errors/step-not-registered.mdx +1 -1
  165. package/docs/errors/timeout-in-workflow.mdx +4 -0
  166. package/docs/errors/webhook-response-not-sent.mdx +4 -0
  167. package/docs/foundations/cancellation.mdx +459 -0
  168. package/docs/foundations/errors-and-retries.mdx +7 -3
  169. package/docs/foundations/hooks.mdx +29 -0
  170. package/docs/foundations/idempotency.mdx +243 -11
  171. package/docs/foundations/index.mdx +1 -23
  172. package/docs/foundations/meta.json +3 -1
  173. package/docs/foundations/serialization.mdx +77 -41
  174. package/docs/foundations/starting-workflows.mdx +79 -2
  175. package/docs/foundations/streaming.mdx +14 -23
  176. package/docs/foundations/versioning.mdx +263 -0
  177. package/docs/getting-started/astro.mdx +12 -2
  178. package/docs/getting-started/express.mdx +6 -2
  179. package/docs/getting-started/fastify.mdx +6 -2
  180. package/docs/getting-started/hono.mdx +6 -2
  181. package/docs/getting-started/index.mdx +16 -10
  182. package/docs/getting-started/meta.json +4 -1
  183. package/docs/getting-started/nestjs.mdx +77 -3
  184. package/docs/getting-started/next.mdx +12 -6
  185. package/docs/getting-started/nitro.mdx +28 -2
  186. package/docs/getting-started/nuxt.mdx +6 -2
  187. package/docs/getting-started/python.mdx +24 -18
  188. package/docs/getting-started/react-router/index.mdx +33 -0
  189. package/docs/getting-started/react-router/meta.json +5 -0
  190. package/docs/getting-started/react-router/v7.mdx +237 -0
  191. package/docs/getting-started/react-router/v8.mdx +232 -0
  192. package/docs/getting-started/sveltekit.mdx +12 -2
  193. package/docs/getting-started/tanstack-start.mdx +245 -0
  194. package/docs/getting-started/vite.mdx +6 -2
  195. package/docs/how-it-works/cancellation.mdx +287 -0
  196. package/docs/how-it-works/code-transform.mdx +21 -17
  197. package/docs/how-it-works/encryption.mdx +5 -5
  198. package/docs/how-it-works/event-sourcing.mdx +17 -9
  199. package/docs/how-it-works/framework-integrations.mdx +96 -337
  200. package/docs/how-it-works/meta.json +2 -1
  201. package/docs/internal/index.mdx +21 -0
  202. package/docs/internal/meta.json +10 -0
  203. package/docs/internal/nitro-native-build.mdx +38 -0
  204. package/docs/internal/nitro-web-ui.mdx +24 -0
  205. package/docs/internal/serializable-abort-controller.mdx +148 -0
  206. package/docs/meta.json +2 -2
  207. package/docs/observability/attributes.mdx +112 -0
  208. package/docs/observability/index.mdx +20 -1
  209. package/docs/observability/meta.json +1 -1
  210. package/docs/observability/tracing.mdx +124 -0
  211. package/docs/testing/index.mdx +2 -2
  212. package/package.json +17 -13
  213. package/docs/api-reference/workflow-api/world/meta.json +0 -4
  214. package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
  215. package/docs/cookbook/advanced/distributed-abort-controller.mdx +0 -318
  216. package/docs/deploying/building-a-world.mdx +0 -251
  217. package/docs/deploying/meta.json +0 -4
  218. package/docs/deploying/world/local-world.mdx +0 -84
  219. package/docs/deploying/world/meta.json +0 -4
  220. package/docs/deploying/world/postgres-world.mdx +0 -222
  221. package/docs/deploying/world/vercel-world.mdx +0 -179
  222. package/docs/migration-guides/index.mdx +0 -34
  223. package/docs/migration-guides/meta.json +0 -9
  224. package/docs/migration-guides/migrating-from-aws-step-functions.mdx +0 -363
  225. package/docs/migration-guides/migrating-from-inngest.mdx +0 -314
  226. package/docs/migration-guides/migrating-from-temporal.mdx +0 -318
  227. package/docs/migration-guides/migrating-from-trigger-dev.mdx +0 -337
package/docs/ai/index.mdx CHANGED
@@ -3,6 +3,7 @@ title: Building Durable AI Agents
3
3
  description: Build AI agents that survive crashes, scale across requests, and maintain state with durable LLM tool-call loops.
4
4
  type: overview
5
5
  summary: Convert a basic AI chat app into a durable, resumable agent using Workflow SDK.
6
+ manualCards: true
6
7
  related:
7
8
  - /docs/foundations/workflows-and-steps
8
9
  - /docs/foundations/streaming
@@ -63,7 +64,7 @@ In order to connect to an LLM, we'll need to set up an API key. The easiest way
63
64
 
64
65
  <Tab value="Gateway">
65
66
 
66
- Get a Gateway API key from the [Vercel Gateway](https://vercel.com/docs/gateway/api-reference/overview) page.
67
+ Get a Gateway API key from the [Vercel Gateway](https://vercel.com/docs/ai-gateway/authentication) page.
67
68
 
68
69
  Then add it to your `.env.local` file:
69
70
 
@@ -92,7 +93,7 @@ Then modify your API endpoint to use the OpenAI provider:
92
93
  {/* @skip-typecheck: incomplete code sample */}
93
94
  ```typescript title="app/api/chat/route.ts" lineNumbers
94
95
  // ...
95
- import { openai } from "@workflow/ai/openai"; // [!code highlight]
96
+ import { openai } from "@ai-sdk/openai"; // [!code highlight]
96
97
 
97
98
  export async function POST(req: Request) {
98
99
  // ...
@@ -120,16 +121,17 @@ The core code that makes all of this happen is quite simple. Here's a breakdown
120
121
 
121
122
  <Tab value="API Route">
122
123
 
123
- Our API route makes a simple call to [AI SDK's `Agent` class](https://ai-sdk.dev/docs/agents/overview), which is a simple wrapper around [AI SDK's `streamText` function](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text#streamtext). This is also where we pass tools to the agent.
124
+ Our API route makes a simple call to [AI SDK's `ToolLoopAgent` class](https://ai-sdk.dev/docs/agents/overview), which encapsulates the LLM call, tool execution loop, and stopping conditions on top of [AI SDK's `streamText` function](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text#streamtext). This is also where we pass tools to the agent.
124
125
 
125
126
  ```typescript title="app/api/chat/route.ts" lineNumbers
126
- import { Experimental_Agent as Agent } from "ai";
127
- import type { LanguageModel } from "ai";
127
+ import { ToolLoopAgent } from "ai";
128
+ import type { UIMessage } from "ai";
129
+ import { convertToModelMessages, createUIMessageStreamResponse } from "ai";
128
130
 
129
131
  export async function POST(req: Request) {
130
132
  const { messages }: { messages: UIMessage[] } = await req.json();
131
- const agent = new Agent({ // [!code highlight]
132
- model: gateway("bedrock/claude-4-5-haiku-20251001-v1"),
133
+ const agent = new ToolLoopAgent({ // [!code highlight]
134
+ model: "bedrock/claude-4-5-haiku-20251001-v1",
133
135
  instructions: FLIGHT_ASSISTANT_PROMPT,
134
136
  tools: flightBookingTools,
135
137
  });
@@ -231,7 +233,7 @@ Now that we have a basic agent using AI SDK, we can modify it to make it durable
231
233
  Add the Workflow SDK packages to your project:
232
234
 
233
235
  ```package-install
234
- npm i workflow @workflow/ai
236
+ npm i workflow @ai-sdk/workflow
235
237
  ```
236
238
 
237
239
  and extend the Next.js config to transform your workflow code (see [Getting Started](/docs/getting-started/next) for more details).
@@ -257,18 +259,18 @@ Move the agent logic into a separate function, which will serve as our workflow
257
259
 
258
260
  {/* @skip-typecheck: Shows two mutually exclusive model options */}
259
261
  ```typescript title="workflows/chat/workflow.ts" lineNumbers
260
- import { DurableAgent } from "@workflow/ai/agent"; // [!code highlight]
262
+ import { WorkflowAgent, type ModelCallStreamPart } from "@ai-sdk/workflow"; // [!code highlight]
261
263
  import { getWritable } from "workflow"; // [!code highlight]
262
264
  import { tools } from "@/ai/tools";
263
- import { openai } from "@workflow/ai/openai";
264
- import type { ModelMessage, UIMessageChunk } from "ai";
265
+ import { openai } from "@ai-sdk/openai";
266
+ import { convertToModelMessages, type UIMessage } from "ai";
265
267
 
266
- export async function chatWorkflow(messages: ModelMessage[]) {
268
+ export async function chatWorkflow(messages: UIMessage[]) {
267
269
  "use workflow"; // [!code highlight]
268
270
 
269
- const writable = getWritable<UIMessageChunk>(); // [!code highlight]
271
+ const writable = getWritable<ModelCallStreamPart>(); // [!code highlight]
270
272
 
271
- const agent = new DurableAgent({ // [!code highlight]
273
+ const agent = new WorkflowAgent({ // [!code highlight]
272
274
 
273
275
  // If using AI Gateway, just specify the model name as a string:
274
276
  model: "bedrock/claude-4-5-haiku-20251001-v1", // [!code highlight]
@@ -280,8 +282,10 @@ export async function chatWorkflow(messages: ModelMessage[]) {
280
282
  tools: flightBookingTools,
281
283
  });
282
284
 
285
+ const modelMessages = await convertToModelMessages(messages); // [!code highlight]
286
+
283
287
  await agent.stream({ // [!code highlight]
284
- messages,
288
+ messages: modelMessages,
285
289
  writable,
286
290
  });
287
291
  }
@@ -290,8 +294,9 @@ export async function chatWorkflow(messages: ModelMessage[]) {
290
294
  Key changes:
291
295
 
292
296
  - Add the `"use workflow"` directive to mark our Agent as a workflow function
293
- - Replaced `Agent` with [`DurableAgent`](/docs/api-reference/workflow-ai/durable-agent) from `@workflow/ai/agent`. This ensures that all calls to the LLM are executed as "steps", and results are aggregated within the workflow context (see [Workflows and Steps](/docs/foundations/workflows-and-steps) for more details on how workflows/steps are defined).
294
- - Use [`getWritable()`](/docs/api-reference/workflow/get-writable) to get a stream for agent output. This stream is persistent, and API endpoints can read from a run's stream at any time.
297
+ - Replace the in-memory agent with [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) from `@ai-sdk/workflow`. This runs the agent loop inside a workflow, persists state across step boundaries, and lets tool executions marked with `"use step"` retry automatically.
298
+ - Convert AI SDK `UIMessage` values to model messages inside the workflow before calling `agent.stream()`.
299
+ - Use [`getWritable()`](/docs/api-reference/workflow/get-writable) to get a stream for agent output. `WorkflowAgent` writes `ModelCallStreamPart` chunks to this persistent stream, and API endpoints can read from a run's stream at any time.
295
300
  </Step>
296
301
 
297
302
  <Step>
@@ -300,19 +305,18 @@ Key changes:
300
305
  Remove the agent call that we just extracted, and replace it with a call to `start()` to run the workflow:
301
306
 
302
307
  ```typescript title="app/api/chat/route.ts" lineNumbers
303
- import type { UIMessage } from "ai";
304
- import { convertToModelMessages, createUIMessageStreamResponse } from "ai";
308
+ import { createModelCallToUIChunkTransform } from "@ai-sdk/workflow";
309
+ import { createUIMessageStreamResponse, type UIMessage } from "ai";
305
310
  import { start } from "workflow/api";
306
311
  import { chatWorkflow } from "@/workflows/chat/workflow";
307
312
 
308
313
  export async function POST(req: Request) {
309
314
  const { messages }: { messages: UIMessage[] } = await req.json();
310
- const modelMessages = await convertToModelMessages(messages);
311
315
 
312
- const run = await start(chatWorkflow, [modelMessages]); // [!code highlight]
316
+ const run = await start(chatWorkflow, [messages]); // [!code highlight]
313
317
 
314
318
  return createUIMessageStreamResponse({
315
- stream: run.readable, // [!code highlight]
319
+ stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()), // [!code highlight]
316
320
  });
317
321
  }
318
322
  ```
@@ -320,7 +324,8 @@ export async function POST(req: Request) {
320
324
  Key changes:
321
325
 
322
326
  - Call `start()` to run the workflow function. This returns a `Run` object, which contains the run ID and the readable stream (see [Starting Workflows](/docs/foundations/starting-workflows) for more details on the `Run` object).
323
- - Pass the `writable` to `agent.stream()` instead of returning a stream directly, ensuring all the Agent output is written to to the run's stream.
327
+ - Pass the `writable` to `agent.stream()` instead of returning a stream directly, ensuring all the Agent output is written to the run's stream.
328
+ - Pipe the readable stream through `createModelCallToUIChunkTransform()` so the raw model-call chunks become AI SDK UI message chunks before they are returned to the client.
324
329
 
325
330
  </Step>
326
331
 
@@ -377,7 +382,7 @@ export async function checkBaggageAllowance(
377
382
 
378
383
  With `"use step"`:
379
384
 
380
- - The tool execution runs in a separate step with full Node.js access. In production, each step is executed in a separate worker process, which scales automatically with your workload.
385
+ - The tool execution runs as a durable step with full Node.js access. The combined runtime may execute it inline in the flow request or resume it through the workflow queue, and can scale queued work with your workload.
381
386
  - Failed tool calls are automatically retried (up to 3 times by default). See [Errors and Retries](/docs/foundations/errors-and-retries) for more details.
382
387
  - Each tool execution appears as a discrete step in observability tools. See [Observability](/docs/observability) for more details.
383
388
  </Step>
@@ -422,7 +427,7 @@ A complete example that includes all of the above, plus all of the "next steps"
422
427
  ## Related Documentation
423
428
 
424
429
  - [Tools](/docs/ai/defining-tools) - Patterns for defining tools for your agent
425
- - [`DurableAgent` API Reference](/docs/api-reference/workflow-ai/durable-agent) - Full API documentation
430
+ - [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) - AI SDK API for durable, resumable agents
426
431
  - [Workflows and Steps](/docs/foundations/workflows-and-steps) - Core concepts
427
432
  - [Streaming](/docs/foundations/streaming) - In-depth streaming guide
428
433
  - [Errors and Retries](/docs/foundations/errors-and-retries) - Error handling patterns
@@ -13,7 +13,7 @@ related:
13
13
 
14
14
  When using [multi-turn workflows](/docs/ai/chat-session-modeling#multi-turn-workflows), messages typically arrive between agent turns. The workflow waits at a hook, receives a message, then starts a new turn. But sometimes you need to inject messages *during* an agent's turn, before tool calls complete or while the model is reasoning.
15
15
 
16
- `DurableAgent`'s `prepareStep` callback enables this by running before each step in the agent loop, giving you a chance to inject queued messages into the conversation. `prepareStep` also allows you to modify the model choice and existing messages mid-turn, see AI SDK's [prepareStep callback](https://ai-sdk.dev/docs/agents/loop-control#prepare-step) for more details.
16
+ `WorkflowAgent`'s `prepareStep` callback enables this by running before each step in the agent loop, giving you a chance to inject queued messages into the conversation. `prepareStep` also allows you to modify the model choice and existing messages mid-turn, see AI SDK's [prepareStep callback](https://ai-sdk.dev/docs/agents/loop-control#prepare-step) for more details.
17
17
 
18
18
  ## When to Use This
19
19
 
@@ -52,20 +52,20 @@ interface PrepareStepResult {
52
52
  Once you have a [multi-turn workflow](/docs/ai/chat-session-modeling#multi-turn-workflows), you can combine a message queue with `prepareStep` to inject messages that arrive during processing:
53
53
 
54
54
  ```typescript title="workflows/chat/index.ts" lineNumbers
55
- import { DurableAgent } from "@workflow/ai/agent";
55
+ import { WorkflowAgent, type ModelCallStreamPart } from "@ai-sdk/workflow";
56
56
  import { getWritable, getWorkflowMetadata } from "workflow";
57
57
  import { chatMessageHook } from "./hooks/chat-message";
58
58
  import { flightBookingTools, FLIGHT_ASSISTANT_PROMPT } from "./steps/tools";
59
- import type { UIMessageChunk, ModelMessage } from "ai";
59
+ import type { ModelMessage } from "ai";
60
60
 
61
61
  export async function chat(initialMessages: ModelMessage[]) {
62
62
  "use workflow";
63
63
 
64
64
  const { workflowRunId: runId } = getWorkflowMetadata();
65
- const writable = getWritable<UIMessageChunk>();
65
+ const writable = getWritable<ModelCallStreamPart>();
66
66
  const messageQueue: Array<{ role: "user"; content: string }> = []; // [!code highlight]
67
67
 
68
- const agent = new DurableAgent({
68
+ const agent = new WorkflowAgent({
69
69
  model: "bedrock/claude-haiku-4-5-20251001-v1",
70
70
  instructions: FLIGHT_ASSISTANT_PROMPT,
71
71
  tools: flightBookingTools,
@@ -111,20 +111,20 @@ The `prepareStep` callback receives messages in `ModelMessage[]` format (with co
111
111
  You can also combine message queueing with the standard multi-turn pattern:
112
112
 
113
113
  ```typescript title="workflows/chat/index.ts" lineNumbers
114
- import { DurableAgent } from "@workflow/ai/agent";
114
+ import { WorkflowAgent, type ModelCallStreamPart } from "@ai-sdk/workflow";
115
115
  import { getWritable, getWorkflowMetadata } from "workflow";
116
116
  import { chatMessageHook } from "./hooks/chat-message";
117
- import type { UIMessageChunk, ModelMessage } from "ai";
117
+ import type { ModelMessage } from "ai";
118
118
 
119
119
  export async function chat(initialMessages: ModelMessage[]) {
120
120
  "use workflow";
121
121
 
122
122
  const { workflowRunId: runId } = getWorkflowMetadata();
123
- const writable = getWritable<UIMessageChunk>();
123
+ const writable = getWritable<ModelCallStreamPart>();
124
124
  const messages: ModelMessage[] = [...initialMessages];
125
125
  const messageQueue: Array<{ role: "user"; content: string }> = [];
126
126
 
127
- const agent = new DurableAgent({ /* ... */ });
127
+ const agent = new WorkflowAgent({ /* ... */ });
128
128
  const hook = chatMessageHook.create({ token: runId });
129
129
 
130
130
  while (true) {
@@ -173,5 +173,5 @@ export async function chat(initialMessages: ModelMessage[]) {
173
173
 
174
174
  - [Chat Session Modeling](/docs/ai/chat-session-modeling) - Single-turn vs multi-turn patterns
175
175
  - [Building Durable AI Agents](/docs/ai) - Complete guide to creating durable agents
176
- - [`DurableAgent` API Reference](/docs/api-reference/workflow-ai/durable-agent) - Full API documentation
176
+ - [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) - AI SDK API for durable, resumable agents
177
177
  - [`defineHook()` API Reference](/docs/api-reference/workflow/define-hook) - Hook configuration options
@@ -12,6 +12,10 @@ related:
12
12
  - /docs/api-reference/workflow-api/get-run
13
13
  ---
14
14
 
15
+ <Callout type="warn">
16
+ `WorkflowChatTransport` now ships in AI SDK as a 1:1 port — import it from `@ai-sdk/workflow` (the `@workflow/ai` export is deprecated). See [Resumable Streaming with `WorkflowChatTransport`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#resumable-streaming-with-workflowchattransport) for the full reference.
17
+ </Callout>
18
+
15
19
  When building chat interfaces, it's common to run into network interruptions, page refreshes, or serverless function timeouts, which can break the connection to an in-progress agent.
16
20
 
17
21
  Where a standard chat implementation would require the user to resend their message and wait for the entire response again, workflow runs are durable, and so are the streams attached to them. This means a stream can be resumed at any point, optionally only syncing the data that was missed since the last connection.
@@ -109,7 +113,7 @@ Replace the default transport in AI-SDK's `useChat` with [`WorkflowChatTransport
109
113
  "use client";
110
114
 
111
115
  import { useChat } from "@ai-sdk/react";
112
- import { WorkflowChatTransport } from "@workflow/ai"; // [!code highlight]
116
+ import { WorkflowChatTransport } from "@ai-sdk/workflow"; // [!code highlight]
113
117
  import { useMemo, useState } from "react";
114
118
 
115
119
  export default function ChatPage() {
@@ -194,6 +198,10 @@ This avoids replaying potentially thousands of chunks and lets the UI render fas
194
198
  When using a negative `initialStartIndex`, the reconnection endpoint **must** return the `x-workflow-stream-tail-index` header (as shown in [Step 2](#add-a-stream-reconnection-endpoint) above). The transport uses this header to compute absolute chunk positions so that retries after a disconnect resume from the correct position. If the header is missing, the transport falls back to `startIndex: 0` (replaying the entire stream) and logs a warning.
195
199
  </Callout>
196
200
 
201
+ ### Mid-part resumes
202
+
203
+ A workflow stream is a flat sequence of chunks, but the AI SDK's UI protocol groups chunks into logical parts (`text-*`, `reasoning-*`, `tool-input-*`) that must be opened with a `*-start` before any `*-delta` or `*-end`. A non-zero `startIndex` can land in the middle of an open part. See [`WorkflowChatTransport` → Mid-part resumes](/docs/api-reference/workflow-ai/workflow-chat-transport#mid-part-resumes) for how this is handled and an example of rewinding to a step boundary on the server.
204
+
197
205
  ## Related Documentation
198
206
 
199
207
  - [`WorkflowChatTransport` API Reference](/docs/api-reference/workflow-ai/workflow-chat-transport) - Full configuration options
@@ -17,9 +17,33 @@ All the functions and primitives that come with Workflow SDK by package.
17
17
  <Card title="workflow/api" href="/docs/api-reference/workflow-api">
18
18
  API reference for runtime functions from the `workflow/api` package.
19
19
  </Card>
20
+ <Card title="workflow/runtime" href="/docs/api-reference/workflow-runtime">
21
+ Runtime functions for resolving the World instance and the low-level World SDK, including storage and analytics queries.
22
+ </Card>
23
+ <Card title="workflow/observability" href="/docs/api-reference/workflow-observability">
24
+ Utilities to hydrate step I/O, parse display names, and decrypt workflow data.
25
+ </Card>
20
26
  <Card title="workflow/next" href="/docs/api-reference/workflow-next">
21
27
  Next.js integration for Workflow SDK that automatically configures bundling and runtime support.
22
28
  </Card>
29
+ <Card title="workflow/nitro" href="/docs/api-reference/workflow-nitro">
30
+ Nitro module for workflow bundling and runtime support.
31
+ </Card>
32
+ <Card title="workflow/nuxt" href="/docs/api-reference/workflow-nuxt">
33
+ Nuxt module for workflow bundling and runtime support.
34
+ </Card>
35
+ <Card title="workflow/sveltekit" href="/docs/api-reference/workflow-sveltekit">
36
+ SvelteKit Vite plugin for workflow bundling and runtime support.
37
+ </Card>
38
+ <Card title="workflow/astro" href="/docs/api-reference/workflow-astro">
39
+ Astro integration for workflow bundling and runtime support.
40
+ </Card>
41
+ <Card title="workflow/vite" href="/docs/api-reference/workflow-vite">
42
+ Standalone Vite plugin for workflow bundling and runtime support.
43
+ </Card>
44
+ <Card title="workflow/nest" href="/docs/api-reference/workflow-nest">
45
+ NestJS module for workflow bundling and runtime support.
46
+ </Card>
23
47
  <Card title="workflow/errors" href="/docs/api-reference/workflow-errors">
24
48
  Semantic error types for handling workflow storage backend failures.
25
49
  </Card>
@@ -4,7 +4,15 @@
4
4
  "workflow-globals",
5
5
  "workflow",
6
6
  "workflow-api",
7
+ "workflow-runtime",
8
+ "workflow-observability",
7
9
  "workflow-next",
10
+ "workflow-nitro",
11
+ "workflow-nuxt",
12
+ "workflow-sveltekit",
13
+ "workflow-astro",
14
+ "workflow-vite",
15
+ "workflow-nest",
8
16
  "workflow-errors",
9
17
  "workflow-serde",
10
18
  "workflow-ai",
@@ -11,7 +11,6 @@ The `@workflow/vitest` package provides a Vitest plugin and test helpers for run
11
11
 
12
12
  Returns a Vite plugin array that handles SWC transforms, bundle building, and in-process handler registration automatically.
13
13
 
14
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
15
14
 
16
15
  ```typescript
17
16
  import { defineConfig } from "vitest/config";
@@ -22,6 +21,29 @@ export default defineConfig({
22
21
  });
23
22
  ```
24
23
 
24
+ Pass a [`WorkflowTestOptions`](#workflowtestoptions) object when your project uses a non-standard layout — for example, a monorepo where `workflows/` does not live at the Vitest config's directory, or when the default `.workflow-data` / `.workflow-vitest` output locations need to move. The plugin forwards these paths to `buildWorkflowTests()` and `setupWorkflowTests()` through Vitest's per-project provided context, so each Vitest workspace project stays isolated.
25
+
26
+
27
+ ```typescript
28
+ import { defineConfig } from "vitest/config";
29
+ import { workflow } from "@workflow/vitest";
30
+
31
+ export default defineConfig({
32
+ plugins: [
33
+ workflow({
34
+ cwd: "./apps/api",
35
+ rootDir: "./apps/api/test-artifacts",
36
+ }),
37
+ ],
38
+ });
39
+ ```
40
+
41
+ **Parameters:**
42
+
43
+ | Parameter | Type | Description |
44
+ | --- | --- | --- |
45
+ | `options?` | `WorkflowTestOptions` | Optional configuration |
46
+
25
47
  **Returns:** `Plugin[]`
26
48
 
27
49
  ## Setup Functions
@@ -30,7 +52,6 @@ export default defineConfig({
30
52
 
31
53
  Builds workflow and step bundles to disk. Called automatically by the `workflow()` plugin in `globalSetup`. Use directly only for [manual setup](/docs/testing#manual-setup).
32
54
 
33
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
34
55
 
35
56
  ```typescript
36
57
  import { buildWorkflowTests } from "@workflow/vitest";
@@ -48,11 +69,10 @@ export async function setup() {
48
69
 
49
70
  ### `setupWorkflowTests()`
50
71
 
51
- Sets up an in-process workflow runtime in each test worker. Imports pre-built bundles, creates a [Local World](/docs/worlds/local) instance with direct handlers, and sets it as the global world. Clears all workflow data on each invocation for full test isolation.
72
+ Sets up an in-process workflow runtime in each test worker. Imports pre-built bundles, creates a [Local World](/worlds/local) instance with direct handlers, and sets it as the global world. Clears all workflow data on each invocation for full test isolation.
52
73
 
53
74
  Called automatically by the `workflow()` plugin in `setupFiles`. Use directly only for [manual setup](/docs/testing#manual-setup).
54
75
 
55
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
56
76
 
57
77
  ```typescript
58
78
  import { beforeAll, afterAll } from "vitest";
@@ -83,7 +103,10 @@ Tears down the workflow test world. Clears the global world and closes the Local
83
103
 
84
104
  | Option | Type | Default | Description |
85
105
  | --- | --- | --- | --- |
86
- | `cwd` | `string` | `process.cwd()` | The working directory of the project (where `workflows/` lives) |
106
+ | `cwd` | `string` | `process.cwd()` | The working directory of the project (where `workflows/` lives). Relative paths resolve against `process.cwd()`. |
107
+ | `rootDir` | `string` | same as `cwd` | Root directory used for default test artifacts. When set, `dataDir` and `outDir` default to `<rootDir>/.workflow-data` and `<rootDir>/.workflow-vitest`. Relative paths resolve against `cwd`. |
108
+ | `dataDir` | `string` | `<rootDir>/.workflow-data` | Directory for workflow runtime data written by the test world. Relative paths resolve against `cwd`. |
109
+ | `outDir` | `string` | `<rootDir>/.workflow-vitest` | Directory for generated workflow and step bundles. Relative paths resolve against `cwd`. |
87
110
 
88
111
  ## Test Helpers
89
112
 
@@ -91,7 +114,6 @@ Tears down the workflow test world. Clears the global world and closes the Local
91
114
 
92
115
  Polls the event log until the workflow has a pending `sleep()` call — one with a `wait_created` event but no corresponding `wait_completed` event. Returns the correlation ID of the pending sleep, which can be passed to [`wakeUp()`](/docs/api-reference/workflow-api/get-run) to target a specific sleep.
93
116
 
94
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
95
117
 
96
118
  ```typescript
97
119
  import { waitForSleep } from "@workflow/vitest"; // [!code highlight]
@@ -120,7 +142,6 @@ await getRun(run.runId).wakeUp({ correlationIds: [sleepId] }); // [!code highlig
120
142
 
121
143
  Polls the hook list and event log until a hook matching the optional `token` filter exists that hasn't been received yet. Returns the matching hook object.
122
144
 
123
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
124
145
 
125
146
  ```typescript
126
147
  import { waitForHook } from "@workflow/vitest"; // [!code highlight]
@@ -8,6 +8,7 @@ prerequisites:
8
8
  related:
9
9
  - /docs/api-reference/workflow/define-hook
10
10
  - /docs/api-reference/workflow/create-webhook
11
+ - /docs/foundations/idempotency
11
12
  ---
12
13
 
13
14
  Creates a low-level hook primitive that can be used to resume a workflow run with arbitrary payloads.
@@ -65,6 +66,8 @@ export default Hook;`}
65
66
 
66
67
  The returned `Hook` object also implements `AsyncIterable<T>`, which allows you to iterate over incoming payloads using `for await...of` syntax.
67
68
 
69
+ Use `hook.getConflict()` to check whether the hook token is already claimed by another hook, including one kept reserved after its run ends, without waiting for hook payload data. Calling `createHook()` on its own does not register the hook — registration is only committed when the workflow suspends. Awaiting `hook.getConflict()` suspends the workflow to commit the registration, then resolves with `null` once `hook_created` is recorded, or with the conflicting [`Run`](/docs/api-reference/workflow-api/get-run).
70
+
68
71
  ## Examples
69
72
 
70
73
  ### Basic Usage
@@ -112,6 +115,81 @@ export async function slackBotWorkflow(channelId: string) {
112
115
  }
113
116
  ```
114
117
 
118
+ ### Detecting Token Conflicts
119
+
120
+ Use `hook.getConflict()` when the workflow needs to claim a hook token before doing other work, but does not need a payload yet:
121
+
122
+ ```typescript lineNumbers
123
+ import { createHook } from "workflow";
124
+
125
+ declare function chargeOrder(orderId: string): Promise<void>; // @setup
126
+
127
+ async function processOrder(orderId: string) {
128
+ "use workflow";
129
+
130
+ using hook = createHook({ // [!code highlight]
131
+ token: `order:${orderId}` // [!code highlight]
132
+ }); // [!code highlight]
133
+
134
+ const conflict = await hook.getConflict(); // [!code highlight]
135
+ if (conflict) { // [!code highlight]
136
+ // Another active workflow run already owns this token.
137
+ return { dedupedTo: conflict.runId };
138
+ }
139
+
140
+ await chargeOrder(orderId);
141
+ }
142
+ ```
143
+
144
+ Because `createHook()` alone does not suspend the workflow, awaiting `hook.getConflict()` is what actually suspends the run and commits the hook registration. It only waits for registration — to receive payload data from a future `resumeHook()` call, await the hook itself or iterate it with `for await...of`.
145
+
146
+ On a conflict, the resolved value is a `Run` handle for the run that owns the token, with durable step-backed accessors. The duplicate run can decide in code how to handle it: return or log `conflict.runId`, inspect `await conflict.status`, wait on `await conflict.returnValue`, or cancel the owner with `await conflict.cancel()` and continue in the current run. See [Run idempotency](/docs/foundations/idempotency#run-idempotency) for these strategies in context.
147
+
148
+ <Callout type="info">
149
+ Custom hook tokens are the recommended way to coordinate active workflow runs. Use a deterministic token from your domain, such as an order ID or conversation ID, create the hook near the beginning of the workflow, and check `await hook.getConflict()` before work that depends on owning the token. See [Run idempotency](/docs/foundations/idempotency#run-idempotency).
150
+ </Callout>
151
+
152
+ ### Keep a Token Unavailable After the Run Ends
153
+
154
+ By default, another Hook can use the token after its workflow ends. Set `experimental_minRetention` to keep the token unavailable for at least a specific time after `createHook()` runs:
155
+
156
+ ```typescript lineNumbers
157
+ import { createHook } from "workflow";
158
+
159
+ declare function processOwnedOrder(orderId: string): Promise<void>; // @setup
160
+
161
+ export async function processOrder(orderId: string) {
162
+ "use workflow";
163
+
164
+ const hook = createHook({ // [!code highlight]
165
+ token: `order:${orderId}`, // [!code highlight]
166
+ experimental_minRetention: "30d", // [!code highlight]
167
+ }); // [!code highlight]
168
+
169
+ const conflict = await hook.getConflict();
170
+ if (conflict) {
171
+ return { status: "duplicate" as const, runId: conflict.runId };
172
+ }
173
+
174
+ await processOwnedOrder(orderId);
175
+ return { status: "processed" as const };
176
+ }
177
+ ```
178
+
179
+ `experimental_minRetention` accepts the same values as [`sleep()`](/docs/api-reference/workflow/sleep): a duration string such as `"30d"`, a number of milliseconds, or an absolute `Date`. Durations start when `createHook()` runs.
180
+
181
+ The Hook remains active until the workflow ends, even if the configured time passes first. Another Hook can use the token only after both the workflow has ended and the configured time has passed. For example, `"30d"` keeps the token unavailable for 29 more days if the workflow ends after 1 day. A workflow that runs for more than 30 days releases the token when it ends.
182
+
183
+ After the workflow ends, [`getHookByToken()`](/docs/api-reference/workflow-api/get-hook-by-token) can still find the Hook until retention ends, but the Hook cannot be resumed.
184
+
185
+ <Callout type="warn">
186
+ `using` auto-disposes the Hook at scope exit, which releases the token immediately and defeats `experimental_minRetention`. Declare retained Hooks with `const` and let the runtime clean them up when the run ends.
187
+ </Callout>
188
+
189
+ <Callout type="warn">
190
+ This option is experimental. Worlds can limit how long tokens are retained; see [World configuration](/docs/configuration/worlds) for each World's limit. If the configured World does not support minimum retention, the workflow fails when registering the Hook. `createWebhook()` does not accept this option.
191
+ </Callout>
192
+
115
193
  ### Waiting for Multiple Payloads
116
194
 
117
195
  You can also wait for multiple payloads by using the `for await...of` syntax.
@@ -195,3 +273,4 @@ This is equivalent to manually calling `dispose()` but ensures the hook is alway
195
273
  - [`defineHook()`](/docs/api-reference/workflow/define-hook) - Type-safe hook helper
196
274
  - [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) - Resume a hook with a payload
197
275
  - [`createWebhook()`](/docs/api-reference/workflow/create-webhook) - Higher-level HTTP webhook abstraction
276
+ - [Idempotency](/docs/foundations/idempotency) - Deduplicate step side effects and workflow starts
@@ -55,6 +55,7 @@ The returned `Webhook` object has:
55
55
 
56
56
  - `url`: The HTTP endpoint URL that external systems can call
57
57
  - `token`: The unique token identifying this webhook
58
+ - `getConflict()`: A promise that resolves with the conflicting run if another active hook already owns this token, or `null` once the webhook endpoint has been registered
58
59
  - Implements `AsyncIterable<T>` for handling multiple requests, where `T` is `Request` (default) or `RequestWithResponse` (manual mode)
59
60
 
60
61
  When using `createWebhook({ respondWith: 'manual' })`, the resolved request type is `RequestWithResponse`, which extends the standard `Request` interface with a `respondWith(response: Response): Promise<void>` method for sending custom responses back to the caller.
@@ -46,24 +46,21 @@ showSections={['parameters']}
46
46
 
47
47
  ### Returns
48
48
 
49
+ `defineHook()` returns a `TypedHook<TInput, TOutput>`:
50
+
49
51
  <TSDoc
50
52
  definition={`
51
- interface DefineHook<T> {
52
- /**
53
-
54
- * Creates a new hook with the defined payload type.
55
- */
56
- create: (options?: HookOptions) => Hook<T>;
57
-
58
- /**
59
-
60
- * Resumes a hook by sending a payload with the defined type.
61
- */
62
- resume: (token: string, payload: T) => Promise<HookEntity | null>;
53
+ interface TypedHook<TInput, TOutput> {
54
+ /** Creates the hook. Call inside a "use workflow" function. */
55
+ create(options?: HookOptions): Hook<TOutput>;
56
+ /** Resumes the hook from runtime code. Resolves to the resumed hook; throws HookNotFoundError if the token does not match an active hook. */
57
+ resume(token: string, payload: TInput): Promise<HookEntity>;
63
58
  }
64
- export default DefineHook;`}
59
+ export default TypedHook;`}
65
60
  />
66
61
 
62
+ `create()` is called inside a `"use workflow"` function to create the hook; `resume()` is called from runtime code (an API route or server action). When a `schema` is provided, `resume()` accepts the raw input type (`TInput`) and the workflow receives the validated and possibly transformed output type (`TOutput`); without a schema, `TOutput` defaults to `TInput`. `resume()` throws [`HookNotFoundError`](/docs/api-reference/workflow-errors/hook-not-found-error) if the token does not match an active hook — it does not return `null`.
63
+
67
64
  ## Examples
68
65
 
69
66
  ### Basic Type-Safe Hook Definition
@@ -93,24 +90,29 @@ export async function workflowWithApproval() {
93
90
 
94
91
  ### Resuming with Type Safety
95
92
 
96
- Hooks can be resumed using the same defined hook and a token. By using the same hook, you can ensure that the payload matches the defined type when resuming a hook.
93
+ Hooks can be resumed using the same defined hook and a token. By using the same hook, you can ensure that the payload matches the defined type when resuming a hook. `resume()` resolves to the resumed hook and throws [`HookNotFoundError`](/docs/api-reference/workflow-errors/hook-not-found-error) if the token does not match an active hook.
97
94
 
98
95
  ```typescript lineNumbers
96
+ import { HookNotFoundError } from "workflow/errors";
97
+
99
98
  // Use the same defined hook to resume
100
99
  export async function POST(request: Request) {
101
100
  const { token, approved, comment } = await request.json();
102
101
 
103
- // Type-safe resumption - TypeScript ensures the payload matches
104
- const result = await approvalHook.resume(token, { // [!code highlight]
105
- approved, // [!code highlight]
106
- comment, // [!code highlight]
107
- }); // [!code highlight]
108
-
109
- if (!result) {
110
- return Response.json({ error: "Hook not found" }, { status: 404 });
102
+ try {
103
+ // Type-safe resumption - TypeScript ensures the payload matches
104
+ const hook = await approvalHook.resume(token, { // [!code highlight]
105
+ approved, // [!code highlight]
106
+ comment, // [!code highlight]
107
+ }); // [!code highlight]
108
+
109
+ return Response.json({ success: true, runId: hook.runId });
110
+ } catch (error) {
111
+ if (HookNotFoundError.is(error)) { // [!code highlight]
112
+ return Response.json({ error: "Hook not found" }, { status: 404 });
113
+ }
114
+ throw error;
111
115
  }
112
-
113
- return Response.json({ success: true, runId: result.runId });
114
116
  }
115
117
  ```
116
118
 
@@ -29,16 +29,38 @@ async function fallibleStep() {
29
29
 
30
30
  ## API Signature
31
31
 
32
- ### Parameters
32
+ ### Constructor
33
+
34
+ `new FatalError(message)`
35
+
36
+ | Parameter | Type | Description |
37
+ | --------- | -------- | ------------------ |
38
+ | `message` | `string` | The error message. |
39
+
40
+ ### Properties
33
41
 
34
42
  <TSDoc
35
43
  definition={`
36
- interface Error {
37
- /**
38
-
39
- * The error message.
40
- */
44
+ interface FatalError {
45
+ /** The error message. */
41
46
  message: string;
47
+ /** Always true. Marks the error as non-retryable. */
48
+ fatal: boolean;
42
49
  }
43
- export default Error;`}
50
+ export default FatalError;`}
44
51
  />
52
+
53
+ ### Static Methods
54
+
55
+ #### `FatalError.is(value)`
56
+
57
+ Type-safe check for `FatalError` instances. Preferred over `instanceof` because it works across module boundaries and VM contexts.
58
+
59
+ ```typescript
60
+ import { FatalError } from "workflow"
61
+ declare const error: unknown; // @setup
62
+
63
+ if (FatalError.is(error)) {
64
+ // error is typed as FatalError
65
+ }
66
+ ```