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.
- package/README.md +63 -20
- package/dist/api-workflow.d.ts +1 -1
- package/dist/api-workflow.d.ts.map +1 -1
- package/dist/api-workflow.js +1 -1
- package/dist/api.d.ts +5 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +12 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/internal/builtins.d.ts +17 -0
- package/dist/internal/builtins.d.ts.map +1 -1
- package/dist/internal/builtins.js +65 -1
- package/dist/internal/errors.d.ts +1 -1
- package/dist/internal/errors.d.ts.map +1 -1
- package/dist/internal/errors.js +2 -2
- package/dist/nest-builder.d.ts +2 -0
- package/dist/nest-builder.d.ts.map +1 -0
- package/dist/nest-builder.js +2 -0
- package/dist/nest-vercel-builder.d.ts +2 -0
- package/dist/nest-vercel-builder.d.ts.map +1 -0
- package/dist/nest-vercel-builder.js +2 -0
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +2 -2
- package/dist/runtime.d.ts +2 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +5 -2
- package/docs/ai/chat-session-modeling.mdx +7 -3
- package/docs/ai/defining-tools.mdx +2 -2
- package/docs/ai/index.mdx +30 -25
- package/docs/ai/message-queueing.mdx +10 -10
- package/docs/ai/resumable-streams.mdx +9 -1
- package/docs/api-reference/index.mdx +24 -0
- package/docs/api-reference/meta.json +8 -0
- package/docs/api-reference/vitest/index.mdx +28 -7
- package/docs/api-reference/workflow/create-hook.mdx +79 -0
- package/docs/api-reference/workflow/create-webhook.mdx +1 -0
- package/docs/api-reference/workflow/define-hook.mdx +26 -24
- package/docs/api-reference/workflow/fatal-error.mdx +29 -7
- package/docs/api-reference/workflow/fetch.mdx +8 -4
- package/docs/api-reference/workflow/index.mdx +3 -0
- package/docs/api-reference/workflow/set-attributes.mdx +61 -0
- package/docs/api-reference/workflow/sleep.mdx +1 -1
- package/docs/api-reference/workflow-ai/durable-agent.mdx +7 -45
- package/docs/api-reference/workflow-ai/index.mdx +2 -2
- package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +44 -1
- package/docs/api-reference/workflow-api/get-hook-by-token.mdx +9 -0
- package/docs/api-reference/workflow-api/get-run.mdx +25 -0
- package/docs/api-reference/workflow-api/index.mdx +6 -8
- package/docs/api-reference/workflow-api/resume-hook.mdx +60 -1
- package/docs/api-reference/workflow-api/resume-webhook.mdx +6 -4
- package/docs/api-reference/workflow-api/start.mdx +39 -6
- package/docs/api-reference/workflow-astro/index.mdx +18 -0
- package/docs/api-reference/workflow-astro/meta.json +4 -0
- package/docs/api-reference/workflow-astro/workflow.mdx +45 -0
- package/docs/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
- package/docs/api-reference/workflow-errors/index.mdx +88 -0
- package/docs/api-reference/workflow-errors/meta.json +6 -0
- package/docs/api-reference/workflow-errors/precondition-failed-error.mdx +68 -0
- package/docs/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
- package/docs/api-reference/workflow-errors/workflow-error.mdx +52 -0
- package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +16 -6
- package/docs/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
- package/docs/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
- package/docs/api-reference/workflow-globals.mdx +4 -1
- package/docs/api-reference/workflow-nest/configure-workflow-controller.mdx +33 -0
- package/docs/api-reference/workflow-nest/index.mdx +31 -0
- package/docs/api-reference/workflow-nest/meta.json +9 -0
- package/docs/api-reference/workflow-nest/nest-local-builder.mdx +64 -0
- package/docs/api-reference/workflow-nest/workflow-controller.mdx +40 -0
- package/docs/api-reference/workflow-nest/workflow-module.mdx +74 -0
- package/docs/api-reference/workflow-next/with-workflow.mdx +56 -2
- package/docs/api-reference/workflow-nitro/index.mdx +60 -0
- package/docs/api-reference/workflow-nuxt/index.mdx +48 -0
- package/docs/api-reference/workflow-observability/hydrate-data.mdx +35 -0
- package/docs/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
- package/docs/api-reference/workflow-observability/index.mdx +64 -0
- package/docs/api-reference/workflow-observability/meta.json +11 -0
- package/docs/api-reference/workflow-observability/observability-revivers.mdx +50 -0
- package/docs/api-reference/workflow-observability/parse-class-name.mdx +41 -0
- package/docs/api-reference/workflow-observability/parse-step-name.mdx +40 -0
- package/docs/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
- package/docs/api-reference/workflow-runtime/create-world.mdx +39 -0
- package/docs/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
- package/docs/api-reference/{workflow-api → workflow-runtime}/get-world.mdx +7 -10
- package/docs/api-reference/workflow-runtime/health-check.mdx +50 -0
- package/docs/api-reference/workflow-runtime/index.mdx +43 -0
- package/docs/api-reference/workflow-runtime/meta.json +12 -0
- package/docs/api-reference/workflow-runtime/set-world.mdx +49 -0
- package/docs/api-reference/workflow-runtime/workflow-entrypoint.mdx +42 -0
- package/docs/api-reference/workflow-runtime/world/analytics.mdx +138 -0
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/index.mdx +10 -10
- package/docs/api-reference/workflow-runtime/world/meta.json +4 -0
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/queue.mdx +8 -6
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/storage.mdx +24 -7
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/streams.mdx +2 -2
- package/docs/api-reference/workflow-serde/index.mdx +0 -1
- package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +1 -2
- package/docs/api-reference/workflow-serde/workflow-serialize.mdx +2 -2
- package/docs/api-reference/workflow-sveltekit/index.mdx +18 -0
- package/docs/api-reference/workflow-sveltekit/meta.json +4 -0
- package/docs/api-reference/workflow-sveltekit/workflow-plugin.mdx +42 -0
- package/docs/api-reference/workflow-vite/index.mdx +18 -0
- package/docs/api-reference/workflow-vite/meta.json +4 -0
- package/docs/api-reference/workflow-vite/workflow.mdx +48 -0
- package/docs/changelog/attributes-mvp.mdx +380 -0
- package/docs/changelog/eager-processing.mdx +269 -0
- package/docs/changelog/index.mdx +3 -1
- package/docs/changelog/lazy-event-creation.md +127 -0
- package/docs/changelog/meta.json +9 -1
- package/docs/changelog/resilient-resume.mdx +22 -0
- package/docs/changelog/resilient-start.mdx +31 -283
- package/docs/changelog/step-message-ownership.mdx +360 -0
- package/docs/changelog/turbo-mode.md +87 -0
- package/docs/comparisons/index.mdx +66 -0
- package/docs/comparisons/meta.json +11 -0
- package/docs/comparisons/workflow-sdk-vs-aws-agentcore.mdx +55 -0
- package/docs/comparisons/workflow-sdk-vs-aws-step-functions.mdx +111 -0
- package/docs/comparisons/workflow-sdk-vs-cloudflare-workflows.mdx +71 -0
- package/docs/comparisons/workflow-sdk-vs-inngest.mdx +102 -0
- package/docs/comparisons/workflow-sdk-vs-temporal.mdx +123 -0
- package/docs/comparisons/workflow-sdk-vs-trigger-dev.mdx +103 -0
- package/docs/configuration/build-and-diagnostics.mdx +70 -0
- package/docs/configuration/cli-and-web-ui.mdx +182 -0
- package/docs/configuration/framework-options.mdx +165 -0
- package/docs/configuration/index.mdx +32 -0
- package/docs/configuration/meta.json +12 -0
- package/docs/configuration/runtime-tuning.mdx +281 -0
- package/docs/configuration/worlds.mdx +283 -0
- package/docs/cookbook/advanced/child-workflows.mdx +203 -256
- package/docs/cookbook/advanced/meta.json +1 -1
- package/docs/cookbook/advanced/publishing-libraries.mdx +33 -24
- package/docs/cookbook/advanced/serializable-steps.mdx +12 -4
- package/docs/cookbook/advanced/upgrading-workflows.mdx +199 -0
- package/docs/cookbook/agent-patterns/agent-cancellation.mdx +34 -75
- package/docs/cookbook/agent-patterns/durable-agent.mdx +14 -142
- package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +11 -3
- package/docs/cookbook/common-patterns/batching.mdx +4 -0
- package/docs/cookbook/common-patterns/idempotency.mdx +40 -52
- package/docs/cookbook/common-patterns/rate-limiting.mdx +5 -1
- package/docs/cookbook/common-patterns/saga.mdx +6 -2
- package/docs/cookbook/common-patterns/scheduling.mdx +12 -0
- package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +4 -0
- package/docs/cookbook/common-patterns/timeouts.mdx +6 -1
- package/docs/cookbook/common-patterns/webhooks.mdx +4 -0
- package/docs/cookbook/common-patterns/workflow-composition.mdx +19 -16
- package/docs/cookbook/index.mdx +3 -3
- package/docs/cookbook/integrations/ai-sdk.mdx +64 -28
- package/docs/cookbook/integrations/chat-sdk.mdx +17 -0
- package/docs/cookbook/integrations/sandbox.mdx +17 -0
- package/docs/{deploying/index.mdx → deploying.mdx} +7 -7
- package/docs/errors/abort-signal-timeout-in-workflow.mdx +84 -0
- package/docs/errors/corrupted-event-log.mdx +11 -8
- package/docs/errors/deployment-mismatch.mdx +71 -0
- package/docs/errors/fetch-in-workflow.mdx +4 -0
- package/docs/errors/hook-conflict.mdx +60 -4
- package/docs/errors/index.mdx +1 -35
- package/docs/errors/node-js-module-in-workflow.mdx +4 -0
- package/docs/errors/replay-divergence.mdx +27 -0
- package/docs/errors/runtime-decryption-failed.mdx +77 -0
- package/docs/errors/serialization-failed.mdx +4 -0
- package/docs/errors/start-invalid-workflow-function.mdx +4 -0
- package/docs/errors/step-executed-multiple-times.mdx +23 -0
- package/docs/errors/step-not-registered.mdx +1 -1
- package/docs/errors/timeout-in-workflow.mdx +4 -0
- package/docs/errors/webhook-response-not-sent.mdx +4 -0
- package/docs/foundations/cancellation.mdx +459 -0
- package/docs/foundations/errors-and-retries.mdx +7 -3
- package/docs/foundations/hooks.mdx +29 -0
- package/docs/foundations/idempotency.mdx +243 -11
- package/docs/foundations/index.mdx +1 -23
- package/docs/foundations/meta.json +3 -1
- package/docs/foundations/serialization.mdx +77 -41
- package/docs/foundations/starting-workflows.mdx +79 -2
- package/docs/foundations/streaming.mdx +14 -23
- package/docs/foundations/versioning.mdx +263 -0
- package/docs/getting-started/astro.mdx +12 -2
- package/docs/getting-started/express.mdx +6 -2
- package/docs/getting-started/fastify.mdx +6 -2
- package/docs/getting-started/hono.mdx +6 -2
- package/docs/getting-started/index.mdx +16 -10
- package/docs/getting-started/meta.json +4 -1
- package/docs/getting-started/nestjs.mdx +77 -3
- package/docs/getting-started/next.mdx +12 -6
- package/docs/getting-started/nitro.mdx +28 -2
- package/docs/getting-started/nuxt.mdx +6 -2
- package/docs/getting-started/python.mdx +24 -18
- package/docs/getting-started/react-router/index.mdx +33 -0
- package/docs/getting-started/react-router/meta.json +5 -0
- package/docs/getting-started/react-router/v7.mdx +237 -0
- package/docs/getting-started/react-router/v8.mdx +232 -0
- package/docs/getting-started/sveltekit.mdx +12 -2
- package/docs/getting-started/tanstack-start.mdx +245 -0
- package/docs/getting-started/vite.mdx +6 -2
- package/docs/how-it-works/cancellation.mdx +287 -0
- package/docs/how-it-works/code-transform.mdx +21 -17
- package/docs/how-it-works/encryption.mdx +5 -5
- package/docs/how-it-works/event-sourcing.mdx +17 -9
- package/docs/how-it-works/framework-integrations.mdx +96 -337
- package/docs/how-it-works/meta.json +2 -1
- package/docs/internal/index.mdx +21 -0
- package/docs/internal/meta.json +10 -0
- package/docs/internal/nitro-native-build.mdx +38 -0
- package/docs/internal/nitro-web-ui.mdx +24 -0
- package/docs/internal/serializable-abort-controller.mdx +148 -0
- package/docs/meta.json +2 -2
- package/docs/observability/attributes.mdx +112 -0
- package/docs/observability/index.mdx +20 -1
- package/docs/observability/meta.json +1 -1
- package/docs/observability/tracing.mdx +124 -0
- package/docs/testing/index.mdx +2 -2
- package/package.json +17 -13
- package/docs/api-reference/workflow-api/world/meta.json +0 -4
- package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
- package/docs/cookbook/advanced/distributed-abort-controller.mdx +0 -318
- package/docs/deploying/building-a-world.mdx +0 -251
- package/docs/deploying/meta.json +0 -4
- package/docs/deploying/world/local-world.mdx +0 -84
- package/docs/deploying/world/meta.json +0 -4
- package/docs/deploying/world/postgres-world.mdx +0 -222
- package/docs/deploying/world/vercel-world.mdx +0 -179
- package/docs/migration-guides/index.mdx +0 -34
- package/docs/migration-guides/meta.json +0 -9
- package/docs/migration-guides/migrating-from-aws-step-functions.mdx +0 -363
- package/docs/migration-guides/migrating-from-inngest.mdx +0 -314
- package/docs/migration-guides/migrating-from-temporal.mdx +0 -318
- 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/
|
|
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 "@
|
|
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 `
|
|
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 {
|
|
127
|
-
import type {
|
|
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
|
|
132
|
-
model:
|
|
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
|
|
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 {
|
|
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 "@
|
|
264
|
-
import
|
|
265
|
+
import { openai } from "@ai-sdk/openai";
|
|
266
|
+
import { convertToModelMessages, type UIMessage } from "ai";
|
|
265
267
|
|
|
266
|
-
export async function chatWorkflow(messages:
|
|
268
|
+
export async function chatWorkflow(messages: UIMessage[]) {
|
|
267
269
|
"use workflow"; // [!code highlight]
|
|
268
270
|
|
|
269
|
-
const writable = getWritable<
|
|
271
|
+
const writable = getWritable<ModelCallStreamPart>(); // [!code highlight]
|
|
270
272
|
|
|
271
|
-
const agent = new
|
|
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
|
-
-
|
|
294
|
-
-
|
|
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
|
|
304
|
-
import {
|
|
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, [
|
|
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
|
|
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
|
|
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
|
-
- [`
|
|
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
|
-
`
|
|
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 {
|
|
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 {
|
|
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<
|
|
65
|
+
const writable = getWritable<ModelCallStreamPart>();
|
|
66
66
|
const messageQueue: Array<{ role: "user"; content: string }> = []; // [!code highlight]
|
|
67
67
|
|
|
68
|
-
const agent = new
|
|
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 {
|
|
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 {
|
|
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<
|
|
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
|
|
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
|
-
- [`
|
|
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
|
|
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](/
|
|
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
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return Response.json({
|
|
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
|
-
###
|
|
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
|
|
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
|
|
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
|
+
```
|