workflow 4.2.0-beta.70 → 4.2.0-beta.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/api.d.ts +1 -1
  2. package/dist/api.d.ts.map +1 -1
  3. package/dist/api.js +1 -1
  4. package/dist/internal/builtins.d.ts +3 -3
  5. package/dist/internal/builtins.d.ts.map +1 -1
  6. package/dist/internal/builtins.js +7 -7
  7. package/dist/internal/errors.d.ts +1 -1
  8. package/dist/internal/errors.d.ts.map +1 -1
  9. package/dist/internal/errors.js +2 -2
  10. package/dist/observability.d.ts +20 -0
  11. package/dist/observability.d.ts.map +1 -0
  12. package/dist/observability.js +20 -0
  13. package/docs/ai/chat-session-modeling.mdx +4 -4
  14. package/docs/ai/defining-tools.mdx +7 -1
  15. package/docs/ai/index.mdx +8 -5
  16. package/docs/ai/message-queueing.mdx +8 -6
  17. package/docs/ai/resumable-streams.mdx +37 -4
  18. package/docs/ai/sleep-and-delays.mdx +2 -0
  19. package/docs/api-reference/index.mdx +3 -0
  20. package/docs/api-reference/meta.json +1 -1
  21. package/docs/api-reference/workflow/define-hook.mdx +2 -0
  22. package/docs/api-reference/workflow/get-writable.mdx +1 -0
  23. package/docs/api-reference/workflow-ai/durable-agent.mdx +7 -5
  24. package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +2 -0
  25. package/docs/api-reference/workflow-api/get-run.mdx +14 -0
  26. package/docs/api-reference/workflow-api/get-world.mdx +105 -0
  27. package/docs/api-reference/workflow-api/start.mdx +24 -0
  28. package/docs/api-reference/workflow-errors/entity-conflict-error.mdx +60 -0
  29. package/docs/api-reference/workflow-errors/hook-not-found-error.mdx +90 -0
  30. package/docs/api-reference/workflow-errors/meta.json +16 -0
  31. package/docs/api-reference/workflow-errors/run-expired-error.mdx +58 -0
  32. package/docs/api-reference/workflow-errors/step-not-registered-error.mdx +56 -0
  33. package/docs/api-reference/workflow-errors/throttle-error.mdx +62 -0
  34. package/docs/api-reference/workflow-errors/too-early-error.mdx +62 -0
  35. package/docs/api-reference/workflow-errors/workflow-not-registered-error.mdx +57 -0
  36. package/docs/api-reference/workflow-errors/workflow-run-cancelled-error.mdx +56 -0
  37. package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +62 -0
  38. package/docs/api-reference/workflow-errors/workflow-run-not-found-error.mdx +56 -0
  39. package/docs/api-reference/workflow-errors/workflow-world-error.mdx +79 -0
  40. package/docs/api-reference/workflow-serde/index.mdx +52 -0
  41. package/docs/api-reference/workflow-serde/meta.json +3 -0
  42. package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +70 -0
  43. package/docs/api-reference/workflow-serde/workflow-serialize.mdx +75 -0
  44. package/docs/changelog/index.mdx +15 -0
  45. package/docs/changelog/meta.json +5 -0
  46. package/docs/deploying/building-a-world.mdx +20 -0
  47. package/docs/deploying/world/vercel-world.mdx +2 -1
  48. package/docs/errors/hook-conflict.mdx +9 -3
  49. package/docs/errors/index.mdx +6 -0
  50. package/docs/errors/step-not-registered.mdx +66 -0
  51. package/docs/errors/webhook-invalid-respond-with-value.mdx +10 -0
  52. package/docs/errors/webhook-response-not-sent.mdx +8 -0
  53. package/docs/errors/workflow-not-registered.mdx +64 -0
  54. package/docs/foundations/common-patterns.mdx +4 -0
  55. package/docs/foundations/errors-and-retries.mdx +29 -0
  56. package/docs/foundations/serialization.mdx +211 -0
  57. package/docs/foundations/streaming.mdx +22 -0
  58. package/docs/getting-started/index.mdx +3 -3
  59. package/docs/getting-started/meta.json +15 -0
  60. package/docs/getting-started/nestjs.mdx +4 -8
  61. package/docs/how-it-works/encryption.mdx +93 -0
  62. package/docs/how-it-works/meta.json +2 -1
  63. package/docs/observability/index.mdx +3 -0
  64. package/package.json +18 -12
package/dist/api.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { type Event, getHookByToken, getRun, Run, resumeHook, resumeWebhook, runStep, type StartOptions, start, type StopSleepOptions, type StopSleepResult, type WorkflowReadableStreamOptions, type WorkflowRun, } from '@workflow/core/runtime';
1
+ export { type Event, getHookByToken, getRun, Run, resumeHook, resumeWebhook, runStep, type StartOptions, start, type StopSleepOptions, type StopSleepResult, type WorkflowReadableStream, type WorkflowReadableStreamOptions, type WorkflowRun, } from '@workflow/core/runtime';
2
2
  //# sourceMappingURL=api.d.ts.map
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,KAAK,EACV,cAAc,EACd,MAAM,EACN,GAAG,EACH,UAAU,EACV,aAAa,EACb,OAAO,EACP,KAAK,YAAY,EACjB,KAAK,EACL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,6BAA6B,EAClC,KAAK,WAAW,GACjB,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,KAAK,EACV,cAAc,EACd,MAAM,EACN,GAAG,EACH,UAAU,EACV,aAAa,EACb,OAAO,EACP,KAAK,YAAY,EACjB,KAAK,EACL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,6BAA6B,EAClC,KAAK,WAAW,GACjB,MAAM,wBAAwB,CAAC"}
package/dist/api.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export { getHookByToken, getRun, Run, resumeHook, resumeWebhook, runStep, start, } from '@workflow/core/runtime';
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2FwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBRUwsY0FBYyxFQUNkLE1BQU0sRUFDTixHQUFHLEVBQ0gsVUFBVSxFQUNWLGFBQWEsRUFDYixPQUFPLEVBRVAsS0FBSyxHQUtOLE1BQU0sd0JBQXdCLENBQUMifQ==
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2FwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBRUwsY0FBYyxFQUNkLE1BQU0sRUFDTixHQUFHLEVBQ0gsVUFBVSxFQUNWLGFBQWEsRUFDYixPQUFPLEVBRVAsS0FBSyxHQU1OLE1BQU0sd0JBQXdCLENBQUMifQ==
@@ -3,7 +3,7 @@
3
3
  * similar to "stdlib" except that are not meant to be imported by users, but are instead "just available"
4
4
  * alongside user defined steps. They are used internally by the runtime
5
5
  */
6
- export declare function __builtin_response_array_buffer(res: Response): Promise<ArrayBuffer>;
7
- export declare function __builtin_response_json(res: Response): Promise<unknown>;
8
- export declare function __builtin_response_text(res: Response): Promise<string>;
6
+ export declare function __builtin_response_array_buffer(this: Request | Response): Promise<ArrayBuffer>;
7
+ export declare function __builtin_response_json(this: Request | Response): Promise<unknown>;
8
+ export declare function __builtin_response_text(this: Request | Response): Promise<string>;
9
9
  //# sourceMappingURL=builtins.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../../src/internal/builtins.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAsB,+BAA+B,CAAC,GAAG,EAAE,QAAQ,wBAGlE;AAED,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,QAAQ,oBAG1D;AAED,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,QAAQ,mBAG1D"}
1
+ {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../../src/internal/builtins.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAsB,+BAA+B,CACnD,IAAI,EAAE,OAAO,GAAG,QAAQ,wBAIzB;AAED,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,oBAGrE;AAED,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,mBAGrE"}
@@ -3,16 +3,16 @@
3
3
  * similar to "stdlib" except that are not meant to be imported by users, but are instead "just available"
4
4
  * alongside user defined steps. They are used internally by the runtime
5
5
  */
6
- export async function __builtin_response_array_buffer(res) {
6
+ export async function __builtin_response_array_buffer() {
7
7
  'use step';
8
- return res.arrayBuffer();
8
+ return this.arrayBuffer();
9
9
  }
10
- export async function __builtin_response_json(res) {
10
+ export async function __builtin_response_json() {
11
11
  'use step';
12
- return res.json();
12
+ return this.json();
13
13
  }
14
- export async function __builtin_response_text(res) {
14
+ export async function __builtin_response_text() {
15
15
  'use step';
16
- return res.text();
16
+ return this.text();
17
17
  }
18
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbHRpbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW50ZXJuYWwvYnVpbHRpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7R0FJRztBQUVILE1BQU0sQ0FBQyxLQUFLLFVBQVUsK0JBQStCLENBQUMsR0FBYTtJQUNqRSxVQUFVLENBQUM7SUFDWCxPQUFPLEdBQUcsQ0FBQyxXQUFXLEVBQUUsQ0FBQztBQUMzQixDQUFDO0FBRUQsTUFBTSxDQUFDLEtBQUssVUFBVSx1QkFBdUIsQ0FBQyxHQUFhO0lBQ3pELFVBQVUsQ0FBQztJQUNYLE9BQU8sR0FBRyxDQUFDLElBQUksRUFBRSxDQUFDO0FBQ3BCLENBQUM7QUFFRCxNQUFNLENBQUMsS0FBSyxVQUFVLHVCQUF1QixDQUFDLEdBQWE7SUFDekQsVUFBVSxDQUFDO0lBQ1gsT0FBTyxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUM7QUFDcEIsQ0FBQyJ9
18
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbHRpbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW50ZXJuYWwvYnVpbHRpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7R0FJRztBQUVILE1BQU0sQ0FBQyxLQUFLLFVBQVUsK0JBQStCO0lBR25ELFVBQVUsQ0FBQztJQUNYLE9BQU8sSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO0FBQzVCLENBQUM7QUFFRCxNQUFNLENBQUMsS0FBSyxVQUFVLHVCQUF1QjtJQUMzQyxVQUFVLENBQUM7SUFDWCxPQUFPLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUNyQixDQUFDO0FBRUQsTUFBTSxDQUFDLEtBQUssVUFBVSx1QkFBdUI7SUFDM0MsVUFBVSxDQUFDO0lBQ1gsT0FBTyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7QUFDckIsQ0FBQyJ9
@@ -1,2 +1,2 @@
1
- export { WorkflowAPIError, WorkflowError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, } from '@workflow/errors';
1
+ export { EntityConflictError, HookConflictError, HookNotFoundError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
2
2
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/internal/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,yBAAyB,EACzB,sBAAsB,EACtB,4BAA4B,EAC5B,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/internal/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,aAAa,EACb,0BAA0B,EAC1B,yBAAyB,EACzB,sBAAsB,EACtB,4BAA4B,EAC5B,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC"}
@@ -1,2 +1,2 @@
1
- export { WorkflowAPIError, WorkflowError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, } from '@workflow/errors';
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2ludGVybmFsL2Vycm9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsZ0JBQWdCLEVBQ2hCLGFBQWEsRUFDYix5QkFBeUIsRUFDekIsc0JBQXNCLEVBQ3RCLDRCQUE0QixFQUM1Qix3QkFBd0IsRUFDeEIsb0JBQW9CLEdBQ3JCLE1BQU0sa0JBQWtCLENBQUMifQ==
1
+ export { EntityConflictError, HookConflictError, HookNotFoundError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2ludGVybmFsL2Vycm9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLGlCQUFpQixFQUNqQixpQkFBaUIsRUFDakIsZUFBZSxFQUNmLG9CQUFvQixFQUNwQixzQkFBc0IsRUFDdEIsYUFBYSxFQUNiLGFBQWEsRUFDYixhQUFhLEVBQ2IsMEJBQTBCLEVBQzFCLHlCQUF5QixFQUN6QixzQkFBc0IsRUFDdEIsNEJBQTRCLEVBQzVCLHdCQUF3QixFQUN4QixvQkFBb0IsRUFDcEIsa0JBQWtCLEdBQ25CLE1BQU0sa0JBQWtCLENBQUMifQ==
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Observability utilities for hydrating serialized workflow data.
3
+ *
4
+ * Use these when inspecting workflow step I/O, run inputs/outputs,
5
+ * or event data from the Workflow SDK's world APIs.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { getWorld } from 'workflow/api';
10
+ * import { hydrateResourceIO, observabilityRevivers } from 'workflow/observability';
11
+ *
12
+ * const world = getWorld();
13
+ * const step = await world.steps.get(runId, stepId, { resolveData: 'all' });
14
+ * const hydrated = hydrateResourceIO(step, observabilityRevivers);
15
+ * // hydrated.input and hydrated.output are now plain JS objects
16
+ * ```
17
+ */
18
+ export { hydrateData, hydrateResourceIO, observabilityRevivers, type Revivers, } from '@workflow/core/serialization-format';
19
+ export { parseClassName, parseStepName, parseWorkflowName, } from '@workflow/utils';
20
+ //# sourceMappingURL=observability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observability.d.ts","sourceRoot":"","sources":["../src/observability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,QAAQ,GACd,MAAM,qCAAqC,CAAC;AAE7C,OAAO,EACL,cAAc,EACd,aAAa,EACb,iBAAiB,GAClB,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Observability utilities for hydrating serialized workflow data.
3
+ *
4
+ * Use these when inspecting workflow step I/O, run inputs/outputs,
5
+ * or event data from the Workflow SDK's world APIs.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { getWorld } from 'workflow/api';
10
+ * import { hydrateResourceIO, observabilityRevivers } from 'workflow/observability';
11
+ *
12
+ * const world = getWorld();
13
+ * const step = await world.steps.get(runId, stepId, { resolveData: 'all' });
14
+ * const hydrated = hydrateResourceIO(step, observabilityRevivers);
15
+ * // hydrated.input and hydrated.output are now plain JS objects
16
+ * ```
17
+ */
18
+ export { hydrateData, hydrateResourceIO, observabilityRevivers, } from '@workflow/core/serialization-format';
19
+ export { parseClassName, parseStepName, parseWorkflowName, } from '@workflow/utils';
20
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib2JzZXJ2YWJpbGl0eS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9vYnNlcnZhYmlsaXR5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7O0dBZ0JHO0FBQ0gsT0FBTyxFQUNMLFdBQVcsRUFDWCxpQkFBaUIsRUFDakIscUJBQXFCLEdBRXRCLE1BQU0scUNBQXFDLENBQUM7QUFFN0MsT0FBTyxFQUNMLGNBQWMsRUFDZCxhQUFhLEVBQ2IsaUJBQWlCLEdBQ2xCLE1BQU0saUJBQWlCLENBQUMifQ==
@@ -39,12 +39,12 @@ export async function chat(messages: UIMessage[]) {
39
39
 
40
40
  const agent = new DurableAgent({
41
41
  model: "bedrock/claude-haiku-4-5-20251001-v1",
42
- system: FLIGHT_ASSISTANT_PROMPT,
42
+ instructions: FLIGHT_ASSISTANT_PROMPT,
43
43
  tools: flightBookingTools,
44
44
  });
45
45
 
46
46
  await agent.stream({
47
- messages: convertToModelMessages(messages), // [!code highlight] Full history from client
47
+ messages: await convertToModelMessages(messages), // [!code highlight] Full history from client
48
48
  writable,
49
49
  });
50
50
  }
@@ -171,7 +171,7 @@ export async function chat(initialMessages: UIMessage[]) {
171
171
 
172
172
  const { workflowRunId: runId } = getWorkflowMetadata();
173
173
  const writable = getWritable<UIMessageChunk>();
174
- const messages: ModelMessage[] = convertToModelMessages(initialMessages);
174
+ const messages: ModelMessage[] = await convertToModelMessages(initialMessages);
175
175
 
176
176
  // Write markers for initial user messages (for replay) // [!code highlight]
177
177
  for (const msg of initialMessages) { // [!code highlight]
@@ -183,7 +183,7 @@ export async function chat(initialMessages: UIMessage[]) {
183
183
 
184
184
  const agent = new DurableAgent({
185
185
  model: "bedrock/claude-haiku-4-5-20251001-v1",
186
- system: FLIGHT_ASSISTANT_PROMPT,
186
+ instructions: FLIGHT_ASSISTANT_PROMPT,
187
187
  tools: flightBookingTools,
188
188
  });
189
189
 
@@ -23,9 +23,12 @@ Just like in regular AI SDK tool definitions, tool in DurableAgent are called wi
23
23
  When you tool needs access to the full message history, you can access it via the `messages` property of the tool call context:
24
24
 
25
25
  ```typescript title="tools.ts" lineNumbers
26
+ import { Experimental_Agent as Agent } from "ai";
27
+ import type { ModelMessage } from "ai";
28
+
26
29
  async function getWeather(
27
30
  { city }: { city: string },
28
- { messages, toolCallId }: { messages: LanguageModelV2Prompt, toolCallId: string }) { // [!code highlight]
31
+ { messages, toolCallId }: { messages: ModelMessage[], toolCallId: string }) { // [!code highlight]
29
32
  "use step";
30
33
  return `Weather in ${city} is sunny`;
31
34
  }
@@ -65,6 +68,9 @@ Tools can be implemented either at the step level or the workflow level, with di
65
68
  Tools can also combine both by starting out on the workflow level, and calling into steps for I/O operations, like so:
66
69
 
67
70
  ```typescript title="tools.ts" lineNumbers
71
+ import { sleep } from "workflow";
72
+ import type { LanguageModel, ModelMessage } from "ai";
73
+
68
74
  // Step: handles I/O with retries
69
75
  async function performFetch(url: string) {
70
76
  "use step";
package/docs/ai/index.mdx CHANGED
@@ -123,15 +123,18 @@ The core code that makes all of this happen is quite simple. Here's a breakdown
123
123
  Our API route makes a simple call to [AI SDK's `Agent` class](https://ai-sdk.dev/docs/agents/overview), which is a simple wrapper around [AI SDK's `streamText` function](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text#streamtext). This is also where we pass tools to the agent.
124
124
 
125
125
  ```typescript title="app/api/chat/route.ts" lineNumbers
126
+ import { Experimental_Agent as Agent } from "ai";
127
+ import type { LanguageModel } from "ai";
128
+
126
129
  export async function POST(req: Request) {
127
130
  const { messages }: { messages: UIMessage[] } = await req.json();
128
131
  const agent = new Agent({ // [!code highlight]
129
132
  model: gateway("bedrock/claude-4-5-haiku-20251001-v1"),
130
- system: FLIGHT_ASSISTANT_PROMPT,
133
+ instructions: FLIGHT_ASSISTANT_PROMPT,
131
134
  tools: flightBookingTools,
132
135
  });
133
- const modelMessages = convertToModelMessages(messages);
134
- const stream = agent.stream({ messages: modelMessages }); // [!code highlight]
136
+ const modelMessages = await convertToModelMessages(messages);
137
+ const stream = await agent.stream({ messages: modelMessages }); // [!code highlight]
135
138
  return createUIMessageStreamResponse({
136
139
  stream: stream.toUIMessageStream(),
137
140
  });
@@ -273,7 +276,7 @@ export async function chatWorkflow(messages: ModelMessage[]) {
273
276
  // ELSE if using a custom provider, pass the provider call as an argument:
274
277
  model: openai("gpt-5.1"), // [!code highlight]
275
278
 
276
- system: FLIGHT_ASSISTANT_PROMPT,
279
+ instructions: FLIGHT_ASSISTANT_PROMPT,
277
280
  tools: flightBookingTools,
278
281
  });
279
282
 
@@ -304,7 +307,7 @@ import { chatWorkflow } from "@/workflows/chat/workflow";
304
307
 
305
308
  export async function POST(req: Request) {
306
309
  const { messages }: { messages: UIMessage[] } = await req.json();
307
- const modelMessages = convertToModelMessages(messages);
310
+ const modelMessages = await convertToModelMessages(messages);
308
311
 
309
312
  const run = await start(chatWorkflow, [modelMessages]); // [!code highlight]
310
313
 
@@ -32,16 +32,18 @@ If you just need basic multi-turn conversations where messages arrive between tu
32
32
  The `prepareStep` callback runs before each step in the agent loop. It receives the current state and can modify the messages sent to the model:
33
33
 
34
34
  ```typescript lineNumbers
35
+ import type { ModelMessage, LanguageModel } from "ai";
36
+
35
37
  interface PrepareStepInfo {
36
- model: string | (() => Promise<LanguageModelV2>); // Current model
38
+ model: string | (() => Promise<LanguageModel>); // Current model
37
39
  stepNumber: number; // 0-indexed step count
38
40
  steps: StepResult[]; // Previous step results
39
- messages: LanguageModelV2Prompt; // Messages to be sent
41
+ messages: ModelMessage[]; // Messages to be sent
40
42
  }
41
43
 
42
44
  interface PrepareStepResult {
43
- model?: string | (() => Promise<LanguageModelV2>); // Override model
44
- messages?: LanguageModelV2Prompt; // Override messages
45
+ model?: string | (() => Promise<LanguageModel>); // Override model
46
+ messages?: ModelMessage[]; // Override messages
45
47
  }
46
48
  ```
47
49
 
@@ -65,7 +67,7 @@ export async function chat(initialMessages: ModelMessage[]) {
65
67
 
66
68
  const agent = new DurableAgent({
67
69
  model: "bedrock/claude-haiku-4-5-20251001-v1",
68
- system: FLIGHT_ASSISTANT_PROMPT,
70
+ instructions: FLIGHT_ASSISTANT_PROMPT,
69
71
  tools: flightBookingTools,
70
72
  });
71
73
 
@@ -101,7 +103,7 @@ export async function chat(initialMessages: ModelMessage[]) {
101
103
  Messages sent via `chatMessageHook.resume()` accumulate in the queue and get injected before the next step, whether that's a tool call or another LLM request.
102
104
 
103
105
  <Callout type="info">
104
- The `prepareStep` callback receives messages in `LanguageModelV2Prompt` format (with content arrays), which is the internal format used by the AI SDK.
106
+ The `prepareStep` callback receives messages in `ModelMessage[]` format (with content arrays), which is the internal format used by the AI SDK.
105
107
  </Callout>
106
108
 
107
109
  ## Combining with Multi-Turn Sessions
@@ -30,7 +30,8 @@ Let's add stream resumption to our Flight Booking Agent that we build in the [Bu
30
30
 
31
31
  Modify your chat endpoint to include the workflow run ID in a response header. The Run ID uniquely identifies the run's stream, so it allows the client to know which stream to reconnect to.
32
32
 
33
- {/* @skip-typecheck: incomplete code sample */}
33
+ {/*@skip-typecheck: incomplete code sample*/}
34
+
34
35
  ```typescript title="app/api/chat/route.ts" lineNumbers
35
36
  // ... imports ...
36
37
 
@@ -76,14 +77,24 @@ export async function GET(
76
77
 
77
78
  // Instead of starting a new run, we fetch an existing run.
78
79
  const run = getRun(id); // [!code highlight]
79
- const stream = run.getReadable({ startIndex }); // [!code highlight]
80
+ const readable = run.getReadable({ startIndex }); // [!code highlight]
81
+
82
+ // Provide the stream's tail index so the transport can resolve
83
+ // negative startIndex values into absolute positions for retries.
84
+ const tailIndex = await readable.getTailIndex(); // [!code highlight]
80
85
 
81
- return createUIMessageStreamResponse({ stream }); // [!code highlight]
86
+ return createUIMessageStreamResponse({
87
+ stream: readable, // [!code highlight]
88
+ headers: { // [!code highlight]
89
+ "x-workflow-stream-tail-index": String(tailIndex), // [!code highlight]
90
+ }, // [!code highlight]
91
+ });
82
92
  }
83
93
  ```
84
94
 
85
- The `startIndex` parameter ensures the client can choose where to resume the stream from. For instance, if the function times out during streaming, the chat transport will use `startIndex` to resume the stream exactly from the last token it received.
95
+ The `startIndex` parameter ensures the client can choose where to resume the stream from. For instance, if the function times out during streaming, the chat transport will use `startIndex` to resume the stream exactly from the last token it received. Negative values are also supported (e.g. `-5` starts 5 chunks before the end), which is useful for custom stream consumers (such as a dashboard showing recent output) that want to show the most recent output without replaying the full stream.
86
96
 
97
+ When using a negative `startIndex`, your stream endpoint must return a `x-workflow-stream-tail-index` header in order for relative resumption to work. Missing the header will fall back to replaying the entire stream.
87
98
  </Step>
88
99
 
89
100
  <Step>
@@ -161,6 +172,28 @@ Now try the flight booking example again. Open it up in a separate tab, or spam
161
172
 
162
173
  This approach also handles page refreshes, as the client will automatically reconnect to the stream from the last known position when the UI loads with a stored run ID, following the behavior of [AI SDK's stream resumption](https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-resume-streams#chatbot-resume-streams).
163
174
 
175
+ ### Resuming from the end of the stream
176
+
177
+ By default, reconnecting replays the entire stream from the beginning (`startIndex: 0`). If you only need to show recent output — for example, when resuming a long conversation after a page refresh — you can set `initialStartIndex` to a negative value to read from the end of the stream instead:
178
+
179
+ {/*@skip-typecheck: incomplete code sample*/}
180
+
181
+ ```typescript
182
+ const { messages, sendMessage } = useChat({
183
+ resume: !!activeWorkflowRunId,
184
+ transport: new WorkflowChatTransport({
185
+ initialStartIndex: -20, // Only fetch the last 20 chunks // [!code highlight]
186
+ // ... callbacks as above
187
+ }),
188
+ });
189
+ ```
190
+
191
+ This avoids replaying potentially thousands of chunks and lets the UI render faster. The negative value is resolved server-side, so `-20` on a 500-chunk stream starts at chunk 480.
192
+
193
+ <Callout>
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
+ </Callout>
196
+
164
197
  ## Related Documentation
165
198
 
166
199
  - [`WorkflowChatTransport` API Reference](/docs/api-reference/workflow-ai/workflow-chat-transport) - Full configuration options
@@ -162,6 +162,8 @@ Aside from providing `sleep()` as a tool, there are other use cases for Agents t
162
162
  When hitting API rate limits, use `RetryableError` with a delay:
163
163
 
164
164
  ```typescript lineNumbers
165
+ import { RetryableError } from "workflow";
166
+
165
167
  async function callRateLimitedAPI(endpoint: string) {
166
168
  "use step";
167
169
 
@@ -17,6 +17,9 @@ All the functions and primitives that come with Workflow DevKit by package.
17
17
  <Card title="workflow/next" href="/docs/api-reference/workflow-next">
18
18
  Next.js integration for Workflow DevKit that automatically configures bundling and runtime support.
19
19
  </Card>
20
+ <Card title="@workflow/serde" href="/docs/api-reference/workflow-serde">
21
+ Serialization symbols for custom class serialization in workflows.
22
+ </Card>
20
23
  <Card title="@workflow/ai" href="/docs/api-reference/workflow-ai">
21
24
  Helpers for integrating AI SDK for building AI-powered workflows.
22
25
  </Card>
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "title": "API Reference",
3
- "pages": ["...", "workflow-ai", "vitest"]
3
+ "pages": ["...", "workflow-errors", "workflow-serde", "workflow-ai", "vitest"]
4
4
  }
@@ -193,6 +193,8 @@ export const approvalHook = defineHook({
193
193
  Tokens are used to identify a specific hook and for resuming a hook. You can customize the token to be more specific to a use case.
194
194
 
195
195
  ```typescript lineNumbers
196
+ import { defineHook } from "workflow";
197
+
196
198
  const slackHook = defineHook<{ text: string; userId: string }>();
197
199
 
198
200
  export async function slackBotWorkflow(channelId: string) {
@@ -208,6 +208,7 @@ Here's a more complex example showing how you might stream AI chat responses:
208
208
  ```typescript lineNumbers
209
209
  import { getWritable } from "workflow";
210
210
  import { generateId, streamText, type UIMessageChunk } from "ai";
211
+ import type { ModelMessage } from "ai";
211
212
 
212
213
  export async function chat(messages: ModelMessage[]) {
213
214
  "use workflow";
@@ -34,7 +34,7 @@ async function myAgent() {
34
34
 
35
35
  const agent = new DurableAgent({
36
36
  model: "anthropic/claude-haiku-4.5",
37
- system: "You are a helpful weather assistant.",
37
+ instructions: "You are a helpful weather assistant.",
38
38
  temperature: 0.7,
39
39
  tools: {
40
40
  getWeather: {
@@ -249,7 +249,7 @@ async function weatherAgentWorkflow(userQuery: string) {
249
249
  execute: getWeather,
250
250
  },
251
251
  },
252
- system: "You are a helpful weather assistant. Always provide accurate weather information.",
252
+ instructions: "You are a helpful weather assistant. Always provide accurate weather information.",
253
253
  });
254
254
 
255
255
  await agent.stream({
@@ -317,6 +317,8 @@ async function multiToolAgentWorkflow(userQuery: string) {
317
317
 
318
318
  ```typescript
319
319
  import { DurableAgent } from "@workflow/ai/agent";
320
+ import { getWritable } from "workflow";
321
+ import type { UIMessageChunk } from "ai";
320
322
  import { z } from "zod";
321
323
 
322
324
  async function searchProducts({ query }: { query: string }) {
@@ -446,7 +448,7 @@ async function agentWithPrepareStep(userMessage: string) {
446
448
 
447
449
  const agent = new DurableAgent({
448
450
  model: "openai/gpt-4.1-mini", // Default model
449
- system: "You are a helpful assistant.",
451
+ instructions: "You are a helpful assistant.",
450
452
  });
451
453
 
452
454
  await agent.stream({
@@ -500,7 +502,7 @@ async function agentWithMessageQueue(initialMessage: string) {
500
502
 
501
503
  const agent = new DurableAgent({
502
504
  model: "anthropic/claude-haiku-4.5",
503
- system: "You are a helpful assistant.",
505
+ instructions: "You are a helpful assistant.",
504
506
  });
505
507
 
506
508
  await agent.stream({
@@ -812,7 +814,7 @@ async function agentWithUIMessages(userMessage: string) {
812
814
 
813
815
  const agent = new DurableAgent({
814
816
  model: "anthropic/claude-haiku-4.5",
815
- system: "You are a helpful assistant.",
817
+ instructions: "You are a helpful assistant.",
816
818
  });
817
819
 
818
820
  const result = await agent.stream({
@@ -71,6 +71,8 @@ export default WorkflowChatTransportOptions;`}
71
71
  - The `onChatSendMessage` callback receives the full response object, allowing you to extract and store the workflow run ID for session resumption
72
72
  - Stream interruptions are automatically detected when a "finish" chunk is not received in the initial response
73
73
  - The `maxConsecutiveErrors` option controls how many reconnection attempts are made before giving up (default: 3)
74
+ - `initialStartIndex` (constructor option) sets the default chunk position for the **first** reconnection attempt (e.g. after a page refresh). Subsequent retries within the same reconnection loop always resume from the last received chunk. Negative values (e.g. `-20`) read from the end of the stream, which is useful for showing only recent output without replaying the full conversation. `startIndex` (per-call option on `reconnectToStream`) overrides `initialStartIndex` for a single reconnection
75
+ - When using a negative `initialStartIndex`, the reconnection endpoint must return the `x-workflow-stream-tail-index` response header (via `readable.getTailIndex()`). The transport reads this header to compute absolute chunk positions for retries. Without it, startIndex is assumed to be 0, replaying the entire stream
74
76
 
75
77
  ## Examples
76
78
 
@@ -39,6 +39,18 @@ export default Run;`}
39
39
  showSections={["returns"]}
40
40
  />
41
41
 
42
+ #### WorkflowReadableStream
43
+
44
+ `run.getReadable()` returns a `WorkflowReadableStream` — a standard `ReadableStream` extended with a `getTailIndex()` helper:
45
+
46
+ <TSDoc
47
+ definition={`
48
+ import type { WorkflowReadableStream } from "workflow/api";
49
+ export default WorkflowReadableStream;`}
50
+ />
51
+
52
+ `getTailIndex()` returns the index of the last known chunk (0-based), or `-1` when no chunks have been written. This is useful when building [reconnection endpoints](/docs/ai/resumable-streams) that need to inform clients where the stream starts.
53
+
42
54
  #### WorkflowReadableStreamOptions
43
55
 
44
56
  <TSDoc
@@ -144,6 +156,8 @@ export async function POST(req: Request) {
144
156
  You can also target specific sleep calls by correlation ID:
145
157
 
146
158
  ```typescript lineNumbers
159
+ import { getRun } from "workflow/api";
160
+
147
161
  const run = getRun("my-run-id"); // @setup
148
162
  const { stoppedCount } = await run.wakeUp({
149
163
  correlationIds: ["wait_abc123"],
@@ -91,6 +91,111 @@ export async function POST(req: Request) {
91
91
  }
92
92
  ```
93
93
 
94
+ ### List Steps for a Run (Without Data)
95
+
96
+ List steps for a workflow run with `resolveData: 'none'` to efficiently get step metadata without fetching serialized input/output. Use `parseStepName` to extract user-friendly display names:
97
+
98
+ ```typescript lineNumbers
99
+ import { getWorld } from "workflow/runtime";
100
+ import { parseStepName } from "@workflow/utils/parse-name"; // [!code highlight]
101
+
102
+ export async function GET(req: Request) {
103
+ const url = new URL(req.url);
104
+ const runId = url.searchParams.get("runId");
105
+
106
+ if (!runId) {
107
+ return Response.json({ error: "No runId provided" }, { status: 400 });
108
+ }
109
+
110
+ try {
111
+ const world = getWorld(); // [!code highlight]
112
+ const steps = await world.steps.list({ // [!code highlight]
113
+ runId, // [!code highlight]
114
+ resolveData: "none", // Skip fetching input/output for performance // [!code highlight]
115
+ }); // [!code highlight]
116
+
117
+ // Map steps to a progress view using parseStepName for display
118
+ const progress = steps.data.map((step) => {
119
+ const parsed = parseStepName(step.stepName); // [!code highlight]
120
+ return {
121
+ stepId: step.stepId,
122
+ // Use shortName for UI display (e.g., "fetchUserData") // [!code highlight]
123
+ displayName: parsed?.shortName ?? step.stepName, // [!code highlight]
124
+ // Module info available for debugging // [!code highlight]
125
+ module: parsed?.moduleSpecifier, // [!code highlight]
126
+ status: step.status,
127
+ startedAt: step.startedAt,
128
+ completedAt: step.completedAt,
129
+ };
130
+ });
131
+
132
+ return Response.json({ progress, cursor: steps.cursor });
133
+ } catch (error) {
134
+ return Response.json(
135
+ { error: "Failed to list steps" },
136
+ { status: 500 }
137
+ );
138
+ }
139
+ }
140
+ ```
141
+
142
+ ### Get Step with Hydrated Input/Output
143
+
144
+ Retrieve a step with its serialized data and hydrate it for display. This example shows how to decrypt and deserialize step input/output:
145
+
146
+ ```typescript lineNumbers
147
+ import { getWorld } from "workflow/runtime";
148
+ import { parseStepName } from "@workflow/utils/parse-name"; // [!code highlight]
149
+ import { // [!code highlight]
150
+ hydrateResourceIO, // [!code highlight]
151
+ observabilityRevivers, // [!code highlight]
152
+ } from "@workflow/core/serialization-format"; // [!code highlight]
153
+
154
+ export async function GET(req: Request) {
155
+ const url = new URL(req.url);
156
+ const runId = url.searchParams.get("runId");
157
+ const stepId = url.searchParams.get("stepId");
158
+
159
+ if (!runId || !stepId) {
160
+ return Response.json({ error: "runId and stepId required" }, { status: 400 });
161
+ }
162
+
163
+ try {
164
+ const world = getWorld(); // [!code highlight]
165
+ // Fetch step with data (default resolveData behavior) // [!code highlight]
166
+ const step = await world.steps.get(runId, stepId); // [!code highlight]
167
+
168
+ // Hydrate serialized input/output for display // [!code highlight]
169
+ const hydrated = hydrateResourceIO(step, observabilityRevivers); // [!code highlight]
170
+
171
+ // Parse the stepName for user-friendly display
172
+ const parsed = parseStepName(step.stepName);
173
+
174
+ return Response.json({
175
+ stepId: hydrated.stepId,
176
+ displayName: parsed?.shortName ?? step.stepName, // [!code highlight]
177
+ module: parsed?.moduleSpecifier, // [!code highlight]
178
+ status: hydrated.status,
179
+ attempt: hydrated.attempt,
180
+ // Hydrated input/output ready for rendering // [!code highlight]
181
+ input: hydrated.input, // [!code highlight]
182
+ output: hydrated.output, // [!code highlight]
183
+ });
184
+ } catch (error) {
185
+ return Response.json(
186
+ { error: "Step not found" },
187
+ { status: 404 }
188
+ );
189
+ }
190
+ }
191
+ ```
192
+
193
+ <Callout type="info">
194
+ The `stepName` field contains a machine-readable identifier like `step//./src/workflows/order//processPayment`.
195
+ Use `parseStepName()` from `@workflow/utils/parse-name` to extract the `shortName` (e.g., `"processPayment"`)
196
+ and `moduleSpecifier` for display in your UI.
197
+ </Callout>
198
+
94
199
  ## Related Functions
95
200
 
96
201
  - [`getRun()`](/docs/api-reference/workflow-api/get-run) - Higher-level API for working with individual runs by ID.
@@ -76,3 +76,27 @@ const run = await start(myWorkflow, ["arg1", "arg2"], { // [!code highlight]
76
76
  deploymentId: "custom-deployment-id" // [!code highlight]
77
77
  }); // [!code highlight]
78
78
  ```
79
+
80
+ ### Using `deploymentId: "latest"`
81
+
82
+ Set `deploymentId` to `"latest"` to automatically resolve the most recent deployment for the current environment. This is useful when you want to ensure a workflow run targets the latest deployed version of your application rather than the deployment that initiated the call.
83
+
84
+ ```typescript
85
+ import { start } from "workflow/api";
86
+ import { myWorkflow } from "./workflows/my-workflow";
87
+
88
+ const run = await start(myWorkflow, ["arg1", "arg2"], { // [!code highlight]
89
+ deploymentId: "latest" // [!code highlight]
90
+ }); // [!code highlight]
91
+ ```
92
+
93
+ <Callout type="info">
94
+ The `deploymentId` option is currently a Vercel-specific feature. The `"latest"` value 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.
95
+ </Callout>
96
+
97
+ <Callout type="warn">
98
+ When using `deploymentId: "latest"`, the workflow run will execute on a potentially different deployment than the one calling `start()`. Be mindful of forward and backward compatibility:
99
+
100
+ - **Workflow identity**: The workflow ID is derived from the function name and file path. If the latest deployment has renamed the workflow function or moved it to a different directory, the workflow ID will no longer match and the run will fail to start.
101
+ - **Input and output compatibility**: The arguments passed to `start()` are serialized by the calling deployment but deserialized by the target deployment. Similarly, the workflow's return value is serialized by the target deployment but deserialized by the caller. If the workflow's expected arguments or return type have changed (e.g. added required fields, removed fields, or changed types), the run may fail or behave unexpectedly. Ensure that input and output schemas remain backward-compatible across deployments.
102
+ </Callout>