ai 6.0.34 → 6.0.36
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 +15 -0
- package/dist/index.d.mts +50 -21
- package/dist/index.d.ts +50 -21
- package/dist/index.js +348 -286
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +280 -219
- package/dist/index.mjs.map +1 -1
- package/dist/internal/index.js +1 -1
- package/dist/internal/index.mjs +1 -1
- package/docs/02-foundations/04-tools.mdx +1 -1
- package/docs/07-reference/05-ai-sdk-errors/ai-ui-message-stream-error.mdx +67 -0
- package/package.json +3 -3
- package/src/error/index.ts +1 -0
- package/src/error/ui-message-stream-error.ts +48 -0
- package/src/ui/process-ui-message-stream.test.ts +242 -0
- package/src/ui/process-ui-message-stream.ts +51 -3
package/dist/internal/index.js
CHANGED
|
@@ -153,7 +153,7 @@ var import_provider_utils2 = require("@ai-sdk/provider-utils");
|
|
|
153
153
|
var import_provider_utils3 = require("@ai-sdk/provider-utils");
|
|
154
154
|
|
|
155
155
|
// src/version.ts
|
|
156
|
-
var VERSION = true ? "6.0.
|
|
156
|
+
var VERSION = true ? "6.0.36" : "0.0.0-test";
|
|
157
157
|
|
|
158
158
|
// src/util/download/download.ts
|
|
159
159
|
var download = async ({ url }) => {
|
package/dist/internal/index.mjs
CHANGED
|
@@ -128,7 +128,7 @@ import {
|
|
|
128
128
|
} from "@ai-sdk/provider-utils";
|
|
129
129
|
|
|
130
130
|
// src/version.ts
|
|
131
|
-
var VERSION = true ? "6.0.
|
|
131
|
+
var VERSION = true ? "6.0.36" : "0.0.0-test";
|
|
132
132
|
|
|
133
133
|
// src/util/download/download.ts
|
|
134
134
|
var download = async ({ url }) => {
|
|
@@ -24,7 +24,7 @@ and [`streamText`](/docs/reference/ai-sdk-core/stream-text) by passing one or mo
|
|
|
24
24
|
A tool consists of three properties:
|
|
25
25
|
|
|
26
26
|
- **`description`**: An optional description of the tool that can influence when the tool is picked.
|
|
27
|
-
- **`inputSchema`**: A [Zod schema](/docs/
|
|
27
|
+
- **`inputSchema`**: A [Zod schema](/docs/reference/ai-sdk-core/zod-schema) or a [JSON schema](/docs/reference/ai-sdk-core/json-schema) that defines the input required for the tool to run. The schema is consumed by the LLM, and also used to validate the LLM tool calls.
|
|
28
28
|
- **`execute`**: An optional async function that is called with the arguments from the tool call.
|
|
29
29
|
|
|
30
30
|
<Note>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: AI_UIMessageStreamError
|
|
3
|
+
description: Learn how to fix AI_UIMessageStreamError
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# AI_UIMessageStreamError
|
|
7
|
+
|
|
8
|
+
This error occurs when a UI message stream contains invalid or out-of-sequence chunks.
|
|
9
|
+
|
|
10
|
+
Common causes:
|
|
11
|
+
|
|
12
|
+
- Receiving a `text-delta` chunk without a preceding `text-start` chunk
|
|
13
|
+
- Receiving a `text-end` chunk without a preceding `text-start` chunk
|
|
14
|
+
- Receiving a `reasoning-delta` chunk without a preceding `reasoning-start` chunk
|
|
15
|
+
- Receiving a `reasoning-end` chunk without a preceding `reasoning-start` chunk
|
|
16
|
+
- Receiving a `tool-input-delta` chunk without a preceding `tool-input-start` chunk
|
|
17
|
+
- Attempting to access a tool invocation that doesn't exist
|
|
18
|
+
|
|
19
|
+
This error often surfaces when an upstream request fails **before any tokens are streamed** and a custom transport tries to write an inline error message to the UI stream without the proper start chunk.
|
|
20
|
+
|
|
21
|
+
## Properties
|
|
22
|
+
|
|
23
|
+
- `chunkType`: The type of chunk that caused the error (e.g., `text-delta`, `reasoning-end`, `tool-input-delta`)
|
|
24
|
+
- `chunkId`: The ID associated with the failing chunk (part ID or toolCallId)
|
|
25
|
+
- `message`: The error message with details about what went wrong
|
|
26
|
+
|
|
27
|
+
## Checking for this Error
|
|
28
|
+
|
|
29
|
+
You can check if an error is an instance of `AI_UIMessageStreamError` using:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { UIMessageStreamError } from 'ai';
|
|
33
|
+
|
|
34
|
+
if (UIMessageStreamError.isInstance(error)) {
|
|
35
|
+
console.log('Chunk type:', error.chunkType);
|
|
36
|
+
console.log('Chunk ID:', error.chunkId);
|
|
37
|
+
// Handle the error
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Common Solutions
|
|
42
|
+
|
|
43
|
+
1. **Ensure proper chunk ordering**: Always send a `*-start` chunk before any `*-delta` or `*-end` chunks for the same ID:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// Correct order
|
|
47
|
+
writer.write({ type: 'text-start', id: 'my-text' });
|
|
48
|
+
writer.write({ type: 'text-delta', id: 'my-text', delta: 'Hello' });
|
|
49
|
+
writer.write({ type: 'text-end', id: 'my-text' });
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
2. **Verify IDs match**: Ensure the `id` used in `*-delta` and `*-end` chunks matches the `id` used in the corresponding `*-start` chunk.
|
|
53
|
+
|
|
54
|
+
3. **Handle error paths correctly**: When writing error messages in custom transports, ensure you emit the full start/delta/end sequence:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// When handling errors in custom transports
|
|
58
|
+
writer.write({ type: 'text-start', id: errorId });
|
|
59
|
+
writer.write({
|
|
60
|
+
type: 'text-delta',
|
|
61
|
+
id: errorId,
|
|
62
|
+
delta: 'Request failed...',
|
|
63
|
+
});
|
|
64
|
+
writer.write({ type: 'text-end', id: errorId });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
4. **Check stream producer logic**: Review your streaming implementation to ensure chunks are sent in the correct order, especially when dealing with concurrent operations or merged streams.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.36",
|
|
4
4
|
"description": "AI SDK by Vercel - The AI Toolkit for TypeScript and JavaScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@opentelemetry/api": "1.9.0",
|
|
45
|
-
"@ai-sdk/gateway": "3.0.
|
|
45
|
+
"@ai-sdk/gateway": "3.0.15",
|
|
46
46
|
"@ai-sdk/provider": "3.0.3",
|
|
47
|
-
"@ai-sdk/provider-utils": "4.0.
|
|
47
|
+
"@ai-sdk/provider-utils": "4.0.7"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@edge-runtime/vm": "^5.0.0",
|
package/src/error/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ export { NoSpeechGeneratedError } from './no-speech-generated-error';
|
|
|
26
26
|
export { NoSuchToolError } from './no-such-tool-error';
|
|
27
27
|
export { ToolCallRepairError } from './tool-call-repair-error';
|
|
28
28
|
export { UnsupportedModelVersionError } from './unsupported-model-version-error';
|
|
29
|
+
export { UIMessageStreamError } from './ui-message-stream-error';
|
|
29
30
|
|
|
30
31
|
export { InvalidDataContentError } from '../prompt/invalid-data-content-error';
|
|
31
32
|
export { InvalidMessageRoleError } from '../prompt/invalid-message-role-error';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { AISDKError } from '@ai-sdk/provider';
|
|
2
|
+
|
|
3
|
+
const name = 'AI_UIMessageStreamError';
|
|
4
|
+
const marker = `vercel.ai.error.${name}`;
|
|
5
|
+
const symbol = Symbol.for(marker);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Error thrown when a UI message stream contains invalid or out-of-sequence chunks.
|
|
9
|
+
*
|
|
10
|
+
* This typically occurs when:
|
|
11
|
+
* - A delta chunk is received without a corresponding start chunk
|
|
12
|
+
* - An end chunk is received without a corresponding start chunk
|
|
13
|
+
* - A tool invocation is not found for the given toolCallId
|
|
14
|
+
*
|
|
15
|
+
* @see https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-ui-message-stream-error
|
|
16
|
+
*/
|
|
17
|
+
export class UIMessageStreamError extends AISDKError {
|
|
18
|
+
private readonly [symbol] = true; // used in isInstance
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The type of chunk that caused the error (e.g., 'text-delta', 'reasoning-end').
|
|
22
|
+
*/
|
|
23
|
+
readonly chunkType: string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The ID associated with the failing chunk (part ID or toolCallId).
|
|
27
|
+
*/
|
|
28
|
+
readonly chunkId: string;
|
|
29
|
+
|
|
30
|
+
constructor({
|
|
31
|
+
chunkType,
|
|
32
|
+
chunkId,
|
|
33
|
+
message,
|
|
34
|
+
}: {
|
|
35
|
+
chunkType: string;
|
|
36
|
+
chunkId: string;
|
|
37
|
+
message: string;
|
|
38
|
+
}) {
|
|
39
|
+
super({ name, message });
|
|
40
|
+
|
|
41
|
+
this.chunkType = chunkType;
|
|
42
|
+
this.chunkId = chunkId;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static isInstance(error: unknown): error is UIMessageStreamError {
|
|
46
|
+
return AISDKError.hasMarker(error, marker);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from './process-ui-message-stream';
|
|
9
9
|
import { InferUIMessageData, UIMessage } from './ui-messages';
|
|
10
10
|
import { beforeEach, describe, it, expect, vi } from 'vitest';
|
|
11
|
+
import { UIMessageStreamError } from '../error/ui-message-stream-error';
|
|
11
12
|
|
|
12
13
|
function createUIMessageStream(parts: UIMessageChunk[]) {
|
|
13
14
|
return convertArrayToReadableStream(parts);
|
|
@@ -224,6 +225,247 @@ describe('processUIMessageStream', () => {
|
|
|
224
225
|
});
|
|
225
226
|
});
|
|
226
227
|
|
|
228
|
+
describe('malformed stream errors', () => {
|
|
229
|
+
it('should throw descriptive error when text-delta is received without text-start', async () => {
|
|
230
|
+
const stream = createUIMessageStream([
|
|
231
|
+
{ type: 'start', messageId: 'msg-123' },
|
|
232
|
+
{ type: 'start-step' },
|
|
233
|
+
{ type: 'text-delta', id: 'text-1', delta: 'Hello' },
|
|
234
|
+
]);
|
|
235
|
+
|
|
236
|
+
state = createStreamingUIMessageState({
|
|
237
|
+
messageId: 'msg-123',
|
|
238
|
+
lastMessage: undefined,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
await expect(
|
|
242
|
+
consumeStream({
|
|
243
|
+
stream: processUIMessageStream({
|
|
244
|
+
stream,
|
|
245
|
+
runUpdateMessageJob,
|
|
246
|
+
onError: error => {
|
|
247
|
+
throw error;
|
|
248
|
+
},
|
|
249
|
+
}),
|
|
250
|
+
onError: error => {
|
|
251
|
+
throw error;
|
|
252
|
+
},
|
|
253
|
+
}),
|
|
254
|
+
).rejects.toThrow(
|
|
255
|
+
'Received text-delta for missing text part with ID "text-1". ' +
|
|
256
|
+
'Ensure a "text-start" chunk is sent before any "text-delta" chunks.',
|
|
257
|
+
);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should throw descriptive error when reasoning-delta is received without reasoning-start', async () => {
|
|
261
|
+
const stream = createUIMessageStream([
|
|
262
|
+
{ type: 'start', messageId: 'msg-123' },
|
|
263
|
+
{ type: 'start-step' },
|
|
264
|
+
{ type: 'reasoning-delta', id: 'reasoning-1', delta: 'Thinking...' },
|
|
265
|
+
]);
|
|
266
|
+
|
|
267
|
+
state = createStreamingUIMessageState({
|
|
268
|
+
messageId: 'msg-123',
|
|
269
|
+
lastMessage: undefined,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
await expect(
|
|
273
|
+
consumeStream({
|
|
274
|
+
stream: processUIMessageStream({
|
|
275
|
+
stream,
|
|
276
|
+
runUpdateMessageJob,
|
|
277
|
+
onError: error => {
|
|
278
|
+
throw error;
|
|
279
|
+
},
|
|
280
|
+
}),
|
|
281
|
+
onError: error => {
|
|
282
|
+
throw error;
|
|
283
|
+
},
|
|
284
|
+
}),
|
|
285
|
+
).rejects.toThrow(
|
|
286
|
+
'Received reasoning-delta for missing reasoning part with ID "reasoning-1". ' +
|
|
287
|
+
'Ensure a "reasoning-start" chunk is sent before any "reasoning-delta" chunks.',
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('should throw descriptive error when tool-input-delta is received without tool-input-start', async () => {
|
|
292
|
+
const stream = createUIMessageStream([
|
|
293
|
+
{ type: 'start', messageId: 'msg-123' },
|
|
294
|
+
{ type: 'start-step' },
|
|
295
|
+
{
|
|
296
|
+
type: 'tool-input-delta',
|
|
297
|
+
toolCallId: 'tool-1',
|
|
298
|
+
inputTextDelta: '{"key":',
|
|
299
|
+
},
|
|
300
|
+
]);
|
|
301
|
+
|
|
302
|
+
state = createStreamingUIMessageState({
|
|
303
|
+
messageId: 'msg-123',
|
|
304
|
+
lastMessage: undefined,
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
await expect(
|
|
308
|
+
consumeStream({
|
|
309
|
+
stream: processUIMessageStream({
|
|
310
|
+
stream,
|
|
311
|
+
runUpdateMessageJob,
|
|
312
|
+
onError: error => {
|
|
313
|
+
throw error;
|
|
314
|
+
},
|
|
315
|
+
}),
|
|
316
|
+
onError: error => {
|
|
317
|
+
throw error;
|
|
318
|
+
},
|
|
319
|
+
}),
|
|
320
|
+
).rejects.toThrow(
|
|
321
|
+
'Received tool-input-delta for missing tool call with ID "tool-1". ' +
|
|
322
|
+
'Ensure a "tool-input-start" chunk is sent before any "tool-input-delta" chunks.',
|
|
323
|
+
);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('should throw descriptive error when text-end is received without text-start', async () => {
|
|
327
|
+
const stream = createUIMessageStream([
|
|
328
|
+
{ type: 'start', messageId: 'msg-123' },
|
|
329
|
+
{ type: 'start-step' },
|
|
330
|
+
{ type: 'text-end', id: 'text-1' },
|
|
331
|
+
]);
|
|
332
|
+
|
|
333
|
+
state = createStreamingUIMessageState({
|
|
334
|
+
messageId: 'msg-123',
|
|
335
|
+
lastMessage: undefined,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
await expect(
|
|
339
|
+
consumeStream({
|
|
340
|
+
stream: processUIMessageStream({
|
|
341
|
+
stream,
|
|
342
|
+
runUpdateMessageJob,
|
|
343
|
+
onError: error => {
|
|
344
|
+
throw error;
|
|
345
|
+
},
|
|
346
|
+
}),
|
|
347
|
+
onError: error => {
|
|
348
|
+
throw error;
|
|
349
|
+
},
|
|
350
|
+
}),
|
|
351
|
+
).rejects.toThrow(
|
|
352
|
+
'Received text-end for missing text part with ID "text-1". ' +
|
|
353
|
+
'Ensure a "text-start" chunk is sent before any "text-end" chunks.',
|
|
354
|
+
);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('should throw descriptive error when reasoning-end is received without reasoning-start', async () => {
|
|
358
|
+
const stream = createUIMessageStream([
|
|
359
|
+
{ type: 'start', messageId: 'msg-123' },
|
|
360
|
+
{ type: 'start-step' },
|
|
361
|
+
{ type: 'reasoning-end', id: 'reasoning-1' },
|
|
362
|
+
]);
|
|
363
|
+
|
|
364
|
+
state = createStreamingUIMessageState({
|
|
365
|
+
messageId: 'msg-123',
|
|
366
|
+
lastMessage: undefined,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
await expect(
|
|
370
|
+
consumeStream({
|
|
371
|
+
stream: processUIMessageStream({
|
|
372
|
+
stream,
|
|
373
|
+
runUpdateMessageJob,
|
|
374
|
+
onError: error => {
|
|
375
|
+
throw error;
|
|
376
|
+
},
|
|
377
|
+
}),
|
|
378
|
+
onError: error => {
|
|
379
|
+
throw error;
|
|
380
|
+
},
|
|
381
|
+
}),
|
|
382
|
+
).rejects.toThrow(
|
|
383
|
+
'Received reasoning-end for missing reasoning part with ID "reasoning-1". ' +
|
|
384
|
+
'Ensure a "reasoning-start" chunk is sent before any "reasoning-end" chunks.',
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('should throw UIMessageStreamError with correct properties for text-delta without text-start', async () => {
|
|
389
|
+
const stream = createUIMessageStream([
|
|
390
|
+
{ type: 'start', messageId: 'msg-123' },
|
|
391
|
+
{ type: 'start-step' },
|
|
392
|
+
{ type: 'text-delta', id: 'missing-id', delta: 'Hello' },
|
|
393
|
+
]);
|
|
394
|
+
|
|
395
|
+
state = createStreamingUIMessageState({
|
|
396
|
+
messageId: 'msg-123',
|
|
397
|
+
lastMessage: undefined,
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
let caughtError: unknown;
|
|
401
|
+
try {
|
|
402
|
+
await consumeStream({
|
|
403
|
+
stream: processUIMessageStream({
|
|
404
|
+
stream,
|
|
405
|
+
runUpdateMessageJob,
|
|
406
|
+
onError: error => {
|
|
407
|
+
throw error;
|
|
408
|
+
},
|
|
409
|
+
}),
|
|
410
|
+
onError: error => {
|
|
411
|
+
throw error;
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
} catch (error) {
|
|
415
|
+
caughtError = error;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
expect(UIMessageStreamError.isInstance(caughtError)).toBe(true);
|
|
419
|
+
expect((caughtError as UIMessageStreamError).chunkType).toBe(
|
|
420
|
+
'text-delta',
|
|
421
|
+
);
|
|
422
|
+
expect((caughtError as UIMessageStreamError).chunkId).toBe('missing-id');
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('should throw UIMessageStreamError with correct properties for tool-input-delta without tool-input-start', async () => {
|
|
426
|
+
const stream = createUIMessageStream([
|
|
427
|
+
{ type: 'start', messageId: 'msg-123' },
|
|
428
|
+
{ type: 'start-step' },
|
|
429
|
+
{
|
|
430
|
+
type: 'tool-input-delta',
|
|
431
|
+
toolCallId: 'missing-tool-id',
|
|
432
|
+
inputTextDelta: '{"key":',
|
|
433
|
+
},
|
|
434
|
+
]);
|
|
435
|
+
|
|
436
|
+
state = createStreamingUIMessageState({
|
|
437
|
+
messageId: 'msg-123',
|
|
438
|
+
lastMessage: undefined,
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
let caughtError: unknown;
|
|
442
|
+
try {
|
|
443
|
+
await consumeStream({
|
|
444
|
+
stream: processUIMessageStream({
|
|
445
|
+
stream,
|
|
446
|
+
runUpdateMessageJob,
|
|
447
|
+
onError: error => {
|
|
448
|
+
throw error;
|
|
449
|
+
},
|
|
450
|
+
}),
|
|
451
|
+
onError: error => {
|
|
452
|
+
throw error;
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
} catch (error) {
|
|
456
|
+
caughtError = error;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
expect(UIMessageStreamError.isInstance(caughtError)).toBe(true);
|
|
460
|
+
expect((caughtError as UIMessageStreamError).chunkType).toBe(
|
|
461
|
+
'tool-input-delta',
|
|
462
|
+
);
|
|
463
|
+
expect((caughtError as UIMessageStreamError).chunkId).toBe(
|
|
464
|
+
'missing-tool-id',
|
|
465
|
+
);
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
227
469
|
describe('server-side tool roundtrip', () => {
|
|
228
470
|
beforeEach(async () => {
|
|
229
471
|
const stream = createUIMessageStream([
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FlexibleSchema, validateTypes } from '@ai-sdk/provider-utils';
|
|
2
|
+
import { UIMessageStreamError } from '../error/ui-message-stream-error';
|
|
2
3
|
import { ProviderMetadata } from '../types';
|
|
3
4
|
import { FinishReason } from '../types/language-model';
|
|
4
5
|
import {
|
|
@@ -108,9 +109,11 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
108
109
|
);
|
|
109
110
|
|
|
110
111
|
if (toolInvocation == null) {
|
|
111
|
-
throw new
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
throw new UIMessageStreamError({
|
|
113
|
+
chunkType: 'tool-invocation',
|
|
114
|
+
chunkId: toolCallId,
|
|
115
|
+
message: `No tool invocation found for tool call ID "${toolCallId}".`,
|
|
116
|
+
});
|
|
114
117
|
}
|
|
115
118
|
|
|
116
119
|
return toolInvocation;
|
|
@@ -313,6 +316,15 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
313
316
|
|
|
314
317
|
case 'text-delta': {
|
|
315
318
|
const textPart = state.activeTextParts[chunk.id];
|
|
319
|
+
if (textPart == null) {
|
|
320
|
+
throw new UIMessageStreamError({
|
|
321
|
+
chunkType: 'text-delta',
|
|
322
|
+
chunkId: chunk.id,
|
|
323
|
+
message:
|
|
324
|
+
`Received text-delta for missing text part with ID "${chunk.id}". ` +
|
|
325
|
+
`Ensure a "text-start" chunk is sent before any "text-delta" chunks.`,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
316
328
|
textPart.text += chunk.delta;
|
|
317
329
|
textPart.providerMetadata =
|
|
318
330
|
chunk.providerMetadata ?? textPart.providerMetadata;
|
|
@@ -322,6 +334,15 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
322
334
|
|
|
323
335
|
case 'text-end': {
|
|
324
336
|
const textPart = state.activeTextParts[chunk.id];
|
|
337
|
+
if (textPart == null) {
|
|
338
|
+
throw new UIMessageStreamError({
|
|
339
|
+
chunkType: 'text-end',
|
|
340
|
+
chunkId: chunk.id,
|
|
341
|
+
message:
|
|
342
|
+
`Received text-end for missing text part with ID "${chunk.id}". ` +
|
|
343
|
+
`Ensure a "text-start" chunk is sent before any "text-end" chunks.`,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
325
346
|
textPart.state = 'done';
|
|
326
347
|
textPart.providerMetadata =
|
|
327
348
|
chunk.providerMetadata ?? textPart.providerMetadata;
|
|
@@ -345,6 +366,15 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
345
366
|
|
|
346
367
|
case 'reasoning-delta': {
|
|
347
368
|
const reasoningPart = state.activeReasoningParts[chunk.id];
|
|
369
|
+
if (reasoningPart == null) {
|
|
370
|
+
throw new UIMessageStreamError({
|
|
371
|
+
chunkType: 'reasoning-delta',
|
|
372
|
+
chunkId: chunk.id,
|
|
373
|
+
message:
|
|
374
|
+
`Received reasoning-delta for missing reasoning part with ID "${chunk.id}". ` +
|
|
375
|
+
`Ensure a "reasoning-start" chunk is sent before any "reasoning-delta" chunks.`,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
348
378
|
reasoningPart.text += chunk.delta;
|
|
349
379
|
reasoningPart.providerMetadata =
|
|
350
380
|
chunk.providerMetadata ?? reasoningPart.providerMetadata;
|
|
@@ -354,6 +384,15 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
354
384
|
|
|
355
385
|
case 'reasoning-end': {
|
|
356
386
|
const reasoningPart = state.activeReasoningParts[chunk.id];
|
|
387
|
+
if (reasoningPart == null) {
|
|
388
|
+
throw new UIMessageStreamError({
|
|
389
|
+
chunkType: 'reasoning-end',
|
|
390
|
+
chunkId: chunk.id,
|
|
391
|
+
message:
|
|
392
|
+
`Received reasoning-end for missing reasoning part with ID "${chunk.id}". ` +
|
|
393
|
+
`Ensure a "reasoning-start" chunk is sent before any "reasoning-end" chunks.`,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
357
396
|
reasoningPart.providerMetadata =
|
|
358
397
|
chunk.providerMetadata ?? reasoningPart.providerMetadata;
|
|
359
398
|
reasoningPart.state = 'done';
|
|
@@ -440,6 +479,15 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
440
479
|
|
|
441
480
|
case 'tool-input-delta': {
|
|
442
481
|
const partialToolCall = state.partialToolCalls[chunk.toolCallId];
|
|
482
|
+
if (partialToolCall == null) {
|
|
483
|
+
throw new UIMessageStreamError({
|
|
484
|
+
chunkType: 'tool-input-delta',
|
|
485
|
+
chunkId: chunk.toolCallId,
|
|
486
|
+
message:
|
|
487
|
+
`Received tool-input-delta for missing tool call with ID "${chunk.toolCallId}". ` +
|
|
488
|
+
`Ensure a "tool-input-start" chunk is sent before any "tool-input-delta" chunks.`,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
443
491
|
|
|
444
492
|
partialToolCall.text += chunk.inputTextDelta;
|
|
445
493
|
|