workflow 5.0.1 → 5.1.0
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/docs/ai/chat-session-modeling.mdx +8 -8
- package/docs/ai/human-in-the-loop.mdx +2 -2
- package/docs/ai/index.mdx +14 -14
- package/docs/ai/streaming-updates-from-tools.mdx +2 -2
- package/docs/api-reference/workflow-nest/index.mdx +4 -1
- package/docs/api-reference/workflow-nest/is-workflow-request.mdx +58 -0
- package/docs/api-reference/workflow-nest/meta.json +1 -0
- package/docs/api-reference/workflow-nest/workflow-controller.mdx +6 -2
- package/docs/api-reference/workflow-nest/workflow-module.mdx +9 -1
- package/docs/configuration/framework-options.mdx +6 -0
- package/docs/configuration/runtime-tuning.mdx +2 -1
- package/docs/configuration/worlds.mdx +4 -4
- package/docs/cookbook/integrations/ai-sdk.mdx +8 -8
- package/docs/cookbook/integrations/chat-sdk.mdx +8 -8
- package/docs/cookbook/integrations/sandbox.mdx +8 -8
- package/docs/errors/node-js-module-in-workflow.mdx +36 -0
- package/docs/foundations/serialization.mdx +1 -1
- package/docs/foundations/streaming.mdx +1 -1
- package/docs/getting-started/astro.mdx +7 -9
- package/docs/getting-started/express.mdx +4 -10
- package/docs/getting-started/fastify.mdx +4 -9
- package/docs/getting-started/hono.mdx +4 -10
- package/docs/getting-started/index.mdx +2 -2
- package/docs/getting-started/nestjs.mdx +157 -36
- package/docs/getting-started/next.mdx +15 -18
- package/docs/getting-started/nitro.mdx +4 -10
- package/docs/getting-started/nuxt.mdx +4 -10
- package/docs/getting-started/react-router/v7.mdx +6 -22
- package/docs/getting-started/react-router/v8.mdx +6 -22
- package/docs/getting-started/sveltekit.mdx +7 -9
- package/docs/getting-started/tanstack-start.mdx +7 -9
- package/docs/getting-started/vite.mdx +7 -9
- package/docs/how-it-works/code-transform.mdx +12 -8
- package/docs/how-it-works/encryption.mdx +3 -1
- package/docs/whats-new.mdx +1 -1
- package/docs/worlds/building-a-world.mdx +6 -1
- package/docs/worlds/postgres.mdx +12 -32
- package/docs/worlds/upgrading-to-v5.mdx +1 -1
- package/docs/worlds/vercel.mdx +7 -5
- package/package.json +12 -12
|
@@ -267,9 +267,9 @@ Use the multi-turn pattern when:
|
|
|
267
267
|
|
|
268
268
|
The multi-turn pattern also supports messages from system events, external services, and multiple users. Every source resumes the same Hook; the workflow queues those messages and processes them between model turns.
|
|
269
269
|
|
|
270
|
-
<
|
|
270
|
+
<TabsWithChildren tabs={["System event","External service","Multiple users"]}>
|
|
271
271
|
|
|
272
|
-
<
|
|
272
|
+
<TabContent order={1}>
|
|
273
273
|
|
|
274
274
|
Scheduled tasks, background jobs, or database triggers can inject updates into an active conversation:
|
|
275
275
|
|
|
@@ -287,9 +287,9 @@ export async function POST(request: Request) {
|
|
|
287
287
|
}
|
|
288
288
|
```
|
|
289
289
|
|
|
290
|
-
</
|
|
290
|
+
</TabContent>
|
|
291
291
|
|
|
292
|
-
<
|
|
292
|
+
<TabContent order={2}>
|
|
293
293
|
|
|
294
294
|
A third-party webhook can notify the conversation about an external event:
|
|
295
295
|
|
|
@@ -309,9 +309,9 @@ export async function POST(request: Request) {
|
|
|
309
309
|
}
|
|
310
310
|
```
|
|
311
311
|
|
|
312
|
-
</
|
|
312
|
+
</TabContent>
|
|
313
313
|
|
|
314
|
-
<
|
|
314
|
+
<TabContent order={3}>
|
|
315
315
|
|
|
316
316
|
Multiple authenticated users can participate in the same workflow-owned session. Include attribution when resuming the Hook:
|
|
317
317
|
|
|
@@ -337,9 +337,9 @@ export async function POST(
|
|
|
337
337
|
|
|
338
338
|
To preserve structured attribution across refreshes, persist the corresponding `UIMessage` using the application-history approach described above.
|
|
339
339
|
|
|
340
|
-
</
|
|
340
|
+
</TabContent>
|
|
341
341
|
|
|
342
|
-
</
|
|
342
|
+
</TabsWithChildren>
|
|
343
343
|
|
|
344
344
|
## Related documentation
|
|
345
345
|
|
|
@@ -189,7 +189,7 @@ export function BookingApproval({ toolCallId, input, output }: BookingApprovalPr
|
|
|
189
189
|
if (output) {
|
|
190
190
|
return (
|
|
191
191
|
<div className="border rounded-lg p-4">
|
|
192
|
-
<p className="text-sm text-
|
|
192
|
+
<p className="text-sm text-gray-900">{output}</p>
|
|
193
193
|
</div>
|
|
194
194
|
);
|
|
195
195
|
}
|
|
@@ -211,7 +211,7 @@ export function BookingApproval({ toolCallId, input, output }: BookingApprovalPr
|
|
|
211
211
|
<div className="border rounded-lg p-4 space-y-4">
|
|
212
212
|
<div className="space-y-2">
|
|
213
213
|
<p className="font-medium">Approve this booking?</p>
|
|
214
|
-
<div className="text-sm text-
|
|
214
|
+
<div className="text-sm text-gray-900">
|
|
215
215
|
{input && (
|
|
216
216
|
<div className="space-y-2">
|
|
217
217
|
<div>Flight: {input.flightNumber}</div>
|
package/docs/ai/index.mdx
CHANGED
|
@@ -59,9 +59,9 @@ cd workflow-examples/flight-booking-app
|
|
|
59
59
|
|
|
60
60
|
### Configure model access
|
|
61
61
|
|
|
62
|
-
<
|
|
62
|
+
<TabsWithChildren tabs={["AI Gateway","Provider package"]}>
|
|
63
63
|
|
|
64
|
-
<
|
|
64
|
+
<TabContent order={1}>
|
|
65
65
|
|
|
66
66
|
AI SDK uses [Vercel AI Gateway](https://vercel.com/docs/ai-gateway) as its default global provider, so plain `"provider/model"` strings need no provider-specific package. Vercel deployments authenticate with OIDC automatically. For local development, link the project and pull a short-lived OIDC token:
|
|
67
67
|
|
|
@@ -72,9 +72,9 @@ vercel env pull .env.local
|
|
|
72
72
|
|
|
73
73
|
You can alternatively set `AI_GATEWAY_API_KEY` from the [AI Gateway authentication](https://vercel.com/docs/ai-gateway/authentication) page.
|
|
74
74
|
|
|
75
|
-
</
|
|
75
|
+
</TabContent>
|
|
76
76
|
|
|
77
|
-
<
|
|
77
|
+
<TabContent order={2}>
|
|
78
78
|
|
|
79
79
|
`WorkflowAgent` accepts any AI SDK provider. To use OpenAI, install its provider package:
|
|
80
80
|
|
|
@@ -98,9 +98,9 @@ const model = openai("gpt-5.6-sol");
|
|
|
98
98
|
|
|
99
99
|
See the [AI SDK provider guide](https://ai-sdk.dev/providers/ai-sdk-providers) for Anthropic, Google, Amazon Bedrock, and other providers.
|
|
100
100
|
|
|
101
|
-
</
|
|
101
|
+
</TabContent>
|
|
102
102
|
|
|
103
|
-
</
|
|
103
|
+
</TabsWithChildren>
|
|
104
104
|
</Step>
|
|
105
105
|
|
|
106
106
|
<Step>
|
|
@@ -111,9 +111,9 @@ Run the app with `npm run dev` and open [http://localhost:3000](http://localhost
|
|
|
111
111
|
|
|
112
112
|
The following sections break down the core code. You don't need to make changes yet.
|
|
113
113
|
|
|
114
|
-
<
|
|
114
|
+
<TabsWithChildren tabs={["API Route","Tools","Client"]}>
|
|
115
115
|
|
|
116
|
-
<
|
|
116
|
+
<TabContent order={1}>
|
|
117
117
|
|
|
118
118
|
Our API route calls [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.
|
|
119
119
|
|
|
@@ -137,9 +137,9 @@ export async function POST(req: Request) {
|
|
|
137
137
|
}
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
</
|
|
140
|
+
</TabContent>
|
|
141
141
|
|
|
142
|
-
<
|
|
142
|
+
<TabContent order={2}>
|
|
143
143
|
|
|
144
144
|
Our tools are mostly mocked out for the sake of the example. We use AI SDK's `tool` function to define the tool, and pass it to the agent. In your own app, this might be any kind of tool call, like database queries, calls to external services, etc.
|
|
145
145
|
|
|
@@ -160,9 +160,9 @@ async function searchFlights({ from, to, date }: { from: string; to: string; dat
|
|
|
160
160
|
}
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
-
</
|
|
163
|
+
</TabContent>
|
|
164
164
|
|
|
165
|
-
<
|
|
165
|
+
<TabContent order={3}>
|
|
166
166
|
|
|
167
167
|
Our `ChatPage` component contains logic for displaying chat messages, but its core responsibility is managing input and output for the [`useChat` hook](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat#usechat) from AI SDK.
|
|
168
168
|
|
|
@@ -207,9 +207,9 @@ export default function ChatPage() {
|
|
|
207
207
|
}
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
-
</
|
|
210
|
+
</TabContent>
|
|
211
211
|
|
|
212
|
-
</
|
|
212
|
+
</TabsWithChildren>
|
|
213
213
|
|
|
214
214
|
</Step>
|
|
215
215
|
|
|
@@ -120,9 +120,9 @@ Update your chat component to detect and render the custom data parts. Data part
|
|
|
120
120
|
to: string; // [!code highlight]
|
|
121
121
|
}; // [!code highlight]
|
|
122
122
|
return ( // [!code highlight]
|
|
123
|
-
<div key={`${part.id}-${flight.flightNumber}`} className="p-3 bg-
|
|
123
|
+
<div key={`${part.id}-${flight.flightNumber}`} className="p-3 bg-gray-100 rounded-md"> // [!code highlight]
|
|
124
124
|
<div className="font-medium">{flight.airline} - {flight.flightNumber}</div> // [!code highlight]
|
|
125
|
-
<div className="text-
|
|
125
|
+
<div className="text-gray-900">{flight.from} → {flight.to}</div> // [!code highlight]
|
|
126
126
|
</div> // [!code highlight]
|
|
127
127
|
); // [!code highlight]
|
|
128
128
|
} // [!code highlight]
|
|
@@ -10,7 +10,7 @@ related:
|
|
|
10
10
|
NestJS integration for Workflow SDK. The `WorkflowModule` builds the workflow bundles on application startup and registers the controller that serves the workflow runtime routes.
|
|
11
11
|
|
|
12
12
|
<Callout>
|
|
13
|
-
NestJS integration is
|
|
13
|
+
NestJS integration is in beta. Both `@nestjs/platform-express` and `@nestjs/platform-fastify` are supported, and deploying to Vercel goes through [`workflow-nest build --vercel`](/docs/getting-started/nestjs#deploy-to-vercel). The same exports are also available from the `@workflow/nest` package.
|
|
14
14
|
</Callout>
|
|
15
15
|
|
|
16
16
|
## Exports
|
|
@@ -25,6 +25,9 @@ NestJS integration is experimental and not yet supported for deployment to Verce
|
|
|
25
25
|
<Card title="WorkflowController" href="/docs/api-reference/workflow-nest/workflow-controller">
|
|
26
26
|
Controller that serves the workflow runtime routes under `.well-known/workflow/v1`
|
|
27
27
|
</Card>
|
|
28
|
+
<Card title="isWorkflowRequest()" href="/docs/api-reference/workflow-nest/is-workflow-request">
|
|
29
|
+
Recognises a workflow request in a guard, so queue deliveries and webhooks are not rejected by your own auth
|
|
30
|
+
</Card>
|
|
28
31
|
<Card title="configureWorkflowController()" href="/docs/api-reference/workflow-nest/configure-workflow-controller">
|
|
29
32
|
Points `WorkflowController` at the directory containing the generated workflow bundles
|
|
30
33
|
</Card>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: isWorkflowRequest
|
|
3
|
+
description: Recognise a Workflow SDK request inside a NestJS guard or interceptor.
|
|
4
|
+
type: reference
|
|
5
|
+
summary: Let queue deliveries and webhooks past an application guard.
|
|
6
|
+
prerequisites:
|
|
7
|
+
- /docs/getting-started/nestjs
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
Returns `true` when NestJS routed the request an `ExecutionContext` is handling to [`WorkflowController`](/docs/api-reference/workflow-nest/workflow-controller), which serves the Workflow SDK's protocol routes under `.well-known/workflow/v1`. The check is on the selected controller rather than the URL, so an application route that can be reached through a URL containing `.well-known/workflow/v1` (a wildcard such as `files/*path`) is still treated as your route.
|
|
11
|
+
|
|
12
|
+
[`WorkflowController`](/docs/api-reference/workflow-nest/workflow-controller) is a controller inside your application, so a global guard runs for it too. A guard that rejects unauthenticated requests rejects every queue delivery and webhook with `403`, and runs stop making progress with no other symptom. Use this helper to exempt them.
|
|
13
|
+
|
|
14
|
+
The workflow routes authenticate their own callers — queue deliveries are signed and webhook tokens are single-use secrets — so letting them past an application guard exposes nothing.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
{/* @skip-typecheck - NestJS decorators require special TypeScript config */}
|
|
19
|
+
|
|
20
|
+
```typescript title="src/auth.guard.ts" lineNumbers
|
|
21
|
+
import {
|
|
22
|
+
Injectable,
|
|
23
|
+
type CanActivate,
|
|
24
|
+
type ExecutionContext,
|
|
25
|
+
} from "@nestjs/common";
|
|
26
|
+
import { isWorkflowRequest } from "workflow/nest"; // [!code highlight]
|
|
27
|
+
|
|
28
|
+
@Injectable()
|
|
29
|
+
export class AuthGuard implements CanActivate {
|
|
30
|
+
canActivate(context: ExecutionContext) {
|
|
31
|
+
if (isWorkflowRequest(context)) return true; // [!code highlight]
|
|
32
|
+
return this.authenticate(context);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## API signature
|
|
38
|
+
|
|
39
|
+
### Parameters
|
|
40
|
+
|
|
41
|
+
| Parameter | Type | Description |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| `context` | `ExecutionContext` | The execution context NestJS passes to a guard or interceptor. |
|
|
44
|
+
|
|
45
|
+
### Returns
|
|
46
|
+
|
|
47
|
+
`boolean`. `false` for a request to any other route, and for non-HTTP execution contexts (RPC, WebSockets), where there is no workflow route to match.
|
|
48
|
+
|
|
49
|
+
## Related exports
|
|
50
|
+
|
|
51
|
+
| Export | Description |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `isWorkflowRoutePath(path, globalPrefix?)` | Whether a raw URL or path addresses a workflow route, for middleware that has a request rather than an `ExecutionContext`. The match is anchored to `globalPrefix` (default `''`), so pass the prefix given to `app.setGlobalPrefix()` unless it excludes the workflow routes. |
|
|
54
|
+
| `WORKFLOW_ROUTE_PREFIX` | `'.well-known/workflow/v1'`, the path the controller is mounted at. |
|
|
55
|
+
|
|
56
|
+
<Callout type="info">
|
|
57
|
+
Interceptors and exception filters need no exemption. The workflow handlers write through `@Res()`, so the exact status and body the workflow runtime produced reach the caller, which is what the queue and third-party webhook senders key off.
|
|
58
|
+
</Callout>
|
|
@@ -9,9 +9,13 @@ prerequisites:
|
|
|
9
9
|
|
|
10
10
|
NestJS controller that handles the well-known workflow endpoints under `.well-known/workflow/v1`. It dynamically imports the generated workflow bundles and converts between Express/Fastify requests and the Web API `Request`/`Response` objects the workflow runtime expects. Both the Express and Fastify HTTP adapters are supported.
|
|
11
11
|
|
|
12
|
-
The conversion preserves bytes in both directions
|
|
12
|
+
The conversion preserves bytes in both directions. [`WorkflowModule`](/docs/api-reference/workflow-nest/workflow-module) keeps the application's body parsers off these routes, so the request body is normally read straight from the stream; a `req.rawBody` left by `{ rawBody: true }`, or a `Buffer`/string body left by a parser, is used when one is present. Responses are written as bytes, and every `set-cookie` value is kept. See [Request bodies and body parsers](/docs/getting-started/nestjs#request-bodies-and-body-parsers).
|
|
13
13
|
|
|
14
|
-
Handlers take `@Res()
|
|
14
|
+
Handlers take `@Res()` and write the response themselves, so an interceptor or exception filter cannot reshape it. That is deliberate: the queue and third-party webhook senders key off the exact status and body the workflow runtime produces.
|
|
15
|
+
|
|
16
|
+
Guards do run, because NestJS runs them before the handler. A global guard that rejects unauthenticated requests rejects queue deliveries too; see [`isWorkflowRequest`](/docs/api-reference/workflow-nest/is-workflow-request).
|
|
17
|
+
|
|
18
|
+
The controller is registered as `VERSION_NEUTRAL`, so `app.enableVersioning()` does not move these routes away from the paths the SDK generates URLs for. A global prefix does apply, and `WorkflowModule` adopts it automatically.
|
|
15
19
|
|
|
16
20
|
[`WorkflowModule.forRoot()`](/docs/api-reference/workflow-nest/workflow-module) registers this controller automatically. You only register it yourself if you are not using `WorkflowModule`.
|
|
17
21
|
|
|
@@ -86,6 +86,7 @@ Extends [`NestBuilderOptions`](/docs/api-reference/workflow-nest/nest-local-buil
|
|
|
86
86
|
| `basePath` | `string` | adopted from `app.setGlobalPrefix()` | Route prefix the workflow endpoints are served under, applied to generated callback and webhook URLs. Set it when a reverse proxy mounts the app on a sub-path NestJS cannot see. |
|
|
87
87
|
| `manageWorldLifecycle` | `boolean` | `false` | Start the target World's background workers with the app and close them on shutdown. Required for self-hosted Worlds, which otherwise never pick up runs. |
|
|
88
88
|
| `preloadBundles` | `boolean` | `false` when `VERCEL` is set, else `true` | Load the generated bundles during startup instead of on the first request. On Vercel, dedicated functions serve the bundles, so there is nothing to preload. |
|
|
89
|
+
| `bypassBodyParser` | `boolean` | `true` | Keep the application's body parsers away from `.well-known/workflow/v1`, so queue deliveries are not rejected by Express's 100 KB limit and signed webhook bodies stay byte-exact. The application's own routes are untouched. On Fastify, where the body limit is enforced per instance rather than per route, nothing is patched and a limit low enough to reject deliveries is reported at startup instead. |
|
|
89
90
|
| `workingDir` | `string` | `process.cwd()` | Working directory for the NestJS application. |
|
|
90
91
|
| `dirs` | `string[]` | `['src']` | Directories to scan for workflow files. |
|
|
91
92
|
| `outDir` | `string` | `'.nestjs/workflow'` (relative to `workingDir`) | Output directory for generated workflow bundles. |
|
|
@@ -126,9 +127,16 @@ The older `WORKFLOW_OPTIONS` token resolves to the same value and is kept for co
|
|
|
126
127
|
|
|
127
128
|
| Hook | Behaviour |
|
|
128
129
|
| --- | --- |
|
|
129
|
-
| `onModuleInit` |
|
|
130
|
+
| `onModuleInit` | Takes the application's body parsers off the workflow routes (unless `bypassBodyParser` is `false`), reconciles the base path against `app.setGlobalPrefix()`, builds the bundles (or verifies they exist when `skipBuild` is set), optionally starts the World, and preloads the bundles. |
|
|
130
131
|
| `onApplicationShutdown` | Closes the World when `manageWorldLifecycle` is set. Call `app.enableShutdownHooks()` so this runs on a signal. |
|
|
131
132
|
|
|
133
|
+
<Callout type="warn">
|
|
134
|
+
`WorkflowModule` reads NestJS's global prefix and HTTP adapter through the
|
|
135
|
+
injector, which only works while a single copy of `@nestjs/core` is installed.
|
|
136
|
+
With two copies the injection tokens differ, prefix handling and the
|
|
137
|
+
body-parser bypass both stop working, and a warning is logged at startup.
|
|
138
|
+
</Callout>
|
|
139
|
+
|
|
132
140
|
<Callout type="warn">
|
|
133
141
|
Workflows and steps run outside the NestJS injector, so providers cannot be injected into `"use workflow"` or `"use step"` code. See [NestJS dependency injection is not available in workflows and steps](/docs/getting-started/nestjs#nestjs-dependency-injection-is-not-available-in-workflows-and-steps).
|
|
134
142
|
</Callout>
|
|
@@ -126,6 +126,12 @@ Configure Workflow through `WorkflowModule.forRoot()`.
|
|
|
126
126
|
- Default: `false`
|
|
127
127
|
- Skips bundle generation when bundles are already pre-built.
|
|
128
128
|
|
|
129
|
+
### `bypassBodyParser`
|
|
130
|
+
|
|
131
|
+
- Environment override: none
|
|
132
|
+
- Default: `true`
|
|
133
|
+
- Keeps the application's body parsers away from `.well-known/workflow/v1`, so queue deliveries are not rejected by Express's 100 KB limit and signed webhook bodies stay byte-exact. Other routes are untouched. On Fastify, where the body limit is enforced per instance rather than per route, nothing is patched and a limit low enough to reject deliveries is reported at startup instead.
|
|
134
|
+
|
|
129
135
|
## Astro
|
|
130
136
|
|
|
131
137
|
### `sourcemap`
|
|
@@ -284,6 +284,7 @@ What a snapshot contains, and how it is protected:
|
|
|
284
284
|
- Default: automatic
|
|
285
285
|
- Forces the write-side codec to `zstd` or `gzip`.
|
|
286
286
|
- Invalid values are ignored. Automatic mode prefers `zstd` when the current Node.js runtime supports it, otherwise it falls back to `gzip`.
|
|
287
|
+
- Writes for a run on another deployment (for example `resumeHook()` reaching a run that started before the project moved to a newer Node.js version) use `zstd` only when that run's deployment runs Node.js 22.15+ (or 23.8+), and `gzip` otherwise, since older Node.js versions cannot decode `zstd`. Runs created before `@workflow/core` recorded the Node.js version always receive `gzip`. Setting this to `zstd` does not override that check.
|
|
287
288
|
|
|
288
289
|
### `WORKFLOW_TRACE_MODE`
|
|
289
290
|
|
|
@@ -315,7 +316,7 @@ What a snapshot contains, and how it is protected:
|
|
|
315
316
|
|
|
316
317
|
Node's own modules do less than the client they replace, so enabling this drops the per-call-site tuning the Worlds configure:
|
|
317
318
|
|
|
318
|
-
- Event-log requests lose HTTP/2, so concurrent reads and writes no longer share one connection, and the enlarged HTTP/2 receive windows no longer apply. This is the largest difference, and it slows down replays that read a big event log. It does not apply to event writes on the
|
|
319
|
+
- Event-log requests lose HTTP/2, so concurrent reads and writes no longer share one connection, and the enlarged HTTP/2 receive windows no longer apply. This is the largest difference, and it slows down replays that read a big event log. It does not apply to event writes on the [WebSocket events transport](/docs/configuration/worlds#workflow_events_transport), which is the default and takes neither transport.
|
|
319
320
|
- Requests lose their transport-level retry. Failures still surface to the layers above, which retry event writes and redeliver queue messages, so nothing is silently dropped, but a failure that a same-connection retry would have hidden now costs a full redelivery.
|
|
320
321
|
- Stream close loses its retry of retriable server errors. A transient failure at close can leave a stream marked closing until the run expires, where it would previously have resolved on the retry.
|
|
321
322
|
|
|
@@ -317,8 +317,8 @@ When enabled (the default), a suspension's eager `step_created` and `wait_create
|
|
|
317
317
|
|
|
318
318
|
- Factory option: none
|
|
319
319
|
- CLI flag: none
|
|
320
|
-
- Default: `
|
|
321
|
-
-
|
|
320
|
+
- Default: `ws`
|
|
321
|
+
- Ships workflow run events to the Vercel World over a WebSocket instead of one HTTP request each. Set to exactly `http` to opt out; any other value, including unset or empty, uses the WebSocket.
|
|
322
322
|
- Ignored when the World is configured with `projectConfig` and routes through the `api-workflow` proxy: that endpoint is an HTTP-only REST gateway and does not forward a WebSocket upgrade, so events stay on HTTP.
|
|
323
323
|
|
|
324
324
|
### `WORKFLOW_EVENTS_TRANSPORT_WS_OVERRIDE_WORKFLOWS`
|
|
@@ -326,8 +326,8 @@ When enabled (the default), a suspension's eager `step_created` and `wait_create
|
|
|
326
326
|
- Factory option: none
|
|
327
327
|
- CLI flag: none
|
|
328
328
|
- Default: none
|
|
329
|
-
- Comma-separated workflows whose runs use the WebSocket events transport even when `WORKFLOW_EVENTS_TRANSPORT` is `http
|
|
330
|
-
- Has no effect
|
|
329
|
+
- Comma-separated workflows whose runs use the WebSocket events transport even when `WORKFLOW_EVENTS_TRANSPORT` is `http`. Each entry is a function name (`processOrder`) or a full workflow name (`workflow//./src/workflows/order//processOrder`), matched exactly and case-sensitively.
|
|
330
|
+
- Has no effect unless `WORKFLOW_EVENTS_TRANSPORT=http`, since every workflow already uses the WebSocket by default.
|
|
331
331
|
- See [`WORKFLOW_EVENTS_TRANSPORT_WS_OVERRIDE_WORKFLOWS`](/worlds/vercel#workflow_events_transport_ws_override_workflows) for details.
|
|
332
332
|
|
|
333
333
|
### `WORKFLOW_WS_MAX_MESSAGE_BYTES`
|
|
@@ -41,9 +41,9 @@ One workflow run represents one full conversation. The workflow suspends between
|
|
|
41
41
|
Because the conversation is one workflow run, it stays on the deployment that started it. If each turn should run on the latest deployment while preserving selected state or streams, see [Versioning](/docs/foundations/versioning) for the child-run continuation pattern.
|
|
42
42
|
</Callout>
|
|
43
43
|
|
|
44
|
-
<
|
|
44
|
+
<TabsWithChildren tabs={["Workflow","API Route","Client"]}>
|
|
45
45
|
|
|
46
|
-
<
|
|
46
|
+
<TabContent order={1}>
|
|
47
47
|
|
|
48
48
|
```typescript title="workflows/support.ts" lineNumbers
|
|
49
49
|
import { streamText, stepCountIs } from "ai";
|
|
@@ -129,9 +129,9 @@ export async function supportWorkflow(initialMessages: ModelMessage[]) {
|
|
|
129
129
|
}
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
</
|
|
132
|
+
</TabContent>
|
|
133
133
|
|
|
134
|
-
<
|
|
134
|
+
<TabContent order={2}>
|
|
135
135
|
|
|
136
136
|
One endpoint handles first turn, follow-ups, and the `/done` exit. The client sends `runId` in the body to distinguish first vs follow-up.
|
|
137
137
|
|
|
@@ -246,9 +246,9 @@ export async function POST(req: Request) {
|
|
|
246
246
|
}
|
|
247
247
|
```
|
|
248
248
|
|
|
249
|
-
</
|
|
249
|
+
</TabContent>
|
|
250
250
|
|
|
251
|
-
<
|
|
251
|
+
<TabContent order={3}>
|
|
252
252
|
|
|
253
253
|
Store the `runId` in a ref and pass it in the body of every follow-up. `WorkflowChatTransport` forwards it for you.
|
|
254
254
|
|
|
@@ -299,9 +299,9 @@ export function SupportChat() {
|
|
|
299
299
|
}
|
|
300
300
|
```
|
|
301
301
|
|
|
302
|
-
</
|
|
302
|
+
</TabContent>
|
|
303
303
|
|
|
304
|
-
</
|
|
304
|
+
</TabsWithChildren>
|
|
305
305
|
|
|
306
306
|
## How it works
|
|
307
307
|
|
|
@@ -66,9 +66,9 @@ Because the session *is* a workflow run, its history is recoverable from the eve
|
|
|
66
66
|
|
|
67
67
|
This pattern uses three files. The bot definition is separate from the workflow so adapter packages stay out of the workflow sandbox.
|
|
68
68
|
|
|
69
|
-
<
|
|
69
|
+
<TabsWithChildren tabs={["Bot Setup","Workflow","Event Handlers"]}>
|
|
70
70
|
|
|
71
|
-
<
|
|
71
|
+
<TabContent order={1}>
|
|
72
72
|
|
|
73
73
|
Register the `Chat` instance as a singleton so step functions can dynamically import it and resolve adapters + state:
|
|
74
74
|
|
|
@@ -95,9 +95,9 @@ export const bot = new Chat<typeof adapters, ThreadState>({
|
|
|
95
95
|
|
|
96
96
|
`registerSingleton()` is important: Chat SDK re-hydrates `Thread` objects inside step functions, and it needs a registered singleton to resolve adapters and state for those rehydrated instances.
|
|
97
97
|
|
|
98
|
-
</
|
|
98
|
+
</TabContent>
|
|
99
99
|
|
|
100
|
-
<
|
|
100
|
+
<TabContent order={2}>
|
|
101
101
|
|
|
102
102
|
The workflow is a plain loop over a hook. It receives the serialized thread + first message from the handler, revives them via Chat SDK's standalone `reviver`, and every platform-side effect goes inside a `"use step"` helper:
|
|
103
103
|
|
|
@@ -174,9 +174,9 @@ export type ChatTurnPayload = {
|
|
|
174
174
|
};
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
</
|
|
177
|
+
</TabContent>
|
|
178
178
|
|
|
179
|
-
<
|
|
179
|
+
<TabContent order={3}>
|
|
180
180
|
|
|
181
181
|
Handlers live outside the workflow file so adapter dependencies don't leak in. They decide whether to start a new workflow or resume an existing one, then store the `runId` in thread state:
|
|
182
182
|
|
|
@@ -256,9 +256,9 @@ export async function POST(
|
|
|
256
256
|
}
|
|
257
257
|
```
|
|
258
258
|
|
|
259
|
-
</
|
|
259
|
+
</TabContent>
|
|
260
260
|
|
|
261
|
-
</
|
|
261
|
+
</TabsWithChildren>
|
|
262
262
|
|
|
263
263
|
## How it works
|
|
264
264
|
|
|
@@ -88,9 +88,9 @@ When the timer wins:
|
|
|
88
88
|
|
|
89
89
|
The only way out is an explicit `/destroy` command.
|
|
90
90
|
|
|
91
|
-
<
|
|
91
|
+
<TabsWithChildren tabs={["Workflow","API Routes","Client"]}>
|
|
92
92
|
|
|
93
|
-
<
|
|
93
|
+
<TabContent order={1}>
|
|
94
94
|
|
|
95
95
|
```typescript title="workflows/sandbox-session.ts" lineNumbers
|
|
96
96
|
import { defineHook, sleep, getWritable, getWorkflowMetadata } from "workflow";
|
|
@@ -298,9 +298,9 @@ export async function sandboxSessionWorkflow() {
|
|
|
298
298
|
}
|
|
299
299
|
```
|
|
300
300
|
|
|
301
|
-
</
|
|
301
|
+
</TabContent>
|
|
302
302
|
|
|
303
|
-
<
|
|
303
|
+
<TabContent order={2}>
|
|
304
304
|
|
|
305
305
|
Two endpoints manage the session. `/start` accepts an optional `{ runId }`: if the run still exists, it replays the event log from index 0 so a returning client fully rehydrates. `/command` resumes the hook and returns immediately; command output lands on the `/start` stream.
|
|
306
306
|
|
|
@@ -379,9 +379,9 @@ export async function POST(req: Request) {
|
|
|
379
379
|
}
|
|
380
380
|
```
|
|
381
381
|
|
|
382
|
-
</
|
|
382
|
+
</TabContent>
|
|
383
383
|
|
|
384
|
-
<
|
|
384
|
+
<TabContent order={3}>
|
|
385
385
|
|
|
386
386
|
On mount, reconnect to the existing run if `localStorage` contains a `runId`. Otherwise, start a new run. Send commands to `/command` with POST requests. Output arrives on the `/start` stream.
|
|
387
387
|
|
|
@@ -472,9 +472,9 @@ export function SandboxRunner() {
|
|
|
472
472
|
}
|
|
473
473
|
```
|
|
474
474
|
|
|
475
|
-
</
|
|
475
|
+
</TabContent>
|
|
476
476
|
|
|
477
|
-
</
|
|
477
|
+
</TabsWithChildren>
|
|
478
478
|
|
|
479
479
|
## How it works
|
|
480
480
|
|
|
@@ -27,6 +27,42 @@ Workflow functions run in a sandboxed environment without full Node.js runtime a
|
|
|
27
27
|
|
|
28
28
|
Node.js modules have side effects and non-deterministic behavior that could break workflow replay guarantees.
|
|
29
29
|
|
|
30
|
+
## Transitive dependencies and dynamic `require()`
|
|
31
|
+
|
|
32
|
+
The same error is reported for a module the workflow bundle could not inline, even when your own code never imports it directly:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
Workflow bundle cannot run in the workflow sandbox.
|
|
36
|
+
|
|
37
|
+
Imports left external (1):
|
|
38
|
+
• "node:fs"
|
|
39
|
+
imported by node_modules/leaky-pkg/index.js
|
|
40
|
+
via app/workflows/order.ts → node_modules/wrapper-pkg/index.js → node_modules/leaky-pkg/index.js
|
|
41
|
+
|
|
42
|
+
Unresolved require() calls (1):
|
|
43
|
+
• dynamic require() in node_modules/lazy-pkg/index.js (bundle 412:31)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The workflow sandbox has no `require`, so both would throw `ReferenceError: require is not defined` when the bundle loads. Follow the import chain to the first file you own and move that work into a step function, or replace a dynamic `require()` with a static import so the bundler can inline it.
|
|
47
|
+
|
|
48
|
+
A `require()` wrapped in `try`/`catch`, or behind a `typeof require` check, is not reported — that is how packages probe for an optional dependency or for a CommonJS environment, and in the sandbox the error is caught or the call never runs:
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
try {
|
|
52
|
+
loadOptional(require("@emotion/is-prop-valid").default);
|
|
53
|
+
} catch {
|
|
54
|
+
// optional dependency, fall back
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (typeof require !== "undefined") {
|
|
58
|
+
crypto = require("crypto");
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A `typeof require` check only excuses the branch it skips when `require` is undefined, so `typeof require === "undefined" ? require(x) : y` is still reported. A `try` only counts when it has a `catch`. The checks read the code's structure without running it, so a `catch` that rethrows the error is still treated as a guard, and the bundle fails at runtime if that code is reached.
|
|
63
|
+
|
|
64
|
+
As a last resort — for example a `require()` that can never run — set `WORKFLOW_ALLOW_UNSAFE_FLOW_BUNDLE=1` to downgrade the build failure to a warning. The bundle still fails at runtime if the code is reached.
|
|
65
|
+
|
|
30
66
|
## Quick fix
|
|
31
67
|
|
|
32
68
|
Move any code using Node.js modules to a step function. Step functions have full Node.js runtime access.
|
|
@@ -36,7 +36,7 @@ The following types can be serialized and passed through workflow functions:
|
|
|
36
36
|
- `BigInt64Array`, `BigUint64Array`
|
|
37
37
|
- `DataView`
|
|
38
38
|
- `Date`
|
|
39
|
-
- `Float32Array`, `Float64Array`
|
|
39
|
+
- `Float16Array`, `Float32Array`, `Float64Array`
|
|
40
40
|
- `Int8Array`, `Int16Array`, `Int32Array`
|
|
41
41
|
- `Map<Serializable, Serializable>`
|
|
42
42
|
- `RegExp`
|
|
@@ -337,7 +337,7 @@ async function runTurn(holderRunId: string, turn: number) {
|
|
|
337
337
|
Pass `{ namespace: "name" }` to target a [namespaced stream](#namespaced-streams). The writable can also be forwarded through `start()` and into steps.
|
|
338
338
|
|
|
339
339
|
<Callout type="warn">
|
|
340
|
-
Contributors should call `releaseLock()`, which flushes pending writes. Calling `close()` closes the shared stream for every writer.
|
|
340
|
+
Contributors should call `releaseLock()`, which flushes pending writes. Calling `close()` closes the shared stream for every writer. Calling `abort()` keeps the chunks already written and leaves the shared stream open.
|
|
341
341
|
</Callout>
|
|
342
342
|
|
|
343
343
|
The API grants append access, not read access or additional authorization. The owning run controls the stream's lifecycle, and writes to an unknown run fail.
|
|
@@ -61,12 +61,12 @@ export default defineConfig({
|
|
|
61
61
|
| --- | --- | --- | --- |
|
|
62
62
|
| `sourcemap` | `boolean \| 'inline' \| 'linked' \| 'external' \| 'both'` | `'inline'` (dev) / `false` (prod) | Controls source maps on generated workflow bundles. Accepts the same values as esbuild's `sourcemap` option. Defaults to `'inline'` in development and `false` in production (smaller function bundles, which helps stay under the Vercel 250 MB function size limit). Set it explicitly, or use the `WORKFLOW_SOURCEMAP` environment variable, to override in either environment. |
|
|
63
63
|
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
<Details>
|
|
65
|
+
<Summary className="[&_h3]:my-0">
|
|
66
|
+
|
|
67
|
+
### Set up IntelliSense for TypeScript (optional)
|
|
68
|
+
|
|
69
|
+
</Summary>
|
|
70
70
|
|
|
71
71
|
To enable helpful hints in your IDE, set up the workflow plugin in `tsconfig.json`:
|
|
72
72
|
|
|
@@ -83,9 +83,7 @@ To enable helpful hints in your IDE, set up the workflow plugin in `tsconfig.jso
|
|
|
83
83
|
}
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
</AccordionItem>
|
|
88
|
-
</Accordion>
|
|
86
|
+
</Details>
|
|
89
87
|
|
|
90
88
|
</Step>
|
|
91
89
|
|
|
@@ -72,12 +72,9 @@ export default defineNitroConfig({
|
|
|
72
72
|
});
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
Setup IntelliSense for TypeScript (Optional)
|
|
79
|
-
</AccordionTrigger>
|
|
80
|
-
<AccordionContent className="[&_p]:my-2">
|
|
75
|
+
<Details>
|
|
76
|
+
<Summary>Setup IntelliSense for TypeScript (Optional)</Summary>
|
|
77
|
+
|
|
81
78
|
To enable helpful hints in your IDE, setup the workflow plugin in `tsconfig.json`:
|
|
82
79
|
|
|
83
80
|
```json title="tsconfig.json" lineNumbers
|
|
@@ -93,10 +90,7 @@ To enable helpful hints in your IDE, setup the workflow plugin in `tsconfig.json
|
|
|
93
90
|
}
|
|
94
91
|
```
|
|
95
92
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
</AccordionItem>
|
|
99
|
-
</Accordion>
|
|
93
|
+
</Details>
|
|
100
94
|
|
|
101
95
|
### Update `package.json`
|
|
102
96
|
|