@dbos-inc/vercel-ai 0.2.5 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,13 +2,10 @@
2
2
 
3
3
  [DBOS](https://docs.dbos.dev/) durable execution for the [Vercel AI SDK](https://ai-sdk.dev/).
4
4
 
5
- This package makes AI SDK **agents** durable, backed by your Postgres database.
6
- All you have to do is wrap your model with `durableCalls` and run your generation inside a DBOS workflow.
5
+ This package makes AI SDK agents durable, backed by your Postgres database.
6
+ All you have to do is wrap your model with `durableCalls` and your tools with `durableTools` and run your agents inside a DBOS workflow.
7
7
  Then, this integration automatically checkpoints every action your agents take in Postgres.
8
- If your process crashes mid-agent, DBOS replays the completed steps from their checkpoints and the agent resumes exactly where it left off.
9
-
10
- This package is implemented as standard AI SDK [middleware](https://ai-sdk.dev/docs/ai-sdk-core/middleware), so you keep your provider, your model configuration, and the familiar APIs like `generateText`, `streamText`, and `ToolLoopAgent`.
11
- Durability is transparent to your agent code.
8
+ If your process is interrupted, DBOS replays your agent from its checkpoints so it resumes from where it left off.
12
9
 
13
10
  ```ts
14
11
  import { DBOS } from '@dbos-inc/dbos-sdk';
@@ -47,95 +44,120 @@ npm install @dbos-inc/vercel-ai @dbos-inc/dbos-sdk ai
47
44
 
48
45
  Requires DBOS v4.21+ or v5, AI SDK v7+, and a Postgres database for DBOS.
49
46
 
50
- ## How it works
47
+ ## Durable Model Calls
51
48
 
52
- When an agent runs inside a DBOS workflow, DBOS makes three things durable:
49
+ To durably checkpoint each call you make to a model, wrap your model in `durableCalls`.
50
+ Then, call your model or agent from a workflow:
53
51
 
54
- - **Every model call.** `durableCalls()` is AI SDK middleware that intercepts `doGenerate`/`doStream` and runs each call through [`DBOS.runStep`](https://docs.dbos.dev/typescript/tutorials/step-tutorial). The complete result (content, usage, finish reason, response metadata) is checkpointed in Postgres. On recovery, completed calls replay from their checkpoints without contacting the model provider.
55
- - **The agent loop.** Because DBOS workflows replay deterministically on recovery and each model call replays from its checkpoint, a multi-step, tool-calling agent resumes from the first unfinished step instead of restarting from the beginning.
56
- - **Tool calls.** MCP tools (via [`durableMCPTools`](#mcp-tools)) are checkpointed automatically. Your own tools' side effects are durable when you wrap their `execute` in `DBOS.runStep` (see [Tools](#tools)).
52
+ ```ts
53
+ import { DBOS } from '@dbos-inc/dbos-sdk';
54
+ import { ToolLoopAgent, wrapLanguageModel } from 'ai';
55
+ import { openai } from '@ai-sdk/openai';
56
+ import { durableCalls } from '@dbos-inc/vercel-ai';
57
57
 
58
- Outside a workflow (or inside another step) the wrapped model calls the provider directly with no checkpointing, so the same model works anywhere in your app.
58
+ const model = wrapLanguageModel({ model: openai('gpt-5'), middleware: durableCalls() });
59
+ const agent = new ToolLoopAgent({ model, instructions: 'You are a helpful research assistant.', tools });
59
60
 
60
- All DBOS step options are accepted and apply per model call:
61
+ const researchAgent = DBOS.registerWorkflow(
62
+ async (question: string) => {
63
+ const result = await agent.stream({ prompt: question });
64
+ for await (const delta of result.textStream) process.stdout.write(delta);
65
+ return await result.text;
66
+ },
67
+ { name: 'researchAgent' },
68
+ );
69
+ ```
70
+
71
+ You can parameterize `durableCalls` to configure model call retries and timeouts:
61
72
 
62
73
  ```ts
63
74
  durableCalls({
64
- retriesAllowed: true, // retry failed model calls (default: true)
65
- maxAttempts: 5, // total attempts when retries are allowed (default: 3)
66
- intervalSeconds: 1, // delay before first retry (default: 1)
67
- backoffRate: 2, // exponential backoff multiplier (default: 2)
68
- shouldRetry: (error) => true, // per-error retry predicate (default: skip provider-declared non-retryable errors and aborts)
69
- timeoutMS: 60000, // per-attempt timeout
70
- name: 'my-model-call', // step name (default: "<provider>.<modelId>.<operation>")
75
+ name?: string; // step name (default: "<provider>.<modelId>.<operation>")
76
+ retriesAllowed?: boolean; // retry failed model calls (default: true)
77
+ maxAttempts?: number; // total attempts when retries are allowed (default: 3)
78
+ intervalSeconds?: number; // delay before first retry (default: 1)
79
+ backoffRate?: number; // exponential backoff multiplier (default: 2)
80
+ shouldRetry?: (error: unknown) => boolean; // default: skip provider-declared non-retryable errors and aborts
81
+ timeoutMS?: number; // per-attempt timeout
82
+ durableStream?: string; // stream each call's output to this durable stream
83
+ include?: { requestBody?: boolean; responseBody?: boolean }; // checkpoint raw provider bodies; match generateText's `include` (default: false)
71
84
  });
72
85
  ```
73
86
 
74
- Retries are on by default so that a transient provider error is absorbed inside a single durable step.
75
- The default `shouldRetry` treats errors the provider marks non-retryable (an AI SDK `APICallError`/`GatewayError` with `isRetryable === false`, e.g. a 401 or an invalid-request 400) and aborts/timeouts as terminal, so they fail fast instead of retrying `maxAttempts` times.
76
- Pass your own `shouldRetry` to override it, or `retriesAllowed: false` to disable step retries.
77
-
78
- Because DBOS owns retries by default, pass `maxRetries: 0` to the AI SDK call so retry behavior is governed in one place; otherwise the two compose multiplicatively and each AI SDK retry is a fresh step.
79
-
80
- ## Streaming
87
+ ## Durable Streams
81
88
 
82
- You can stream durable model responses inside a workflow with `streamText`.
83
- During streaming, DBOS checkpoints only the final completed output, not individual deltas.
84
- As a consequence:
85
-
86
- - You can safely forward streamed deltas to a UI or terminal, but you should not perform durable steps on them because model responses are not resumable. Instead, run your own durable steps on the complete result (`result.text`) after the stream ends. Tool calls performed by the AI SDK during streaming are already durable because they execute after the model call has been checkpointed.
87
- - Do not exit a stream before it completes. To stop reading early, either drain the stream (`await result.consumeStream()`) or abort it.
88
- - To abort early, pass an `abortSignal` to `streamText` and fire it. The abort detaches your consumer, but inside a workflow it does not cut the model call short; the step keeps draining and checkpoints the complete response.
89
- - The AI SDK's own `timeout` option does not bound a durable model call, because its signal is an abort signal. Bound the call with the step's `timeoutMS` instead (`durableCalls({ timeoutMS })`), which DBOS tears down deterministically.
89
+ You can **durably stream** agent or model output so it can be read by an external client or UI.
90
+ To do this, configure `durableCalls` or `durableTools`/`durableMCPTools` with a durable stream name:
90
91
 
91
92
  ```ts
92
- import { streamText } from 'ai';
93
- import { durableCalls } from '@dbos-inc/vercel-ai';
93
+ import { createUIMessageStreamResponse, streamText } from 'ai';
94
+ import { durableCalls, durableTools, readDurableStream } from '@dbos-inc/vercel-ai';
94
95
 
95
- const model = wrapLanguageModel({
96
- model: openai('gpt-5'),
97
- middleware: durableCalls({ retriesAllowed: true, maxAttempts: 5 }),
98
- });
96
+ const model = wrapLanguageModel({ model: openai('gpt-5'), middleware: durableCalls({ durableStream: 'ui' }) });
97
+ const tools = durableTools(myTools, { durableStream: 'ui' });
99
98
 
100
- const streamingAgent = DBOS.registerWorkflow(async (prompt: string) => {
101
- const result = streamText({ model, prompt });
102
- for await (const delta of result.textStream) {
103
- process.stdout.write(delta);
104
- }
99
+ const chatTurn = DBOS.registerWorkflow(async (messages: ModelMessage[]) => {
100
+ const result = streamText({ model, messages, tools, stopWhen: stepCountIs(10) });
105
101
  return await result.text;
106
- }, { name: 'streamingAgent' });
102
+ }, { name: 'chatTurn' });
103
+
104
+ const handle = await DBOS.startWorkflow(chatTurn)(messages);
105
+ return createUIMessageStreamResponse({
106
+ stream: readDurableStream({ workflowID: handle.workflowID, key: 'ui', messageId }),
107
+ });
107
108
  ```
108
109
 
109
- ## Tools
110
+ You can read from a durable stream using `readDurableStream`, for example to stream it to a UI.
111
+ It emits a stream of AI SDK `UIMessageChunk`.
112
+ You can also pass a `DBOSClient` into `readDurableStream` to read it from a different process.
113
+
114
+ You can write your own data to a stream with `writeDurableStream(key, chunks)`.
115
+ Your streams are closed when your workflow finishes; you can also close a stream early using `closeDurableStream`.
116
+
117
+ If a workflow is interrupted during a model call, when the workflow recovers, it restarts the model call and streams its output again.
118
+ Readers that connect afterwards see the model's output once; live readers receive a transient `data-dbos-superseded` chunk indicating the model call has been restarted.
110
119
 
111
- Model calls in a tool-calling loop are each checkpointed individually, so a recovered agent resumes mid-loop.
112
- You should wrap your tool's `execute` in a DBOS step so it is checkpointed too.
120
+ ## Durable Tools
121
+
122
+ To durably checkpoint your agents' tool calls, wrap them in `durableTools`:
113
123
 
114
124
  ```ts
115
125
  import { tool, stepCountIs } from 'ai';
126
+ import { durableTools } from '@dbos-inc/vercel-ai';
116
127
  import { z } from 'zod';
117
128
 
129
+ const tools = durableTools({
130
+ getWeather: tool({
131
+ description: 'Get the weather for a city',
132
+ inputSchema: z.object({ city: z.string() }),
133
+ execute: ({ city }) => fetchWeather(city),
134
+ }),
135
+ });
136
+
118
137
  const agent = DBOS.registerWorkflow(async (question: string) => {
119
- const result = await generateText({
120
- model,
121
- prompt: question,
122
- tools: {
123
- getWeather: tool({
124
- description: 'Get the weather for a city',
125
- inputSchema: z.object({ city: z.string() }),
126
- execute: ({ city }) => DBOS.runStep(() => fetchWeather(city), { name: 'getWeather' }),
127
- }),
128
- },
129
- stopWhen: stepCountIs(10),
130
- });
138
+ const result = await generateText({ model, prompt: question, tools, stopWhen: stepCountIs(10) });
131
139
  return result.text;
132
140
  }, { name: 'weatherAgent' });
133
141
  ```
134
142
 
135
- ### MCP tools
143
+ You can pass step configuration (such as timeouts or retries) to `durableTools`.
144
+ You can set defaults for all tools or configure tools individually.
145
+ Retries are off by default.
136
146
 
137
- `durableMCPTools` wraps an [MCP](https://modelcontextprotocol.io/) client (e.g. from [`@ai-sdk/mcp`](https://www.npmjs.com/package/@ai-sdk/mcp)) so both the tool listing and every tool call run as durable steps.
138
- Each tool call is checkpointed so recovery replays results instead of re-invoking the tool:
147
+ ```ts
148
+ const tools = durableTools(myTools, {
149
+ timeoutMS: 30_000,
150
+ tools: {
151
+ getWeather: { retriesAllowed: true, maxAttempts: 3 },
152
+ },
153
+ });
154
+ ```
155
+
156
+ When using durable tools, to ensure the ordering of parallel tool calls is consistent during recovery, do not await I/O in callbacks that run before a tool executes, such as `onToolExecutionStart`.
157
+
158
+ ### Durable MCP Tools
159
+
160
+ `durableMCPTools` wraps an [MCP](https://modelcontextprotocol.io/) client (for example, from [`@ai-sdk/mcp`](https://www.npmjs.com/package/@ai-sdk/mcp)) so both the tool listing and every tool call run as durable steps:
139
161
 
140
162
  ```ts
141
163
  import { createMCPClient } from '@ai-sdk/mcp';
@@ -157,30 +179,34 @@ const tools = await durableMCPTools(mcpClient, {
157
179
  });
158
180
  ```
159
181
 
160
- ## Concurrency
182
+ ## Durable Subagents
161
183
 
162
- Run **one durable model call at a time within a single workflow**.
163
- DBOS requires workflows to be deterministic, but the AI SDK issues concurrent model calls in nondeterministic order.
164
- To guard against nondeterminism, this integration throws an error if it detects concurrent durable model calls in the same workflow.
165
- Sequential calls (including a normal tool-calling loop, where each model call completes before the next begins) are unaffected.
166
-
167
- To fan out model calls in parallel, give each its own **child workflow**:
184
+ You can delegate complex tasks to **subagents**, which act as tools for their "parent" agent.
185
+ To create a durable subagent, wrap your agent in `agentTool`, then pass it into `durableTools` just like any other tool:
168
186
 
169
187
  ```ts
170
- const summarizeOne = DBOS.registerWorkflow(
171
- async (doc: string) => (await generateText({ model, prompt: `Summarize: ${doc}` })).text,
172
- { name: 'summarizeOne' },
173
- );
188
+ import { ToolLoopAgent } from 'ai';
189
+ import { agentTool, durableTools } from '@dbos-inc/vercel-ai';
174
190
 
175
- const summarizeAll = DBOS.registerWorkflow(async (docs: string[]) => {
176
- const handles = await Promise.all(
177
- docs.map((doc) => DBOS.startWorkflow(summarizeOne)(doc)),
178
- );
179
- return Promise.all(handles.map((h) => h.getResult()));
180
- }, { name: 'summarizeAll' });
191
+ const researcher = new ToolLoopAgent({ model, instructions: 'Research thoroughly.', tools: researchTools });
192
+
193
+ const research = agentTool({
194
+ name: 'research', // subagent name
195
+ description: 'Research a question in depth',
196
+ inputSchema: z.object({ question: z.string() }),
197
+ agent: researcher,
198
+ prompt: ({ question }) => question, // tool input → prompt (or ModelMessage[])
199
+ });
200
+
201
+ const tools = durableTools({ research, getWeather }, { durableStream: 'ui' });
202
+ const orchestrator = new ToolLoopAgent({ model, tools });
181
203
  ```
182
204
 
183
- ## Embeddings
205
+ Internally, subagents are implemented as child workflows of the parent agent workflow, so each call has its own checkpoints and parallel calls are safe.
206
+ Call `agentTool` before `DBOS.launch()`, since it registers that workflow.
207
+ By default, the tool returns the subagent's final text; you can configure this with the `output` parameter.
208
+
209
+ ## Durable Embedding Models
184
210
 
185
211
  `durableEmbeddingCalls` enables durable calls to embedding models:
186
212
 
@@ -193,12 +219,10 @@ const embeddingModel = wrapEmbeddingModel({
193
219
  middleware: durableEmbeddingCalls({ retriesAllowed: true }),
194
220
  });
195
221
 
196
- const { embeddings } = await embedMany({ model: embeddingModel, values: chunks, maxParallelCalls: 1 });
222
+ const { embeddings } = await embedMany({ model: embeddingModel, values: chunks });
197
223
  ```
198
224
 
199
- Pass `maxParallelCalls: 1` when embedding more values than the model's per-call limit. `embedMany` otherwise splits the input into batches and runs them concurrently, which the concurrency guard rejects (their step order would be nondeterministic on replay); `maxParallelCalls: 1` runs the batches sequentially, keeping them durable and replay-safe.
200
-
201
- ## Images
225
+ ## Durable Image Models
202
226
 
203
227
  `durableImageCalls` makes image generation durable:
204
228
 
@@ -0,0 +1,42 @@
1
+ import type { FlexibleSchema, ModelMessage, Tool } from 'ai' with { 'resolution-mode': 'import' };
2
+ /** Marks a tool built by agentTool: durableTools leaves it unwrapped (it is a child workflow, not a step) and binds its durable stream. */
3
+ export declare const AGENT_TOOL: unique symbol;
4
+ type StreamingAgent = {
5
+ stream(options: {
6
+ prompt: string;
7
+ } | {
8
+ messages: ModelMessage[];
9
+ }): PromiseLike<{
10
+ consumeStream(): PromiseLike<void>;
11
+ readonly text: PromiseLike<string>;
12
+ }>;
13
+ };
14
+ export interface AgentToolOptions<INPUT, AGENT extends StreamingAgent, OUTPUT> {
15
+ /** Name of the child workflow; must be unique. */
16
+ name: string;
17
+ description: string;
18
+ inputSchema: FlexibleSchema<INPUT>;
19
+ agent: AGENT;
20
+ /** Turns the tool's input into the sub-agent's prompt. */
21
+ prompt: (input: INPUT) => string | ModelMessage[];
22
+ /** Turns the sub-agent's result into the tool's output (default: its final text); must be serializable. */
23
+ output?: (result: Awaited<ReturnType<AGENT['stream']>>) => OUTPUT | Promise<OUTPUT>;
24
+ /** Record the call in this durable stream: a `data-dbos-subagent` part naming the child workflow, then its output. */
25
+ durableStream?: string;
26
+ /** Name of a DBOS queue to run each child on, e.g. to bound how many sub-agents run at once. */
27
+ queue?: string;
28
+ /** Workflow timeout for each child. */
29
+ timeoutMS?: number;
30
+ }
31
+ export type AgentTool<INPUT, OUTPUT> = Tool<INPUT, OUTPUT> & {
32
+ /** The registered child workflow; call it directly to run the sub-agent without a model in the loop. */
33
+ workflow: (input: INPUT) => Promise<OUTPUT>;
34
+ };
35
+ /**
36
+ * Wraps an agent as a tool whose every call runs as a child workflow: durable at model-call granularity, with its own
37
+ * concurrency guard, safe to call in parallel, and visible as a child in the parent's step list. Call it at module load,
38
+ * before `DBOS.launch()`, since it registers the child workflow.
39
+ */
40
+ export declare function agentTool<INPUT, AGENT extends StreamingAgent, OUTPUT = string>(options: AgentToolOptions<INPUT, AGENT, OUTPUT>): AgentTool<INPUT, OUTPUT>;
41
+ export {};
42
+ //# sourceMappingURL=agent-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-tool.d.ts","sourceRoot":"","sources":["../src/agent-tool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC,OAAO,iBAAiB,EAAE,QAAQ,EAAE,CAAC;AAIlG,2IAA2I;AAC3I,eAAO,MAAM,UAAU,EAAE,OAAO,MAAoD,CAAC;AAkBrF,KAAK,cAAc,GAAG;IACpB,MAAM,CAAC,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,QAAQ,EAAE,YAAY,EAAE,CAAA;KAAE,GAAG,WAAW,CAAC;QAAE,aAAa,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;KAAE,CAAC,CAAC;CAC7J,CAAC;AAEF,MAAM,WAAW,gBAAgB,CAAC,KAAK,EAAE,KAAK,SAAS,cAAc,EAAE,MAAM;IAC3E,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IACnC,KAAK,EAAE,KAAK,CAAC;IACb,0DAA0D;IAC1D,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,GAAG,YAAY,EAAE,CAAC;IAClD,2GAA2G;IAC3G,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpF,sHAAsH;IACtH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gGAAgG;IAChG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,SAAS,CAAC,KAAK,EAAE,MAAM,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG;IAC3D,wGAAwG;IACxG,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC7C,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,SAAS,cAAc,EAAE,MAAM,GAAG,MAAM,EAC5E,OAAO,EAAE,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,GAC9C,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAa1B"}
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AGENT_TOOL = void 0;
4
+ exports.agentTool = agentTool;
5
+ const node_async_hooks_1 = require("node:async_hooks");
6
+ const dbos_sdk_1 = require("@dbos-inc/dbos-sdk");
7
+ const durable_stream_1 = require("./durable-stream");
8
+ const internal_1 = require("./internal");
9
+ /** Marks a tool built by agentTool: durableTools leaves it unwrapped (it is a child workflow, not a step) and binds its durable stream. */
10
+ exports.AGENT_TOOL = Symbol.for('@dbos-inc/vercel-ai/agentTool');
11
+ // Captured at module load, outside any workflow: a cancellation triggered by an abort must not claim a function id in the parent's log.
12
+ const outsideWorkflow = node_async_hooks_1.AsyncLocalStorage.snapshot();
13
+ // The child's row appears shortly after the call starts and a cancel of a missing row is a no-op, so wait for it, giving up once the call has settled.
14
+ async function cancelChild(childID, settled) {
15
+ for (let i = 0; i < 100 && !settled(); i++) {
16
+ if (await dbos_sdk_1.DBOS.getWorkflowStatus(childID)) {
17
+ // The child's own sub-agents are its children; without the cascade they would run on with nobody awaiting them.
18
+ await dbos_sdk_1.DBOS.cancelWorkflow(childID, { cancelChildren: true });
19
+ return;
20
+ }
21
+ await new Promise((resolve) => setTimeout(resolve, 50));
22
+ }
23
+ }
24
+ /**
25
+ * Wraps an agent as a tool whose every call runs as a child workflow: durable at model-call granularity, with its own
26
+ * concurrency guard, safe to call in parallel, and visible as a child in the parent's step list. Call it at module load,
27
+ * before `DBOS.launch()`, since it registers the child workflow.
28
+ */
29
+ function agentTool(options) {
30
+ const { name, agent, prompt, output } = options;
31
+ const run = async (input) => {
32
+ const request = prompt(input);
33
+ // stream, not generate: only streamed calls write to a durable stream.
34
+ const result = await agent.stream(typeof request === 'string' ? { prompt: request } : { messages: request });
35
+ await result.consumeStream();
36
+ return output ? await output(result) : (await result.text);
37
+ };
38
+ const registered = dbos_sdk_1.DBOS.registerWorkflow(run, { name });
39
+ // Unbound on purpose: DBOS's registered invoker reads `this`, and as a method `this` would be the tool object.
40
+ const workflow = (input) => registered(input);
41
+ return build(options, registered, workflow, options.durableStream);
42
+ }
43
+ function build(options, registered, workflow, durableStream) {
44
+ const { name, description, inputSchema, queue, timeoutMS } = options;
45
+ const execute = async (input, execOptions) => {
46
+ if (!(0, internal_1.isInWorkflowFunction)())
47
+ return workflow(input);
48
+ const { toolCallId } = execOptions;
49
+ // The tool call id comes from the checkpointed model output, so the child id is the same on replay and known for cancellation.
50
+ const childID = `${dbos_sdk_1.DBOS.workflowID}-${toolCallId}`;
51
+ // Start and getResult each reserve their function id synchronously here, so parallel calls replay in order.
52
+ const started = dbos_sdk_1.DBOS.startWorkflow(registered, { workflowID: childID, queueName: queue, timeoutMS })(input);
53
+ const pending = dbos_sdk_1.DBOS.getResult(childID);
54
+ started.catch(() => { });
55
+ pending.catch(() => { });
56
+ let settled = false;
57
+ const cancel = () => void outsideWorkflow(() => cancelChild(childID, () => settled)).catch(() => { });
58
+ execOptions.abortSignal?.addEventListener('abort', cancel, { once: true });
59
+ if (execOptions.abortSignal?.aborted)
60
+ cancel();
61
+ try {
62
+ if (durableStream) {
63
+ await (0, durable_stream_1.writeDurableStream)(durableStream, [
64
+ { type: 'data-dbos-subagent', id: toolCallId, data: { toolCallId, workflowID: childID, name } },
65
+ ]);
66
+ }
67
+ await started;
68
+ const result = (await pending);
69
+ // A tool record, not a raw chunk, so the reader masks a sub-agent's error text like any other tool's.
70
+ if (durableStream)
71
+ await (0, durable_stream_1.writeToolRecord)(durableStream, toolCallId, { output: result });
72
+ return result;
73
+ }
74
+ catch (error) {
75
+ if (durableStream)
76
+ await (0, durable_stream_1.writeToolRecord)(durableStream, toolCallId, { errorText: error instanceof Error ? error.message : String(error) });
77
+ throw error;
78
+ }
79
+ finally {
80
+ settled = true;
81
+ execOptions.abortSignal?.removeEventListener('abort', cancel);
82
+ }
83
+ };
84
+ return {
85
+ description,
86
+ inputSchema,
87
+ execute,
88
+ workflow,
89
+ [exports.AGENT_TOOL]: (key) => build(options, registered, workflow, key),
90
+ };
91
+ }
92
+ //# sourceMappingURL=agent-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-tool.js","sourceRoot":"","sources":["../src/agent-tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ import type { UIMessageChunk } from 'ai' with { 'resolution-mode': 'import' };
2
+ import type { LanguageModelV4FinishReason, LanguageModelV4StreamPart } from '@ai-sdk/provider' with { 'resolution-mode': 'import' };
3
+ /** Durable stream config: the DBOS stream key, or the key plus batching limits for the model step's writes. */
4
+ export type DurableStreamOptions = string | {
5
+ key: string;
6
+ maxBatchParts?: number;
7
+ maxBatchDelayMs?: number;
8
+ };
9
+ /** One DBOS stream value; the reader turns these into AI SDK UI message chunks. */
10
+ export type DurableStreamRecord = {
11
+ kind: 'model';
12
+ step: number;
13
+ attempt: string;
14
+ parts: LanguageModelV4StreamPart[];
15
+ } | {
16
+ kind: 'model-end';
17
+ step: number;
18
+ attempt: string;
19
+ finishReason?: LanguageModelV4FinishReason;
20
+ aborted?: true;
21
+ } | {
22
+ kind: 'tool';
23
+ step: number;
24
+ attempt: number;
25
+ toolCallId: string;
26
+ output?: unknown;
27
+ errorText?: string;
28
+ } | {
29
+ kind: 'ui';
30
+ step?: number;
31
+ attempt?: number;
32
+ chunks: UIMessageChunk[];
33
+ } | {
34
+ kind: 'end';
35
+ finishReason: string;
36
+ };
37
+ interface ResolvedDurableStream {
38
+ key: string;
39
+ maxBatchParts: number;
40
+ maxBatchDelayMs: number;
41
+ }
42
+ export declare function resolveDurableStream(options: DurableStreamOptions | undefined): ResolvedDurableStream | undefined;
43
+ /** Batches a live model step's parts into step-scope stream writes; nothing is written on replay because the step body does not run. */
44
+ export declare class ModelStreamWriter {
45
+ private readonly config;
46
+ private pending;
47
+ private chain;
48
+ private failure;
49
+ private timer;
50
+ private readonly step;
51
+ private readonly attempt;
52
+ constructor(config: ResolvedDurableStream);
53
+ push(part: LanguageModelV4StreamPart): void;
54
+ /** Flushes, records how the call ended, and resolves once every write is durable; a write that failed after retries fails the call here. */
55
+ end(outcome: {
56
+ finishReason: LanguageModelV4FinishReason;
57
+ } | {
58
+ aborted: true;
59
+ }): Promise<void>;
60
+ /** After a failure: flush what streamed so the record matches what the consumer saw; the stream's end then comes from the workflow's status. */
61
+ abandon(): Promise<void>;
62
+ private flush;
63
+ private write;
64
+ }
65
+ /** Records a tool call's outcome from inside its step. */
66
+ export declare function writeToolRecord(key: string, toolCallId: string, outcome: {
67
+ output: unknown;
68
+ } | {
69
+ errorText: string;
70
+ }): Promise<void>;
71
+ /**
72
+ * Appends UI message chunks to a durable stream. From a step the write is cheap and at-least-once, so give data parts
73
+ * stable ids; from workflow code it is a checkpointed step, so the number of calls must be deterministic.
74
+ */
75
+ export declare function writeDurableStream(key: string, chunks: UIMessageChunk[]): Promise<void>;
76
+ /** Marks the end of the turn explicitly and closes the stream; without it the reader infers the end from the last model call or the workflow's status. */
77
+ export declare function closeDurableStream(key: string, finishReason?: string): Promise<void>;
78
+ /** What the reader needs from DBOS: the `DBOS` class in a launched process, or a `DBOSClient` anywhere else. */
79
+ export interface DurableStreamSource {
80
+ readStream<T>(workflowID: string, key: string, options?: {
81
+ offset?: number;
82
+ }): AsyncGenerator<T, void, unknown>;
83
+ readStreamOffset<T>(workflowID: string, key: string, offset: number, options?: {
84
+ timeoutSeconds?: number;
85
+ }): Promise<T>;
86
+ retrieveWorkflow(workflowID: string): {
87
+ getStatus(): Promise<{
88
+ status: string;
89
+ error?: unknown;
90
+ } | null>;
91
+ };
92
+ }
93
+ export interface ReadDurableStreamOptions {
94
+ workflowID: string;
95
+ key: string;
96
+ /** Id for the `start` chunk; omitted on a resume (`offset` > 0). */
97
+ messageId?: string;
98
+ /** Number of records already consumed, from the last `data-dbos-offset` chunk. */
99
+ offset?: number;
100
+ /** Defaults to `DBOS`; pass a `DBOSClient` to read from a process that has not launched DBOS. */
101
+ client?: DurableStreamSource;
102
+ /** Emit reasoning parts (default true, as in the AI SDK). */
103
+ sendReasoning?: boolean;
104
+ /** Emit source parts (default false, as in the AI SDK). */
105
+ sendSources?: boolean;
106
+ /** Text sent to clients for a workflow or tool error; defaults to a generic message, as in the AI SDK, so server details stay private. */
107
+ onError?: (error: unknown) => string;
108
+ }
109
+ /** Reads a durable stream as AI SDK UI message chunks, live or after the fact, resuming from `offset`. */
110
+ export declare function readDurableStream(options: ReadDurableStreamOptions): ReadableStream<UIMessageChunk>;
111
+ export {};
112
+ //# sourceMappingURL=durable-stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-stream.d.ts","sourceRoot":"","sources":["../src/durable-stream.ts"],"names":[],"mappings":"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"}