@ai-sdk/workflow 0.0.0-6b196531-20260710185421

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.
@@ -0,0 +1,26 @@
1
+ import type { Experimental_SandboxSession as SandboxSession } from 'ai';
2
+
3
+ /**
4
+ * Build a minimal {@link SandboxSession} for tests. Only `run` returns a usable
5
+ * result by default; every other method throws so tests stay explicit about
6
+ * what they exercise. Pass `overrides` to customize individual methods.
7
+ */
8
+ export function createTestSandbox(
9
+ overrides: Partial<SandboxSession> = {},
10
+ ): SandboxSession {
11
+ const notImplemented = () => {
12
+ throw new Error('not implemented in test sandbox');
13
+ };
14
+ return {
15
+ description: 'test sandbox',
16
+ readFile: notImplemented,
17
+ readBinaryFile: notImplemented,
18
+ readTextFile: notImplemented,
19
+ writeFile: notImplemented,
20
+ writeBinaryFile: notImplemented,
21
+ writeTextFile: notImplemented,
22
+ spawn: notImplemented,
23
+ run: async () => ({ exitCode: 0, stdout: '', stderr: '' }),
24
+ ...overrides,
25
+ };
26
+ }
@@ -0,0 +1,238 @@
1
+ import type {
2
+ Experimental_LanguageModelStreamPart as ModelCallStreamPart,
3
+ ToolSet,
4
+ UIMessageChunk,
5
+ } from 'ai';
6
+
7
+ /**
8
+ * Convert a single ModelCallStreamPart to a UIMessageChunk.
9
+ * Returns undefined for parts that don't map to UI chunks.
10
+ */
11
+ export function toUIMessageChunk(
12
+ part: ModelCallStreamPart<ToolSet>,
13
+ ): UIMessageChunk | undefined {
14
+ switch (part.type) {
15
+ case 'text-start':
16
+ return {
17
+ type: 'text-start',
18
+ id: part.id,
19
+ ...(part.providerMetadata != null
20
+ ? { providerMetadata: part.providerMetadata }
21
+ : {}),
22
+ };
23
+
24
+ case 'text-delta':
25
+ return {
26
+ type: 'text-delta',
27
+ id: part.id,
28
+ delta: part.text,
29
+ ...(part.providerMetadata != null
30
+ ? { providerMetadata: part.providerMetadata }
31
+ : {}),
32
+ };
33
+
34
+ case 'text-end':
35
+ return {
36
+ type: 'text-end',
37
+ id: part.id,
38
+ ...(part.providerMetadata != null
39
+ ? { providerMetadata: part.providerMetadata }
40
+ : {}),
41
+ };
42
+
43
+ case 'reasoning-start':
44
+ return {
45
+ type: 'reasoning-start',
46
+ id: part.id,
47
+ ...(part.providerMetadata != null
48
+ ? { providerMetadata: part.providerMetadata }
49
+ : {}),
50
+ };
51
+
52
+ case 'reasoning-delta':
53
+ return {
54
+ type: 'reasoning-delta',
55
+ id: part.id,
56
+ delta: part.text,
57
+ ...(part.providerMetadata != null
58
+ ? { providerMetadata: part.providerMetadata }
59
+ : {}),
60
+ };
61
+
62
+ case 'reasoning-end':
63
+ return {
64
+ type: 'reasoning-end',
65
+ id: part.id,
66
+ ...(part.providerMetadata != null
67
+ ? { providerMetadata: part.providerMetadata }
68
+ : {}),
69
+ };
70
+
71
+ case 'file': {
72
+ const file = part.file;
73
+ // GeneratedFile.base64 always has data (lazy-converted from Uint8Array if needed)
74
+ return {
75
+ type: 'file',
76
+ mediaType: file.mediaType,
77
+ url: `data:${file.mediaType};base64,${file.base64}`,
78
+ };
79
+ }
80
+
81
+ case 'source': {
82
+ if (part.sourceType === 'url') {
83
+ return {
84
+ type: 'source-url',
85
+ sourceId: part.id,
86
+ url: part.url,
87
+ title: part.title,
88
+ ...(part.providerMetadata != null
89
+ ? { providerMetadata: part.providerMetadata }
90
+ : {}),
91
+ };
92
+ }
93
+ if (part.sourceType === 'document') {
94
+ return {
95
+ type: 'source-document',
96
+ sourceId: part.id,
97
+ mediaType: part.mediaType,
98
+ title: part.title,
99
+ filename: part.filename,
100
+ ...(part.providerMetadata != null
101
+ ? { providerMetadata: part.providerMetadata }
102
+ : {}),
103
+ };
104
+ }
105
+ return undefined;
106
+ }
107
+
108
+ case 'tool-input-start':
109
+ return {
110
+ type: 'tool-input-start',
111
+ toolCallId: part.id,
112
+ toolName: part.toolName,
113
+ ...(part.providerExecuted != null
114
+ ? { providerExecuted: part.providerExecuted }
115
+ : {}),
116
+ };
117
+
118
+ case 'tool-input-delta':
119
+ return {
120
+ type: 'tool-input-delta',
121
+ toolCallId: part.id,
122
+ inputTextDelta: part.delta,
123
+ };
124
+
125
+ case 'tool-call': {
126
+ // parseToolCall adds invalid/error at runtime for failed parses
127
+ const toolCallPart = part as typeof part & {
128
+ invalid?: boolean;
129
+ error?: unknown;
130
+ };
131
+ if (toolCallPart.invalid) {
132
+ return {
133
+ type: 'tool-input-error',
134
+ toolCallId: toolCallPart.toolCallId,
135
+ toolName: toolCallPart.toolName,
136
+ input: toolCallPart.input,
137
+ errorText:
138
+ toolCallPart.error instanceof Error
139
+ ? toolCallPart.error.message
140
+ : String(toolCallPart.error ?? 'Invalid tool call'),
141
+ };
142
+ }
143
+ return {
144
+ type: 'tool-input-available',
145
+ toolCallId: part.toolCallId,
146
+ toolName: part.toolName,
147
+ input: part.input,
148
+ ...(part.providerExecuted != null
149
+ ? { providerExecuted: part.providerExecuted }
150
+ : {}),
151
+ ...(part.providerMetadata != null
152
+ ? { providerMetadata: part.providerMetadata }
153
+ : {}),
154
+ };
155
+ }
156
+
157
+ case 'tool-result':
158
+ return {
159
+ type: 'tool-output-available',
160
+ toolCallId: part.toolCallId,
161
+ output: part.output,
162
+ };
163
+
164
+ case 'tool-error':
165
+ return {
166
+ type: 'tool-output-error',
167
+ toolCallId: part.toolCallId,
168
+ errorText:
169
+ part.error instanceof Error ? part.error.message : String(part.error),
170
+ };
171
+
172
+ case 'error': {
173
+ const error = part.error;
174
+ return {
175
+ type: 'error',
176
+ errorText: error instanceof Error ? error.message : String(error),
177
+ };
178
+ }
179
+
180
+ // These don't produce UI chunks
181
+ case 'tool-input-end':
182
+ case 'model-call-start':
183
+ case 'model-call-response-metadata':
184
+ case 'model-call-end':
185
+ case 'raw':
186
+ return undefined;
187
+
188
+ default: {
189
+ // Pass through tool-approval-request, step boundaries, and other
190
+ // chunks as-is. Step boundaries (finish-step/start-step) are not
191
+ // standard ModelCallStreamPart types but are written by the
192
+ // WorkflowAgent between tool execution and the next model step
193
+ // to ensure proper message splitting in convertToModelMessages.
194
+ const passthroughPart = part as any;
195
+ if (passthroughPart.type === 'tool-approval-request') {
196
+ return {
197
+ type: 'tool-approval-request',
198
+ approvalId: passthroughPart.approvalId,
199
+ toolCallId: passthroughPart.toolCallId,
200
+ } as UIMessageChunk;
201
+ }
202
+ if (
203
+ passthroughPart.type === 'finish-step' ||
204
+ passthroughPart.type === 'start-step' ||
205
+ passthroughPart.type === 'tool-output-denied'
206
+ ) {
207
+ return passthroughPart as UIMessageChunk;
208
+ }
209
+ return undefined;
210
+ }
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Create a TransformStream that converts ModelCallStreamPart to UIMessageChunk.
216
+ * Wraps toUIMessageChunk with start/start-step/finish-step lifecycle chunks.
217
+ */
218
+ export function createModelCallToUIChunkTransform(): TransformStream<
219
+ ModelCallStreamPart<ToolSet>,
220
+ UIMessageChunk
221
+ > {
222
+ return new TransformStream<ModelCallStreamPart<ToolSet>, UIMessageChunk>({
223
+ start: controller => {
224
+ controller.enqueue({ type: 'start' });
225
+ controller.enqueue({ type: 'start-step' });
226
+ },
227
+ flush: controller => {
228
+ controller.enqueue({ type: 'finish-step' });
229
+ controller.enqueue({ type: 'finish' });
230
+ },
231
+ transform: (part, controller) => {
232
+ const uiChunk = toUIMessageChunk(part);
233
+ if (uiChunk) {
234
+ controller.enqueue(uiChunk);
235
+ }
236
+ },
237
+ });
238
+ }
package/src/types.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Shared types for AI SDK V4.
3
+ */
4
+ import type { LanguageModelV4 } from '@ai-sdk/provider';
5
+
6
+ /**
7
+ * Language model type for AI SDK V4.
8
+ *
9
+ * This is a simple alias for LanguageModelV4 from @ai-sdk/provider.
10
+ */
11
+ export type CompatibleLanguageModel = LanguageModelV4;