workflow 4.5.0 → 4.6.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/resumable-streams.mdx +4 -0
- package/docs/api-reference/workflow/create-hook.mdx +2 -2
- package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +39 -0
- package/docs/api-reference/workflow-api/start.mdx +2 -0
- package/docs/api-reference/workflow-next/with-workflow.mdx +26 -4
- package/docs/deploying/world/postgres-world.mdx +33 -1
- package/docs/foundations/hooks.mdx +1 -1
- package/docs/observability/index.mdx +13 -0
- package/docs/v4/api-reference/workflow-nitro/index.mdx +1 -0
- package/docs/v4/api-reference/workflow-nuxt/index.mdx +1 -0
- package/docs/v5/api-reference/workflow-nitro/index.mdx +1 -0
- package/docs/v5/api-reference/workflow-nuxt/index.mdx +1 -0
- package/package.json +10 -10
|
@@ -194,6 +194,10 @@ This avoids replaying potentially thousands of chunks and lets the UI render fas
|
|
|
194
194
|
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
195
|
</Callout>
|
|
196
196
|
|
|
197
|
+
### Mid-part resumes
|
|
198
|
+
|
|
199
|
+
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.
|
|
200
|
+
|
|
197
201
|
## Related Documentation
|
|
198
202
|
|
|
199
203
|
- [`WorkflowChatTransport` API Reference](/docs/api-reference/workflow-ai/workflow-chat-transport) - Full configuration options
|
|
@@ -65,7 +65,7 @@ export default Hook;`}
|
|
|
65
65
|
|
|
66
66
|
The returned `Hook` object also implements `AsyncIterable<T>`, which allows you to iterate over incoming payloads using `for await...of` syntax.
|
|
67
67
|
|
|
68
|
-
Use `hook.getConflict()` to check whether the hook token is already claimed by another active hook, 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 `{ runId }` identifying the conflicting run if another active hook already owns the same token.
|
|
68
|
+
Use `hook.getConflict()` (available starting in `workflow@4.5.0`) to check whether the hook token is already claimed by another active hook, 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 `{ runId }` identifying the conflicting run if another active hook already owns the same token.
|
|
69
69
|
|
|
70
70
|
## Examples
|
|
71
71
|
|
|
@@ -116,7 +116,7 @@ export async function slackBotWorkflow(channelId: string) {
|
|
|
116
116
|
|
|
117
117
|
### Detecting Token Conflicts
|
|
118
118
|
|
|
119
|
-
Use `hook.getConflict()` when the workflow needs to claim a hook token before doing other work, but does not need a payload yet:
|
|
119
|
+
Use `hook.getConflict()` (available starting in `workflow@4.5.0`) when the workflow needs to claim a hook token before doing other work, but does not need a payload yet:
|
|
120
120
|
|
|
121
121
|
```typescript lineNumbers
|
|
122
122
|
import { createHook } from "workflow";
|
|
@@ -254,6 +254,45 @@ export default function ChatWithCustomConfig() {
|
|
|
254
254
|
}
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
+
## Mid-part resumes
|
|
258
|
+
|
|
259
|
+
A workflow stream is a flat sequence of chunks, but the AI SDK's UI protocol groups chunks into logical parts: a `text-start` opens a text part that subsequent `text-delta`s extend and a `text-end` closes, and the same shape applies to `reasoning-*` and `tool-input-*`. The AI SDK client enforces that grammar — a `reasoning-delta` whose `reasoning-start` was never seen throws and breaks the chat.
|
|
260
|
+
|
|
261
|
+
A non-zero `startIndex` (in particular a negative `initialStartIndex`) resolves to a chunk offset with no awareness of those part boundaries, so it can land in the middle of an open part. When that happens, `WorkflowChatTransport` will **drop chunks that reference a part it didn't see a start for** and log a one-time warning. The chat keeps working, but any partial part overlapping the resume cursor is discarded. Tool calls are an exception: `tool-input-available` / `tool-input-error` chunks are self-contained (they carry the full input), so a tool call is recovered as soon as one of those chunks appears in the resumed window — only its streamed input deltas are lost.
|
|
262
|
+
|
|
263
|
+
To preserve those partial parts, rewind to a step boundary on the server before returning the readable. `start-step` / `finish-step` chunks are the natural seams — no UI part is ever open across them. Sketch:
|
|
264
|
+
|
|
265
|
+
{/*@skip-typecheck: incomplete code sample*/}
|
|
266
|
+
|
|
267
|
+
```typescript title="app/api/chat/[id]/stream/route.ts"
|
|
268
|
+
const run = getRun(id);
|
|
269
|
+
const tailIndex = await run.getReadable().getTailIndex();
|
|
270
|
+
|
|
271
|
+
let resolved = startIndex < 0
|
|
272
|
+
? Math.max(0, tailIndex + 1 + startIndex)
|
|
273
|
+
: startIndex;
|
|
274
|
+
|
|
275
|
+
if (startIndex !== 0) {
|
|
276
|
+
// Walk back from `resolved` to the most recent start-step (or chunk 0),
|
|
277
|
+
// capping the lookback so a single huge step can't trigger an unbounded scan.
|
|
278
|
+
const LOOKBACK = 200;
|
|
279
|
+
const probe = run.getReadable({ startIndex: Math.max(0, resolved - LOOKBACK) });
|
|
280
|
+
let i = Math.max(0, resolved - LOOKBACK);
|
|
281
|
+
let lastBoundary = i;
|
|
282
|
+
for await (const chunk of probe as unknown as AsyncIterable<{ type: string }>) {
|
|
283
|
+
if (i >= resolved) break;
|
|
284
|
+
if (chunk.type === "start-step") lastBoundary = i;
|
|
285
|
+
i++;
|
|
286
|
+
}
|
|
287
|
+
resolved = lastBoundary;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return createUIMessageStreamResponse({
|
|
291
|
+
stream: run.getReadable({ startIndex: resolved }),
|
|
292
|
+
headers: { "x-workflow-stream-tail-index": String(tailIndex) },
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
257
296
|
## See Also
|
|
258
297
|
|
|
259
298
|
- [DurableAgent](/docs/api-reference/workflow-ai/durable-agent) - Building durable AI agents within workflows
|
|
@@ -97,6 +97,8 @@ const run = await start(myWorkflow, ["arg1", "arg2"], { // [!code highlight]
|
|
|
97
97
|
|
|
98
98
|
<Callout type="info">
|
|
99
99
|
The `deploymentId` option is currently a Vercel-specific feature. Other Worlds may implement this option differently to match their own deployment runtimes, and the World spec may rename it from `deploymentId` to `version` in a future SDK version. On Vercel, `"latest"` resolves to the most recent deployment matching your current environment — the same production target for production deployments, or the same git branch for preview deployments.
|
|
100
|
+
|
|
101
|
+
In Worlds without atomic, immutable deployments (such as local development or self-hosted Postgres), there is no notion of multiple deployments to resolve between, so `deploymentId: "latest"` has no effect: the SDK logs a warning and the run targets the current deployment. This means a workflow that opts into `"latest"` on Vercel still runs unchanged in local development.
|
|
100
102
|
</Callout>
|
|
101
103
|
|
|
102
104
|
<Callout type="warn">
|
|
@@ -31,11 +31,35 @@ export default withWorkflow(nextConfig, workflowConfig); // [!code highlight]
|
|
|
31
31
|
If a package in `serverExternalPackages` contains workflow code (`"use step"`,
|
|
32
32
|
`"use workflow"`, or serialization classes), `withWorkflow()` automatically
|
|
33
33
|
removes it from `serverExternalPackages` for the current build and prints a
|
|
34
|
-
warning.
|
|
35
|
-
|
|
34
|
+
warning. Workflow still compiles the package so its directives are transformed.
|
|
35
|
+
Remove that package from `serverExternalPackages` in your
|
|
36
36
|
`next.config` to silence the warning.
|
|
37
37
|
</Callout>
|
|
38
38
|
|
|
39
|
+
### Workflow Discovery in Next.js
|
|
40
|
+
|
|
41
|
+
`withWorkflow()` discovers workflows by scanning your Next.js entrypoints — App
|
|
42
|
+
Router `route`, `page`, and `layout` files (under `app/` or `src/app/`) and any
|
|
43
|
+
file under `pages/` or `src/pages/` — for `start()` calls imported from
|
|
44
|
+
`workflow/api`. The workflow and step files themselves can live anywhere (for
|
|
45
|
+
example `src/workflows/`); they are discovered transitively through imports, as
|
|
46
|
+
long as a `start()` call in an entrypoint statically reaches them.
|
|
47
|
+
|
|
48
|
+
<Callout type="info">
|
|
49
|
+
Call `start()` from server-side entrypoints, including Route Handlers and Server
|
|
50
|
+
Actions. Don't call workflow functions directly — that bypasses the workflow
|
|
51
|
+
runtime.
|
|
52
|
+
</Callout>
|
|
53
|
+
|
|
54
|
+
### Next.js Server Actions and `"use server"`
|
|
55
|
+
|
|
56
|
+
Don't put a top-level `"use server"` directive in modules imported by workflow
|
|
57
|
+
or step functions. Workflow transformation wraps imported modules in synchronous
|
|
58
|
+
initializers, and Next.js rejects a `"use server"` directive inside that wrapper
|
|
59
|
+
with errors like `Server Actions must be async functions`. Keep `"use server"`
|
|
60
|
+
on the files that define your Server Actions, and move shared logic into
|
|
61
|
+
separate modules that don't carry the directive.
|
|
62
|
+
|
|
39
63
|
### Monorepos and Workspace Imports
|
|
40
64
|
|
|
41
65
|
By default, Next.js detects the correct workspace root automatically. If your Next.js app lives in a subdirectory such as `apps/web` and workspace resolution is not working correctly, you can set `outputFileTracingRoot` as a workaround:
|
|
@@ -68,7 +92,6 @@ const nextConfig: NextConfig = {};
|
|
|
68
92
|
|
|
69
93
|
export default withWorkflow(nextConfig, {
|
|
70
94
|
workflows: {
|
|
71
|
-
lazyDiscovery: true,
|
|
72
95
|
local: {
|
|
73
96
|
port: 4000,
|
|
74
97
|
},
|
|
@@ -78,7 +101,6 @@ export default withWorkflow(nextConfig, {
|
|
|
78
101
|
|
|
79
102
|
| Option | Type | Default | Description |
|
|
80
103
|
| --- | --- | --- | --- |
|
|
81
|
-
| `workflows.lazyDiscovery` | `boolean` | `false` | When `true`, defers workflow discovery until files are requested instead of scanning eagerly at startup. Useful for large projects where startup time matters. |
|
|
82
104
|
| `workflows.local.port` | `number` | — | Overrides the `PORT` environment variable for local development. Has no effect when deployed to Vercel. |
|
|
83
105
|
|
|
84
106
|
<Callout type="info">
|
|
@@ -31,10 +31,42 @@ WORKFLOW_POSTGRES_URL="postgres://user:password@host:5432/database"
|
|
|
31
31
|
|
|
32
32
|
Run the migration script to create the necessary tables in your database. Ensure `WORKFLOW_POSTGRES_URL` is set when running this command:
|
|
33
33
|
|
|
34
|
+
<Tabs items={["npm", "pnpm", "Yarn", "Bun"]}>
|
|
35
|
+
|
|
36
|
+
<Tab value="npm">
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx --package=@workflow/world-postgres bootstrap
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
</Tab>
|
|
43
|
+
|
|
44
|
+
<Tab value="pnpm">
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pnpm dlx --package @workflow/world-postgres bootstrap
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
</Tab>
|
|
51
|
+
|
|
52
|
+
<Tab value="Yarn">
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
yarn dlx --package @workflow/world-postgres bootstrap
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
</Tab>
|
|
59
|
+
|
|
60
|
+
<Tab value="Bun">
|
|
61
|
+
|
|
34
62
|
```bash
|
|
35
|
-
|
|
63
|
+
bunx --package @workflow/world-postgres bootstrap
|
|
36
64
|
```
|
|
37
65
|
|
|
66
|
+
</Tab>
|
|
67
|
+
|
|
68
|
+
</Tabs>
|
|
69
|
+
|
|
38
70
|
<Callout type="info">
|
|
39
71
|
The migration is idempotent and can safely be run as a post-deployment lifecycle script.
|
|
40
72
|
</Callout>
|
|
@@ -87,7 +87,7 @@ The key points:
|
|
|
87
87
|
|
|
88
88
|
### Checking for Token Conflicts
|
|
89
89
|
|
|
90
|
-
Sometimes you need to know that a hook token has been claimed, but you do not want to wait for external data yet. Await `hook.getConflict()` for that:
|
|
90
|
+
Sometimes you need to know that a hook token has been claimed, but you do not want to wait for external data yet. Await `hook.getConflict()` (available starting in `workflow@4.5.0`) for that:
|
|
91
91
|
|
|
92
92
|
```typescript lineNumbers
|
|
93
93
|
import { createHook } from "workflow";
|
|
@@ -42,6 +42,19 @@ npx workflow inspect runs --web
|
|
|
42
42
|
|
|
43
43
|

|
|
44
44
|
|
|
45
|
+
To share a link to a specific run without opening a browser, use the `--url`
|
|
46
|
+
flag. It prints the dashboard deep link to stdout and exits (no browser, no
|
|
47
|
+
local server) — useful for scripts, PR comments, or automation. Add `--json` to
|
|
48
|
+
get `{ "url": "..." }`.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Print the deep-link URL for a run (no browser, no server)
|
|
52
|
+
npx workflow inspect run <run_id> --url
|
|
53
|
+
|
|
54
|
+
# Vercel runs: add the backend (and --env preview for preview deployments)
|
|
55
|
+
npx workflow inspect run <run_id> --backend vercel --url
|
|
56
|
+
```
|
|
57
|
+
|
|
45
58
|
## Backends
|
|
46
59
|
|
|
47
60
|
The Workflow SDK CLI can inspect data from any [World](/docs/deploying). By default, it inspects data in your local development environment. For example, if you are using Next.js to develop workflows locally, the
|
|
@@ -27,6 +27,7 @@ When enabled, the module:
|
|
|
27
27
|
- Registers the workflow runtime routes under `/.well-known/workflow/v1/`.
|
|
28
28
|
- Serves a redirect to the local observability dashboard at `/_workflow` in development.
|
|
29
29
|
- Configures Vercel function rules (queue triggers and `maxDuration`) for the workflow routes when deploying to Vercel.
|
|
30
|
+
- Uses Nitro's `workspaceDir` as the workflow project root so monorepo apps can import sibling workspace packages without extra workflow config.
|
|
30
31
|
|
|
31
32
|
## Module Options
|
|
32
33
|
|
|
@@ -25,6 +25,7 @@ When enabled, the module:
|
|
|
25
25
|
- Registers the [`workflow/nitro`](/docs/api-reference/workflow-nitro) module on Nuxt's Nitro server, which transforms `"use workflow"` and `"use step"` directives, builds the workflow bundles, and registers the workflow runtime routes under `/.well-known/workflow/v1/`.
|
|
26
26
|
- Configures Vite to bundle (rather than externalize) the Workflow SDK packages in SSR mode so workflow code is transformed correctly.
|
|
27
27
|
- Enables the `workflow` TypeScript plugin by default for IDE IntelliSense.
|
|
28
|
+
- Uses Nuxt/Nitro's detected `workspaceDir` so monorepo apps can import sibling workspace packages without extra workflow config.
|
|
28
29
|
|
|
29
30
|
## Module Options
|
|
30
31
|
|
|
@@ -27,6 +27,7 @@ When enabled, the module:
|
|
|
27
27
|
- Registers the workflow runtime routes under `/.well-known/workflow/v1/`.
|
|
28
28
|
- Serves a redirect to the local observability dashboard at `/_workflow` in development.
|
|
29
29
|
- Configures Vercel function rules (queue triggers and `maxDuration`) for the workflow routes when deploying to Vercel.
|
|
30
|
+
- Uses Nitro's `workspaceDir` as the workflow project root so monorepo apps can import sibling workspace packages without extra workflow config.
|
|
30
31
|
|
|
31
32
|
## Module Options
|
|
32
33
|
|
|
@@ -25,6 +25,7 @@ When enabled, the module:
|
|
|
25
25
|
- Registers the [`workflow/nitro`](/docs/api-reference/workflow-nitro) module on Nuxt's Nitro server, which transforms `"use workflow"` and `"use step"` directives, builds the workflow bundles, and registers the workflow runtime routes under `/.well-known/workflow/v1/`.
|
|
26
26
|
- Configures Vite to bundle (rather than externalize) the Workflow SDK packages in SSR mode so workflow code is transformed correctly.
|
|
27
27
|
- Enables the `workflow` TypeScript plugin by default for IDE IntelliSense.
|
|
28
|
+
- Uses Nuxt/Nitro's detected `workspaceDir` so monorepo apps can import sibling workspace packages without extra workflow config.
|
|
28
29
|
|
|
29
30
|
## Module Options
|
|
30
31
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "workflow",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.0",
|
|
4
4
|
"description": "Workflow SDK - Build durable, resilient, and observable workflows",
|
|
5
5
|
"main": "dist/typescript-plugin.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -57,18 +57,18 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"ms": "2.1.3",
|
|
60
|
-
"@workflow/astro": "4.0.
|
|
61
|
-
"@workflow/cli": "4.
|
|
62
|
-
"@workflow/core": "4.
|
|
60
|
+
"@workflow/astro": "4.0.11",
|
|
61
|
+
"@workflow/cli": "4.3.0",
|
|
62
|
+
"@workflow/core": "4.6.0",
|
|
63
63
|
"@workflow/errors": "4.1.4",
|
|
64
64
|
"@workflow/typescript-plugin": "4.0.3",
|
|
65
65
|
"@workflow/utils": "4.1.3",
|
|
66
|
-
"@workflow/next": "4.0
|
|
67
|
-
"@workflow/nest": "
|
|
68
|
-
"@workflow/nitro": "4.1.
|
|
69
|
-
"@workflow/nuxt": "4.0.
|
|
70
|
-
"@workflow/sveltekit": "4.0.
|
|
71
|
-
"@workflow/rollup": "4.0.
|
|
66
|
+
"@workflow/next": "4.1.0",
|
|
67
|
+
"@workflow/nest": "4.0.12",
|
|
68
|
+
"@workflow/nitro": "4.1.2",
|
|
69
|
+
"@workflow/nuxt": "4.0.12",
|
|
70
|
+
"@workflow/sveltekit": "4.0.11",
|
|
71
|
+
"@workflow/rollup": "4.0.11"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@types/ms": "2.1.0",
|