@ai-sdk/workflow 1.0.70 → 2.0.1
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/CHANGELOG.md +26 -0
- package/README.md +1 -1
- package/dist/index.d.ts +28 -10
- package/dist/index.js +31 -6
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
- package/src/do-stream-step.ts +10 -2
- package/src/normalize-ui-message-stream.ts +10 -0
- package/src/stream-text-iterator.ts +1 -1
- package/src/to-ui-message-chunk.ts +40 -14
- package/src/workflow-agent.ts +1 -1
- package/src/workflow-chat-transport.ts +10 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/workflow",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "WorkflowAgent for building AI agents with AI SDK",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,21 +27,22 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"ajv": "^8.20.0",
|
|
30
|
-
"@ai-sdk/provider
|
|
31
|
-
"ai": "
|
|
32
|
-
"
|
|
30
|
+
"@ai-sdk/provider": "4.0.7",
|
|
31
|
+
"@ai-sdk/provider-utils": "5.0.28",
|
|
32
|
+
"ai": "7.0.71"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "22.19.19",
|
|
36
|
-
"@workflow/vitest": "
|
|
36
|
+
"@workflow/vitest": "5.0.0-beta.42",
|
|
37
37
|
"tsup": "^8.5.1",
|
|
38
38
|
"typescript": "5.8.3",
|
|
39
39
|
"vitest": "4.1.6",
|
|
40
|
-
"workflow": "
|
|
41
|
-
"zod": "
|
|
40
|
+
"workflow": "5.0.0-beta.42",
|
|
41
|
+
"zod": "4.4.3",
|
|
42
42
|
"@vercel/ai-tsconfig": "0.0.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
+
"workflow": "^5.0.0-beta.42",
|
|
45
46
|
"zod": "^3.25.76 || ^4.1.8"
|
|
46
47
|
},
|
|
47
48
|
"engines": {
|
package/src/do-stream-step.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { isAbortError } from '@ai-sdk/provider-utils';
|
|
|
6
6
|
import {
|
|
7
7
|
experimental_streamLanguageModelCall as streamModelCall,
|
|
8
8
|
gateway,
|
|
9
|
-
type Experimental_LanguageModelStreamPart
|
|
9
|
+
type Experimental_LanguageModelStreamPart,
|
|
10
10
|
type FinishReason,
|
|
11
11
|
type LanguageModel,
|
|
12
12
|
type LanguageModelUsage,
|
|
@@ -22,7 +22,10 @@ import {
|
|
|
22
22
|
resolveSerializableTools,
|
|
23
23
|
type SerializableToolDef,
|
|
24
24
|
} from './serializable-schema.js';
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
export type ModelCallStreamPart<TTools extends ToolSet = ToolSet> =
|
|
27
|
+
| Experimental_LanguageModelStreamPart<TTools>
|
|
28
|
+
| { type: 'reset-step' };
|
|
26
29
|
|
|
27
30
|
export type ModelStopCondition = StopCondition<NoInfer<ToolSet>, any>;
|
|
28
31
|
|
|
@@ -248,6 +251,11 @@ export async function doStreamStep(
|
|
|
248
251
|
const writer = writable?.getWriter();
|
|
249
252
|
|
|
250
253
|
try {
|
|
254
|
+
// A workflow step can be retried after already writing partial output.
|
|
255
|
+
// Reset the current UI step before every attempt so a retry invalidates
|
|
256
|
+
// chunks left behind by an earlier execution.
|
|
257
|
+
await writer?.write({ type: 'reset-step' });
|
|
258
|
+
|
|
251
259
|
for await (const part of modelStream) {
|
|
252
260
|
switch (part.type) {
|
|
253
261
|
case 'text-delta':
|
|
@@ -115,6 +115,16 @@ export async function* normalizeUIMessageStreamParts(
|
|
|
115
115
|
|
|
116
116
|
for await (const chunk of source) {
|
|
117
117
|
switch (chunk.type) {
|
|
118
|
+
case 'reset-step':
|
|
119
|
+
// A retried model-call step starts a new frame. Forget parts from the
|
|
120
|
+
// invalidated attempt so reused ids are framed normally.
|
|
121
|
+
text.open.clear();
|
|
122
|
+
text.ended.clear();
|
|
123
|
+
reasoning.open.clear();
|
|
124
|
+
reasoning.ended.clear();
|
|
125
|
+
yield chunk;
|
|
126
|
+
break;
|
|
127
|
+
|
|
118
128
|
case 'finish-step':
|
|
119
129
|
// The consumer clears its active-part maps here, so part ids may be
|
|
120
130
|
// legitimately reused in the next step. Reset to match.
|
|
@@ -7,7 +7,6 @@ import type {
|
|
|
7
7
|
import type { Context } from '@ai-sdk/provider-utils';
|
|
8
8
|
import {
|
|
9
9
|
experimental_filterActiveTools as filterActiveTools,
|
|
10
|
-
type Experimental_LanguageModelStreamPart as ModelCallStreamPart,
|
|
11
10
|
type Experimental_SandboxSession as SandboxSession,
|
|
12
11
|
type Instructions,
|
|
13
12
|
type LanguageModel,
|
|
@@ -22,6 +21,7 @@ import { createRestrictedTelemetryDispatcher } from 'ai/internal';
|
|
|
22
21
|
import {
|
|
23
22
|
type DoStreamStepRawResult,
|
|
24
23
|
doStreamStep,
|
|
24
|
+
type ModelCallStreamPart,
|
|
25
25
|
type ModelStopCondition,
|
|
26
26
|
type ParsedToolCall,
|
|
27
27
|
type ProviderExecutedToolResult,
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
ToolSet,
|
|
4
|
-
UIMessageChunk,
|
|
5
|
-
} from 'ai';
|
|
1
|
+
import type { ToolSet, UIMessageChunk } from 'ai';
|
|
2
|
+
import type { ModelCallStreamPart } from './do-stream-step.js';
|
|
6
3
|
|
|
7
4
|
/**
|
|
8
5
|
* Convert a single ModelCallStreamPart to a UIMessageChunk.
|
|
@@ -12,6 +9,9 @@ export function toUIMessageChunk(
|
|
|
12
9
|
part: ModelCallStreamPart<ToolSet>,
|
|
13
10
|
): UIMessageChunk | undefined {
|
|
14
11
|
switch (part.type) {
|
|
12
|
+
case 'reset-step':
|
|
13
|
+
return { type: 'reset-step' };
|
|
14
|
+
|
|
15
15
|
case 'text-start':
|
|
16
16
|
return {
|
|
17
17
|
type: 'text-start',
|
|
@@ -214,24 +214,50 @@ export function toUIMessageChunk(
|
|
|
214
214
|
/**
|
|
215
215
|
* Create a TransformStream that converts ModelCallStreamPart to UIMessageChunk.
|
|
216
216
|
* Wraps toUIMessageChunk with start/start-step/finish-step lifecycle chunks.
|
|
217
|
+
*
|
|
218
|
+
* When resuming a stream, provide `uiStartIndex` and replay the source
|
|
219
|
+
* ModelCallStreamPart stream from index 0. The transform will omit the UI
|
|
220
|
+
* chunks that the client has already received. Raw model stream parts and UI
|
|
221
|
+
* message chunks do not have a one-to-one relationship, so a UI chunk index
|
|
222
|
+
* must not be used as the source stream's start index.
|
|
217
223
|
*/
|
|
218
|
-
export function createModelCallToUIChunkTransform(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
224
|
+
export function createModelCallToUIChunkTransform({
|
|
225
|
+
uiStartIndex = 0,
|
|
226
|
+
}: {
|
|
227
|
+
/**
|
|
228
|
+
* Number of transformed UIMessageChunks to omit from the output.
|
|
229
|
+
* Must be a non-negative safe integer.
|
|
230
|
+
*/
|
|
231
|
+
uiStartIndex?: number;
|
|
232
|
+
} = {}): TransformStream<ModelCallStreamPart<ToolSet>, UIMessageChunk> {
|
|
233
|
+
if (!Number.isSafeInteger(uiStartIndex) || uiStartIndex < 0) {
|
|
234
|
+
throw new RangeError('uiStartIndex must be a non-negative safe integer');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let uiChunkIndex = 0;
|
|
238
|
+
|
|
239
|
+
const enqueue = (
|
|
240
|
+
controller: TransformStreamDefaultController<UIMessageChunk>,
|
|
241
|
+
chunk: UIMessageChunk,
|
|
242
|
+
) => {
|
|
243
|
+
if (uiChunkIndex++ >= uiStartIndex) {
|
|
244
|
+
controller.enqueue(chunk);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
222
248
|
return new TransformStream<ModelCallStreamPart<ToolSet>, UIMessageChunk>({
|
|
223
249
|
start: controller => {
|
|
224
|
-
|
|
225
|
-
|
|
250
|
+
enqueue(controller, { type: 'start' });
|
|
251
|
+
enqueue(controller, { type: 'start-step' });
|
|
226
252
|
},
|
|
227
253
|
flush: controller => {
|
|
228
|
-
|
|
229
|
-
|
|
254
|
+
enqueue(controller, { type: 'finish-step' });
|
|
255
|
+
enqueue(controller, { type: 'finish' });
|
|
230
256
|
},
|
|
231
257
|
transform: (part, controller) => {
|
|
232
258
|
const uiChunk = toUIMessageChunk(part);
|
|
233
259
|
if (uiChunk) {
|
|
234
|
-
|
|
260
|
+
enqueue(controller, uiChunk);
|
|
235
261
|
}
|
|
236
262
|
},
|
|
237
263
|
});
|
package/src/workflow-agent.ts
CHANGED
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
type FinishReason,
|
|
21
21
|
type LanguageModelResponseMetadata,
|
|
22
22
|
type LanguageModelUsage,
|
|
23
|
-
type Experimental_LanguageModelStreamPart as ModelCallStreamPart,
|
|
24
23
|
type ModelMessage,
|
|
25
24
|
type StepResult,
|
|
26
25
|
type StopCondition,
|
|
@@ -45,6 +44,7 @@ import {
|
|
|
45
44
|
validateApprovedToolApprovals,
|
|
46
45
|
} from 'ai/internal';
|
|
47
46
|
import { createLanguageModelToolResultOutput } from './create-language-model-tool-result-output.js';
|
|
47
|
+
import type { ModelCallStreamPart } from './do-stream-step.js';
|
|
48
48
|
import { streamTextIterator } from './stream-text-iterator.js';
|
|
49
49
|
|
|
50
50
|
// Re-export for consumers
|
|
@@ -59,6 +59,10 @@ function createOrphanFilter(): OrphanFilter {
|
|
|
59
59
|
|
|
60
60
|
function shouldDrop(chunk: UIMessageChunk): boolean {
|
|
61
61
|
switch (chunk.type) {
|
|
62
|
+
case 'reset-step':
|
|
63
|
+
seenStartedIds.clear();
|
|
64
|
+
seenStartedToolCallIds.clear();
|
|
65
|
+
return false;
|
|
62
66
|
case 'text-start':
|
|
63
67
|
case 'reasoning-start':
|
|
64
68
|
seenStartedIds.add(chunk.id);
|
|
@@ -109,7 +113,8 @@ export interface ReconnectToStreamOptions {
|
|
|
109
113
|
abortSignal?: AbortSignal;
|
|
110
114
|
/**
|
|
111
115
|
* Override the `startIndex` for this reconnection.
|
|
112
|
-
* Negative values read from the end
|
|
116
|
+
* Negative values read from the end when the server's durable stream and
|
|
117
|
+
* tail-index header use the same UIMessageChunk index space.
|
|
113
118
|
* When omitted, falls back to the constructor's `initialStartIndex`.
|
|
114
119
|
*/
|
|
115
120
|
startIndex?: number;
|
|
@@ -174,9 +179,10 @@ export interface WorkflowChatTransportOptions<UI_MESSAGE extends UIMessage> {
|
|
|
174
179
|
/**
|
|
175
180
|
* Default `startIndex` to use when reconnecting to a stream without a known
|
|
176
181
|
* chunk position (i.e. the initial reconnection, not a retry).
|
|
177
|
-
* Negative values read from the end of
|
|
178
|
-
* last 10 chunks), which is useful for resuming a chat UI
|
|
179
|
-
* refresh without replaying the full conversation.
|
|
182
|
+
* Negative values read from the end of a durable UIMessageChunk stream (e.g.
|
|
183
|
+
* `-10` fetches the last 10 chunks), which is useful for resuming a chat UI
|
|
184
|
+
* after a page refresh without replaying the full conversation. Raw
|
|
185
|
+
* ModelCallStreamPart streams do not support negative UI chunk indexes.
|
|
180
186
|
*
|
|
181
187
|
* Can be overridden per-call via `ReconnectToStreamOptions.startIndex`.
|
|
182
188
|
*
|