ai 7.0.91 → 7.0.93
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 +27 -0
- package/dist/index.d.ts +178 -159
- package/dist/index.js +143 -25
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +1 -1
- package/dist/internal/index.js +2 -2
- package/dist/internal/index.js.map +1 -1
- package/docs/02-foundations/02-providers-and-models.mdx +1 -0
- package/docs/03-agents/04-loop-control.mdx +5 -3
- package/docs/03-agents/07-workflow-agent.mdx +27 -6
- package/docs/03-ai-sdk-core/10-generating-structured-data.mdx +15 -3
- package/docs/03-ai-sdk-core/16-mcp-tools.mdx +64 -1
- package/docs/03-ai-sdk-core/35-image-generation.mdx +7 -0
- package/docs/03-ai-sdk-core/36-transcription.mdx +36 -35
- package/docs/03-ai-sdk-harnesses/02-harness-agent.mdx +36 -0
- package/docs/04-ai-sdk-ui/20-streaming-data.mdx +11 -6
- package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +14 -0
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +30 -3
- package/docs/07-reference/01-ai-sdk-core/28-output.mdx +27 -1
- package/docs/07-reference/01-ai-sdk-core/80-smooth-stream.mdx +1 -1
- package/docs/07-reference/02-ai-sdk-ui/01-use-chat.mdx +1 -1
- package/docs/07-reference/02-ai-sdk-ui/40-create-ui-message-stream.mdx +4 -0
- package/docs/07-reference/02-ai-sdk-ui/41-create-ui-message-stream-response.mdx +6 -1
- package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +42 -28
- package/docs/07-reference/05-ai-sdk-errors/ai-no-image-generated-error.mdx +7 -0
- package/package.json +6 -6
- package/src/agent/tool-loop-agent-settings.ts +15 -0
- package/src/embed/embed-many.ts +27 -2
- package/src/error/no-image-generated-error.ts +9 -0
- package/src/generate-image/generate-image.ts +1 -1
- package/src/generate-text/generate-text-events.ts +1 -1
- package/src/generate-text/output.ts +111 -1
- package/src/generate-text/smooth-stream.ts +19 -4
- package/src/generate-text/stream-text.ts +2 -6
- package/src/ui/call-completion-api.ts +1 -1
- package/src/ui/chat.ts +1 -1
- package/src/ui/convert-to-model-messages.ts +8 -2
- package/src/ui/http-chat-transport.ts +2 -2
- package/src/ui/validate-ui-messages.ts +14 -0
- package/src/util/async-iterable-stream.ts +1 -1
- package/src/util/data-url.ts +1 -1
- package/src/util/merge-abort-signals.ts +1 -1
|
@@ -33,6 +33,9 @@ const response = createUIMessageStreamResponse({
|
|
|
33
33
|
},
|
|
34
34
|
stream: createUIMessageStream({
|
|
35
35
|
execute({ writer }) {
|
|
36
|
+
// The outer stream owns the assistant message lifecycle.
|
|
37
|
+
writer.write({ type: 'start' });
|
|
38
|
+
|
|
36
39
|
// Write custom data (type must be 'data-<name>')
|
|
37
40
|
writer.write({
|
|
38
41
|
type: 'data-message',
|
|
@@ -68,7 +71,9 @@ const response = createUIMessageStreamResponse({
|
|
|
68
71
|
prompt: 'Say hello',
|
|
69
72
|
});
|
|
70
73
|
|
|
71
|
-
writer.merge(
|
|
74
|
+
writer.merge(
|
|
75
|
+
toUIMessageStream({ stream: result.stream, sendStart: false }),
|
|
76
|
+
);
|
|
72
77
|
},
|
|
73
78
|
}),
|
|
74
79
|
});
|
|
@@ -99,7 +99,7 @@ To see `WorkflowAgent` in action, check out [these examples](#examples).
|
|
|
99
99
|
type: 'StopCondition | StopCondition[]',
|
|
100
100
|
isOptional: true,
|
|
101
101
|
description:
|
|
102
|
-
'Default stop condition for the agent loop.
|
|
102
|
+
'Default stop condition for the agent loop. When omitted, WorkflowAgent has no maximum step count and continues until natural completion. Use `isStepCount()` to bound execution. Per-stream values override this default.',
|
|
103
103
|
},
|
|
104
104
|
{
|
|
105
105
|
name: 'activeTools',
|
|
@@ -268,24 +268,24 @@ To see `WorkflowAgent` in action, check out [these examples](#examples).
|
|
|
268
268
|
},
|
|
269
269
|
{
|
|
270
270
|
name: 'onToolExecutionStart',
|
|
271
|
-
type: '
|
|
271
|
+
type: 'WorkflowAgentOnToolExecutionStartCallback',
|
|
272
272
|
isOptional: true,
|
|
273
273
|
description:
|
|
274
274
|
"Callback called right before a tool's execute function runs. If also specified in `stream()`, both callbacks fire (constructor first). Experimental (can break in patch releases).",
|
|
275
275
|
properties: [
|
|
276
276
|
{
|
|
277
|
-
type: '
|
|
277
|
+
type: 'WorkflowAgentToolExecutionStartEvent',
|
|
278
278
|
parameters: [
|
|
279
279
|
{
|
|
280
|
-
name: '
|
|
281
|
-
type: 'string',
|
|
280
|
+
name: 'toolCall',
|
|
281
|
+
type: '{ type: "tool-call"; toolCallId: string; toolName: string; input: unknown }',
|
|
282
282
|
description:
|
|
283
|
-
'
|
|
283
|
+
'The tool call being executed. For concrete tool sets, the tool name and input are correlated.',
|
|
284
284
|
},
|
|
285
285
|
{
|
|
286
|
-
name: '
|
|
287
|
-
type: '
|
|
288
|
-
description: 'The
|
|
286
|
+
name: 'stepNumber',
|
|
287
|
+
type: 'number',
|
|
288
|
+
description: 'The current step number, starting at zero.',
|
|
289
289
|
},
|
|
290
290
|
{
|
|
291
291
|
name: 'messages',
|
|
@@ -295,9 +295,9 @@ To see `WorkflowAgent` in action, check out [these examples](#examples).
|
|
|
295
295
|
},
|
|
296
296
|
{
|
|
297
297
|
name: 'toolContext',
|
|
298
|
-
type: 'InferToolContext<TOOLS[
|
|
298
|
+
type: 'InferToolContext<TOOLS[NAME]> | undefined',
|
|
299
299
|
description:
|
|
300
|
-
'
|
|
300
|
+
'The validated context for the tool call. For concrete tool sets, each event union member pairs it with the corresponding tool name.',
|
|
301
301
|
},
|
|
302
302
|
],
|
|
303
303
|
},
|
|
@@ -305,24 +305,24 @@ To see `WorkflowAgent` in action, check out [these examples](#examples).
|
|
|
305
305
|
},
|
|
306
306
|
{
|
|
307
307
|
name: 'onToolExecutionEnd',
|
|
308
|
-
type: '
|
|
308
|
+
type: 'WorkflowAgentOnToolExecutionEndCallback',
|
|
309
309
|
isOptional: true,
|
|
310
310
|
description:
|
|
311
|
-
"Callback called right after a tool's execute function completes or errors.
|
|
311
|
+
"Callback called right after a tool's execute function completes or errors. Check `success` to determine whether `output` or `error` is available. If also specified in `stream()`, both callbacks fire (constructor first). Experimental (can break in patch releases).",
|
|
312
312
|
properties: [
|
|
313
313
|
{
|
|
314
|
-
type: '
|
|
314
|
+
type: 'WorkflowAgentToolExecutionEndEvent',
|
|
315
315
|
parameters: [
|
|
316
316
|
{
|
|
317
|
-
name: '
|
|
318
|
-
type: 'string',
|
|
317
|
+
name: 'toolCall',
|
|
318
|
+
type: '{ type: "tool-call"; toolCallId: string; toolName: string; input: unknown }',
|
|
319
319
|
description:
|
|
320
|
-
'
|
|
320
|
+
'The tool call that was executed. For concrete tool sets, the tool name and input are correlated.',
|
|
321
321
|
},
|
|
322
322
|
{
|
|
323
|
-
name: '
|
|
324
|
-
type: '
|
|
325
|
-
description: 'The
|
|
323
|
+
name: 'stepNumber',
|
|
324
|
+
type: 'number',
|
|
325
|
+
description: 'The current step number, starting at zero.',
|
|
326
326
|
},
|
|
327
327
|
{
|
|
328
328
|
name: 'durationMs',
|
|
@@ -338,15 +338,29 @@ To see `WorkflowAgent` in action, check out [these examples](#examples).
|
|
|
338
338
|
},
|
|
339
339
|
{
|
|
340
340
|
name: 'toolContext',
|
|
341
|
-
type: 'InferToolContext<TOOLS[
|
|
341
|
+
type: 'InferToolContext<TOOLS[NAME]> | undefined',
|
|
342
|
+
description:
|
|
343
|
+
'The validated context for the tool call. For concrete tool sets, each event union member pairs it with the corresponding tool name.',
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: 'success',
|
|
347
|
+
type: 'boolean',
|
|
348
|
+
description:
|
|
349
|
+
'Whether the tool execution succeeded. Discriminates between the output and error event variants.',
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
name: 'output',
|
|
353
|
+
type: 'InferToolOutput<TOOLS[NAME]>',
|
|
354
|
+
isOptional: true,
|
|
342
355
|
description:
|
|
343
|
-
'
|
|
356
|
+
'The tool output. Available when `success` is `true` and correlated with the configured tool.',
|
|
344
357
|
},
|
|
345
358
|
{
|
|
346
|
-
name: '
|
|
347
|
-
type: '
|
|
359
|
+
name: 'error',
|
|
360
|
+
type: 'unknown',
|
|
361
|
+
isOptional: true,
|
|
348
362
|
description:
|
|
349
|
-
|
|
363
|
+
'The tool execution error. Available when `success` is `false`.',
|
|
350
364
|
},
|
|
351
365
|
],
|
|
352
366
|
},
|
|
@@ -509,7 +523,7 @@ const result = await agent.stream({
|
|
|
509
523
|
name: 'stopWhen',
|
|
510
524
|
type: 'StopCondition | StopCondition[]',
|
|
511
525
|
isOptional: true,
|
|
512
|
-
description: 'Condition(s) for ending the agent loop.
|
|
526
|
+
description: 'Condition(s) for ending the agent loop. When omitted and no constructor-level condition is configured, WorkflowAgent has no maximum step count and continues until natural completion. Use `isStepCount()` to bound execution.',
|
|
513
527
|
},
|
|
514
528
|
|
|
515
529
|
{
|
|
@@ -644,14 +658,14 @@ const result = await agent.stream({
|
|
|
644
658
|
},
|
|
645
659
|
{
|
|
646
660
|
name: 'onToolExecutionStart',
|
|
647
|
-
type: '
|
|
661
|
+
type: 'WorkflowAgentOnToolExecutionStartCallback',
|
|
648
662
|
isOptional: true,
|
|
649
663
|
description:
|
|
650
664
|
'Per-call onToolExecutionStart callback. If also specified in the constructor, both fire (constructor first).',
|
|
651
665
|
},
|
|
652
666
|
{
|
|
653
667
|
name: 'onToolExecutionEnd',
|
|
654
|
-
type: '
|
|
668
|
+
type: 'WorkflowAgentOnToolExecutionEndCallback',
|
|
655
669
|
isOptional: true,
|
|
656
670
|
description:
|
|
657
671
|
'Per-call onToolExecutionEnd callback. If also specified in the constructor, both fire (constructor first).',
|
|
@@ -14,6 +14,7 @@ It can arise due to the following reasons:
|
|
|
14
14
|
## Properties
|
|
15
15
|
|
|
16
16
|
- `message`: The error message (optional, defaults to `'No image generated.'`).
|
|
17
|
+
- `calls`: Results from the underlying image model calls, including generated images, provider metadata, response metadata, warnings, and usage (optional).
|
|
17
18
|
- `responses`: Metadata about the image model responses, including timestamp, model, and headers (optional).
|
|
18
19
|
- `cause`: The cause of the error. You can use this for more detailed error handling (optional).
|
|
19
20
|
|
|
@@ -31,6 +32,12 @@ try {
|
|
|
31
32
|
console.log('NoImageGeneratedError');
|
|
32
33
|
console.log('Cause:', error.cause);
|
|
33
34
|
console.log('Responses:', error.responses);
|
|
35
|
+
|
|
36
|
+
for (const call of error.calls ?? []) {
|
|
37
|
+
console.log('Provider metadata:', call.providerMetadata);
|
|
38
|
+
console.log('Warnings:', call.warnings);
|
|
39
|
+
console.log('Usage:', call.usage);
|
|
40
|
+
}
|
|
34
41
|
}
|
|
35
42
|
}
|
|
36
43
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.93",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -42,18 +42,18 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ai-sdk/gateway": "4.0.
|
|
45
|
+
"@ai-sdk/gateway": "4.0.75",
|
|
46
46
|
"@ai-sdk/provider": "4.0.10",
|
|
47
47
|
"@ai-sdk/provider-utils": "5.0.36"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@ai-sdk/amazon-bedrock": "5.0.
|
|
50
|
+
"@ai-sdk/amazon-bedrock": "5.0.75",
|
|
51
51
|
"@ai-sdk/deepseek": "3.0.39",
|
|
52
|
-
"@ai-sdk/google": "4.0.
|
|
52
|
+
"@ai-sdk/google": "4.0.64",
|
|
53
53
|
"@ai-sdk/groq": "4.0.37",
|
|
54
|
-
"@ai-sdk/huggingface": "2.0.
|
|
54
|
+
"@ai-sdk/huggingface": "2.0.44",
|
|
55
55
|
"@ai-sdk/moonshotai": "3.0.45",
|
|
56
|
-
"@ai-sdk/openai": "4.0.
|
|
56
|
+
"@ai-sdk/openai": "4.0.59",
|
|
57
57
|
"@ai-sdk/test-server": "2.0.1",
|
|
58
58
|
"@ai-sdk/xai": "4.0.54",
|
|
59
59
|
"@edge-runtime/vm": "^5.0.0",
|
|
@@ -17,6 +17,10 @@ import type {
|
|
|
17
17
|
GenerateTextOnStepStartCallback,
|
|
18
18
|
} from '../generate-text/generate-text-events';
|
|
19
19
|
import type { GenerateTextInclude } from '../generate-text/generate-text';
|
|
20
|
+
import type {
|
|
21
|
+
OnLanguageModelCallEndCallback,
|
|
22
|
+
OnLanguageModelCallStartCallback,
|
|
23
|
+
} from '../generate-text/language-model-events';
|
|
20
24
|
import type { Output } from '../generate-text/output';
|
|
21
25
|
import type { PrepareStepFunction } from '../generate-text/prepare-step';
|
|
22
26
|
import type { StopCondition } from '../generate-text/stop-condition';
|
|
@@ -212,6 +216,17 @@ export type ToolLoopAgentSettings<
|
|
|
212
216
|
NoInfer<OUTPUT>
|
|
213
217
|
>;
|
|
214
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Callback that is called immediately before the provider model call begins.
|
|
221
|
+
*/
|
|
222
|
+
onLanguageModelCallStart?: OnLanguageModelCallStartCallback;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Callback that is called after the model response has been normalized and parsed,
|
|
226
|
+
* but before any client-side tool execution begins.
|
|
227
|
+
*/
|
|
228
|
+
onLanguageModelCallEnd?: OnLanguageModelCallEndCallback<NoInfer<TOOLS>>;
|
|
229
|
+
|
|
215
230
|
/**
|
|
216
231
|
* Callback that is called before each tool execution begins.
|
|
217
232
|
*/
|
package/src/embed/embed-many.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { InvalidResponseDataError } from '@ai-sdk/provider';
|
|
1
2
|
import {
|
|
2
3
|
createIdGenerator,
|
|
3
4
|
withUserAgentSuffix,
|
|
@@ -264,6 +265,8 @@ export async function embedMany({
|
|
|
264
265
|
};
|
|
265
266
|
});
|
|
266
267
|
|
|
268
|
+
validateEmbeddingCount({ embeddings, values });
|
|
269
|
+
|
|
267
270
|
logWarnings({
|
|
268
271
|
warnings,
|
|
269
272
|
provider: model.provider,
|
|
@@ -325,8 +328,8 @@ export async function embedMany({
|
|
|
325
328
|
|
|
326
329
|
for (const parallelChunk of parallelChunks) {
|
|
327
330
|
const results = await Promise.all(
|
|
328
|
-
parallelChunk.map(chunk => {
|
|
329
|
-
|
|
331
|
+
parallelChunk.map(async chunk => {
|
|
332
|
+
const result = await retry(async () => {
|
|
330
333
|
const embedCallId = generateCallId();
|
|
331
334
|
|
|
332
335
|
await notify({
|
|
@@ -373,6 +376,13 @@ export async function embedMany({
|
|
|
373
376
|
response: modelResponse.response,
|
|
374
377
|
};
|
|
375
378
|
});
|
|
379
|
+
|
|
380
|
+
validateEmbeddingCount({
|
|
381
|
+
embeddings: result.embeddings,
|
|
382
|
+
values: chunk,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
return result;
|
|
376
386
|
}),
|
|
377
387
|
);
|
|
378
388
|
|
|
@@ -436,6 +446,21 @@ export async function embedMany({
|
|
|
436
446
|
});
|
|
437
447
|
}
|
|
438
448
|
|
|
449
|
+
function validateEmbeddingCount({
|
|
450
|
+
embeddings,
|
|
451
|
+
values,
|
|
452
|
+
}: {
|
|
453
|
+
embeddings: Array<Embedding>;
|
|
454
|
+
values: Array<string>;
|
|
455
|
+
}) {
|
|
456
|
+
if (embeddings.length !== values.length) {
|
|
457
|
+
throw new InvalidResponseDataError({
|
|
458
|
+
data: embeddings,
|
|
459
|
+
message: `Expected ${values.length} embeddings, but received ${embeddings.length}.`,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
439
464
|
const textEncoder = new TextEncoder();
|
|
440
465
|
|
|
441
466
|
function splitByEmbeddingLimits({
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AISDKError } from '@ai-sdk/provider';
|
|
2
|
+
import type { GenerateImageCall } from '../generate-image/generate-image-result';
|
|
2
3
|
import type { ImageModelResponseMetadata } from '../types/image-model-response-metadata';
|
|
3
4
|
|
|
4
5
|
const name = 'AI_NoImageGeneratedError';
|
|
@@ -14,6 +15,11 @@ const symbol = Symbol.for(marker);
|
|
|
14
15
|
export class NoImageGeneratedError extends AISDKError {
|
|
15
16
|
private readonly [symbol] = true; // used in isInstance
|
|
16
17
|
|
|
18
|
+
/**
|
|
19
|
+
* The results of the underlying image model calls.
|
|
20
|
+
*/
|
|
21
|
+
readonly calls: Array<GenerateImageCall> | undefined;
|
|
22
|
+
|
|
17
23
|
/**
|
|
18
24
|
* The response metadata for each call.
|
|
19
25
|
*/
|
|
@@ -22,14 +28,17 @@ export class NoImageGeneratedError extends AISDKError {
|
|
|
22
28
|
constructor({
|
|
23
29
|
message = 'No image generated.',
|
|
24
30
|
cause,
|
|
31
|
+
calls,
|
|
25
32
|
responses,
|
|
26
33
|
}: {
|
|
27
34
|
message?: string;
|
|
28
35
|
cause?: Error;
|
|
36
|
+
calls?: Array<GenerateImageCall>;
|
|
29
37
|
responses?: Array<ImageModelResponseMetadata>;
|
|
30
38
|
}) {
|
|
31
39
|
super({ name, message, cause });
|
|
32
40
|
|
|
41
|
+
this.calls = calls;
|
|
33
42
|
this.responses = responses;
|
|
34
43
|
}
|
|
35
44
|
|
|
@@ -295,7 +295,7 @@ export async function generateImage({
|
|
|
295
295
|
logWarnings({ warnings, provider: model.provider, model: model.modelId });
|
|
296
296
|
|
|
297
297
|
if (!images.length) {
|
|
298
|
-
throw new NoImageGeneratedError({ responses });
|
|
298
|
+
throw new NoImageGeneratedError({ calls, responses });
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
return new DefaultGenerateImageResult({
|
|
@@ -294,7 +294,7 @@ export type GenerateTextEndEvent<
|
|
|
294
294
|
};
|
|
295
295
|
|
|
296
296
|
/**
|
|
297
|
-
* Event passed to
|
|
297
|
+
* Event passed to an `onAbort` callback for text generation.
|
|
298
298
|
*
|
|
299
299
|
* Called when a streaming text generation operation is aborted before it
|
|
300
300
|
* completes.
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
safeValidateTypes,
|
|
12
12
|
type FlexibleSchema,
|
|
13
13
|
} from '@ai-sdk/provider-utils';
|
|
14
|
+
import { InvalidArgumentError } from '../error/invalid-argument-error';
|
|
14
15
|
import { NoObjectGeneratedError } from '../error/no-object-generated-error';
|
|
15
16
|
import type { FinishReason } from '../types/language-model';
|
|
16
17
|
import type { LanguageModelResponseMetadata } from '../types/language-model-response-metadata';
|
|
@@ -189,6 +190,8 @@ export const object = <OBJECT>({
|
|
|
189
190
|
* When the model generates a text response, it will return an array of elements.
|
|
190
191
|
*
|
|
191
192
|
* @param element - The schema of the array elements to generate.
|
|
193
|
+
* @param minItems - Optional minimum number of elements to generate.
|
|
194
|
+
* @param maxItems - Optional maximum number of elements to generate.
|
|
192
195
|
* @param name - Optional name of the output that should be generated. Used by some providers for additional LLM guidance, e.g. via tool or schema name.
|
|
193
196
|
* @param description - Optional description of the output that should be generated. Used by some providers for additional LLM guidance, e.g. via tool or schema description.
|
|
194
197
|
*
|
|
@@ -196,10 +199,20 @@ export const object = <OBJECT>({
|
|
|
196
199
|
*/
|
|
197
200
|
export const array = <ELEMENT>({
|
|
198
201
|
element: inputElementSchema,
|
|
202
|
+
minItems,
|
|
203
|
+
maxItems,
|
|
199
204
|
name,
|
|
200
205
|
description,
|
|
201
206
|
}: {
|
|
202
207
|
element: FlexibleSchema<ELEMENT>;
|
|
208
|
+
/**
|
|
209
|
+
* Optional minimum number of elements to generate.
|
|
210
|
+
*/
|
|
211
|
+
minItems?: number;
|
|
212
|
+
/**
|
|
213
|
+
* Optional maximum number of elements to generate.
|
|
214
|
+
*/
|
|
215
|
+
maxItems?: number;
|
|
203
216
|
/**
|
|
204
217
|
* Optional name of the output that should be generated.
|
|
205
218
|
* Used by some providers for additional LLM guidance, e.g. via tool or schema name.
|
|
@@ -211,6 +224,17 @@ export const array = <ELEMENT>({
|
|
|
211
224
|
*/
|
|
212
225
|
description?: string;
|
|
213
226
|
}): Output<Array<ELEMENT>, Array<ELEMENT>, ELEMENT> => {
|
|
227
|
+
validateArrayBound({ name: 'minItems', value: minItems });
|
|
228
|
+
validateArrayBound({ name: 'maxItems', value: maxItems });
|
|
229
|
+
|
|
230
|
+
if (minItems != null && maxItems != null && minItems > maxItems) {
|
|
231
|
+
throw new InvalidArgumentError({
|
|
232
|
+
parameter: 'minItems',
|
|
233
|
+
value: minItems,
|
|
234
|
+
message: 'minItems must be less than or equal to maxItems',
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
214
238
|
const elementSchema = asSchema(inputElementSchema);
|
|
215
239
|
|
|
216
240
|
return {
|
|
@@ -236,7 +260,12 @@ export const array = <ELEMENT>({
|
|
|
236
260
|
...($defs != null && { $defs }),
|
|
237
261
|
type: 'object',
|
|
238
262
|
properties: {
|
|
239
|
-
elements: {
|
|
263
|
+
elements: {
|
|
264
|
+
type: 'array',
|
|
265
|
+
items: itemSchema,
|
|
266
|
+
...(minItems != null && { minItems }),
|
|
267
|
+
...(maxItems != null && { maxItems }),
|
|
268
|
+
},
|
|
240
269
|
},
|
|
241
270
|
required: ['elements'],
|
|
242
271
|
additionalProperties: false,
|
|
@@ -288,6 +317,23 @@ export const array = <ELEMENT>({
|
|
|
288
317
|
});
|
|
289
318
|
}
|
|
290
319
|
|
|
320
|
+
const lengthValidationError = getArrayLengthValidationError({
|
|
321
|
+
value: outerValue.elements,
|
|
322
|
+
minItems,
|
|
323
|
+
maxItems,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
if (lengthValidationError != null) {
|
|
327
|
+
throw new NoObjectGeneratedError({
|
|
328
|
+
message: 'No object generated: response did not match schema.',
|
|
329
|
+
cause: lengthValidationError,
|
|
330
|
+
text,
|
|
331
|
+
response: context.response,
|
|
332
|
+
usage: context.usage,
|
|
333
|
+
finishReason: context.finishReason,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
291
337
|
const validatedElements: Array<ELEMENT> = [];
|
|
292
338
|
for (const element of outerValue.elements) {
|
|
293
339
|
const validationResult = await safeValidateTypes({
|
|
@@ -372,6 +418,16 @@ export const array = <ELEMENT>({
|
|
|
372
418
|
publishedElements < partialOutput.length;
|
|
373
419
|
publishedElements++
|
|
374
420
|
) {
|
|
421
|
+
if (maxItems != null && publishedElements >= maxItems) {
|
|
422
|
+
controller.error(
|
|
423
|
+
getArrayLengthValidationError({
|
|
424
|
+
value: partialOutput,
|
|
425
|
+
maxItems,
|
|
426
|
+
}),
|
|
427
|
+
);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
375
431
|
controller.enqueue(partialOutput[publishedElements]);
|
|
376
432
|
}
|
|
377
433
|
}
|
|
@@ -381,6 +437,60 @@ export const array = <ELEMENT>({
|
|
|
381
437
|
};
|
|
382
438
|
};
|
|
383
439
|
|
|
440
|
+
function validateArrayBound({
|
|
441
|
+
name,
|
|
442
|
+
value,
|
|
443
|
+
}: {
|
|
444
|
+
name: 'minItems' | 'maxItems';
|
|
445
|
+
value: number | undefined;
|
|
446
|
+
}) {
|
|
447
|
+
if (value == null) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (!Number.isInteger(value)) {
|
|
452
|
+
throw new InvalidArgumentError({
|
|
453
|
+
parameter: name,
|
|
454
|
+
value,
|
|
455
|
+
message: `${name} must be an integer`,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (value < 0) {
|
|
460
|
+
throw new InvalidArgumentError({
|
|
461
|
+
parameter: name,
|
|
462
|
+
value,
|
|
463
|
+
message: `${name} must be greater than or equal to 0`,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function getArrayLengthValidationError({
|
|
469
|
+
value,
|
|
470
|
+
minItems,
|
|
471
|
+
maxItems,
|
|
472
|
+
}: {
|
|
473
|
+
value: Array<unknown>;
|
|
474
|
+
minItems?: number;
|
|
475
|
+
maxItems?: number;
|
|
476
|
+
}): TypeValidationError | undefined {
|
|
477
|
+
if (minItems != null && value.length < minItems) {
|
|
478
|
+
return new TypeValidationError({
|
|
479
|
+
value,
|
|
480
|
+
cause: `elements array must contain at least ${minItems} items`,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (maxItems != null && value.length > maxItems) {
|
|
485
|
+
return new TypeValidationError({
|
|
486
|
+
value,
|
|
487
|
+
cause: `elements array must contain at most ${maxItems} items`,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return undefined;
|
|
492
|
+
}
|
|
493
|
+
|
|
384
494
|
/**
|
|
385
495
|
* Output specification for choice generation.
|
|
386
496
|
* When the model generates a text response, it will return a one of the choice options.
|
|
@@ -10,6 +10,15 @@ const CHUNKING_REGEXPS = {
|
|
|
10
10
|
line: /\n+/m,
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
// Browsers heavily throttle timers in hidden documents (e.g. background tabs),
|
|
14
|
+
// which would stall the smoothing delay and, through backpressure, the entire
|
|
15
|
+
// stream. Smoothing has no visual purpose there, so the delay is skipped.
|
|
16
|
+
function isDocumentHidden(): boolean {
|
|
17
|
+
return (
|
|
18
|
+
typeof document !== 'undefined' && document.visibilityState === 'hidden'
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
13
22
|
/**
|
|
14
23
|
* Detects the first chunk in a buffer.
|
|
15
24
|
*
|
|
@@ -22,7 +31,7 @@ export type ChunkDetector = (buffer: string) => string | undefined | null;
|
|
|
22
31
|
/**
|
|
23
32
|
* Smooths text and reasoning streaming output.
|
|
24
33
|
*
|
|
25
|
-
* @param delayInMs - The delay in milliseconds between each chunk. Defaults to 10ms. Can be set to `null` to skip the delay.
|
|
34
|
+
* @param delayInMs - The delay in milliseconds between each chunk. Defaults to 10ms. Can be set to `null` to skip the delay. The delay is skipped while the document is hidden (e.g. browser background tabs), where timer throttling would otherwise stall the stream.
|
|
26
35
|
* @param chunking - Controls how the text is chunked for streaming. Use "word" to stream word by word (default), "line" to stream line by line, provide a custom RegExp pattern that does not match the empty string for custom chunking, provide an Intl.Segmenter for locale-aware word segmentation (recommended for CJK languages), or provide a custom ChunkDetector function.
|
|
27
36
|
*
|
|
28
37
|
* @returns A transform stream that smooths text streaming output.
|
|
@@ -126,7 +135,10 @@ export function smoothStream<TOOLS extends ToolSet>({
|
|
|
126
135
|
function flushBuffer(
|
|
127
136
|
controller: TransformStreamDefaultController<TextStreamPart<TOOLS>>,
|
|
128
137
|
) {
|
|
129
|
-
if (
|
|
138
|
+
if (
|
|
139
|
+
type !== undefined &&
|
|
140
|
+
(buffer.length > 0 || providerMetadata != null)
|
|
141
|
+
) {
|
|
130
142
|
controller.enqueue({
|
|
131
143
|
type,
|
|
132
144
|
text: buffer,
|
|
@@ -148,7 +160,10 @@ export function smoothStream<TOOLS extends ToolSet>({
|
|
|
148
160
|
}
|
|
149
161
|
|
|
150
162
|
// Flush buffer when type or id changes
|
|
151
|
-
if (
|
|
163
|
+
if (
|
|
164
|
+
(chunk.type !== type || chunk.id !== id) &&
|
|
165
|
+
(buffer.length > 0 || providerMetadata != null)
|
|
166
|
+
) {
|
|
152
167
|
flushBuffer(controller);
|
|
153
168
|
}
|
|
154
169
|
|
|
@@ -167,7 +182,7 @@ export function smoothStream<TOOLS extends ToolSet>({
|
|
|
167
182
|
controller.enqueue({ type, text: match, id });
|
|
168
183
|
buffer = buffer.slice(match.length);
|
|
169
184
|
|
|
170
|
-
await delay(delayInMs);
|
|
185
|
+
await delay(isDocumentHidden() ? null : delayInMs);
|
|
171
186
|
}
|
|
172
187
|
},
|
|
173
188
|
});
|
|
@@ -95,6 +95,7 @@ import {
|
|
|
95
95
|
type ActiveToolSubset,
|
|
96
96
|
} from './filter-active-tools';
|
|
97
97
|
import type {
|
|
98
|
+
GenerateTextAbortEvent,
|
|
98
99
|
GenerateTextEndEvent,
|
|
99
100
|
GenerateTextOnStartCallback,
|
|
100
101
|
GenerateTextOnStepEndCallback,
|
|
@@ -329,12 +330,7 @@ export type StreamTextOnEndCallback<
|
|
|
329
330
|
export type StreamTextOnAbortCallback<
|
|
330
331
|
TOOLS extends ToolSet,
|
|
331
332
|
RUNTIME_CONTEXT extends Context,
|
|
332
|
-
> = Callback<
|
|
333
|
-
/**
|
|
334
|
-
* Details for all previously finished steps.
|
|
335
|
-
*/
|
|
336
|
-
readonly steps: StepResult<TOOLS, RUNTIME_CONTEXT>[];
|
|
337
|
-
}>;
|
|
333
|
+
> = Callback<GenerateTextAbortEvent<TOOLS, RUNTIME_CONTEXT>>;
|
|
338
334
|
|
|
339
335
|
/**
|
|
340
336
|
* Generate a text and call tools for a given prompt using a language model.
|
package/src/ui/chat.ts
CHANGED
|
@@ -420,8 +420,8 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
420
420
|
|
|
421
421
|
// update the message with the new content
|
|
422
422
|
this.state.replaceMessage(messageIndex, {
|
|
423
|
-
...uiMessage,
|
|
424
423
|
id: message.messageId,
|
|
424
|
+
...uiMessage,
|
|
425
425
|
role: uiMessage.role ?? 'user',
|
|
426
426
|
metadata: message.metadata,
|
|
427
427
|
} as UI_MESSAGE);
|
|
@@ -212,6 +212,12 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
|
|
|
212
212
|
const toolName = getToolName(part);
|
|
213
213
|
|
|
214
214
|
if (part.state !== 'input-streaming') {
|
|
215
|
+
const callProviderMetadata =
|
|
216
|
+
part.callProviderMetadata ??
|
|
217
|
+
(part.state === 'output-error'
|
|
218
|
+
? part.resultProviderMetadata
|
|
219
|
+
: undefined);
|
|
220
|
+
|
|
215
221
|
content.push({
|
|
216
222
|
type: 'tool-call' as const,
|
|
217
223
|
toolCallId: part.toolCallId,
|
|
@@ -222,8 +228,8 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
|
|
|
222
228
|
('rawInput' in part ? part.rawInput : undefined))
|
|
223
229
|
: part.input,
|
|
224
230
|
providerExecuted: part.providerExecuted,
|
|
225
|
-
...(
|
|
226
|
-
? { providerOptions:
|
|
231
|
+
...(callProviderMetadata != null
|
|
232
|
+
? { providerOptions: callProviderMetadata }
|
|
227
233
|
: {}),
|
|
228
234
|
});
|
|
229
235
|
|