ai 7.0.79 → 7.0.83
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 +48 -0
- package/dist/index.d.ts +139 -53
- package/dist/index.js +872 -496
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +9 -1
- package/dist/internal/index.js +4 -2
- package/dist/internal/index.js.map +1 -1
- package/dist/test/index.d.ts +4 -1
- package/dist/test/index.js +4 -0
- package/dist/test/index.js.map +1 -1
- package/docs/03-agents/06-policy-tool-approvals.mdx +1 -1
- package/docs/03-agents/06-tool-approvals.mdx +6 -0
- package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +6 -4
- package/docs/03-ai-sdk-core/50-error-handling.mdx +17 -2
- package/docs/04-ai-sdk-ui/03-chatbot-tool-usage.mdx +6 -0
- package/docs/04-ai-sdk-ui/50-stream-protocol.mdx +2 -2
- package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +8 -7
- package/docs/07-reference/01-ai-sdk-core/06-embed-many.mdx +6 -2
- package/docs/07-reference/01-ai-sdk-core/16-tool-loop-agent.mdx +1 -1
- package/docs/07-reference/02-ai-sdk-ui/31-convert-to-model-messages.mdx +2 -2
- package/docs/07-reference/02-ai-sdk-ui/40-create-ui-message-stream.mdx +35 -6
- package/docs/07-reference/04-ai-sdk-workflow/03-generate-video.mdx +157 -0
- package/docs/07-reference/04-ai-sdk-workflow/index.mdx +6 -0
- package/docs/07-reference/05-ai-sdk-errors/ai-stream-provider-error.mdx +52 -0
- package/docs/07-reference/05-ai-sdk-errors/index.mdx +1 -0
- package/package.json +14 -3
- package/src/agent/create-agent-ui-stream.ts +2 -2
- package/src/embed/embed-many.ts +78 -8
- package/src/error/index.ts +1 -0
- package/src/error/stream-provider-error.ts +78 -0
- package/src/generate-text/execute-tools-from-stream.ts +3 -0
- package/src/generate-text/generate-text-result.ts +2 -1
- package/src/generate-text/generate-text.ts +6 -3
- package/src/generate-text/stream-language-model-call.ts +8 -0
- package/src/generate-text/to-response-messages.ts +1 -0
- package/src/generate-text/tool-approval-configuration.ts +5 -1
- package/src/generate-text/tool-approval-request-output.ts +5 -0
- package/src/middleware/wrap-embedding-model.ts +11 -2
- package/src/model/get-embedding-model-max-input-bytes-per-call.ts +15 -0
- package/src/prompt/content-part.ts +1 -0
- package/src/prompt/normalize-stream-provider-error.ts +131 -0
- package/src/test/mock-embedding-model-v4.ts +9 -0
- package/src/ui/convert-to-model-messages.ts +3 -0
- package/src/ui/direct-chat-transport.ts +2 -2
- package/src/ui/last-assistant-message-is-complete-with-approval-responses.ts +1 -0
- package/src/ui/process-ui-message-stream.ts +6 -0
- package/src/ui/ui-messages.ts +10 -0
- package/src/ui/validate-ui-messages.ts +146 -55
- package/src/ui-message-stream/create-ui-message-stream.ts +47 -17
- package/src/ui-message-stream/handle-ui-message-stream-finish.ts +51 -14
- package/src/ui-message-stream/index.ts +5 -1
- package/src/ui-message-stream/to-ui-message-chunk.ts +1 -0
- package/src/ui-message-stream/to-ui-message-stream.ts +108 -26
- package/src/ui-message-stream/ui-message-chunks.ts +2 -0
- package/src/ui-message-stream/ui-message-stream-on-end-callback.ts +7 -0
- package/src/ui-message-stream/ui-message-stream-outcome.ts +12 -0
- package/src/ui-message-stream/ui-message-stream-writer.ts +15 -0
- package/src/util/create-stitchable-stream.ts +31 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { AISDKError } from '@ai-sdk/provider';
|
|
2
|
+
import { isProviderStreamError } from '@ai-sdk/provider-utils';
|
|
3
|
+
import { StreamProviderError } from '../error/stream-provider-error';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Normalizes well-formed provider error payloads without changing existing
|
|
7
|
+
* Error instances or malformed/unknown values.
|
|
8
|
+
*/
|
|
9
|
+
export function normalizeStreamProviderError(error: unknown): unknown {
|
|
10
|
+
if (
|
|
11
|
+
isError(error) ||
|
|
12
|
+
AISDKError.isInstance(error) ||
|
|
13
|
+
StreamProviderError.isInstance(error)
|
|
14
|
+
) {
|
|
15
|
+
return error;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const outer = asRecord(error);
|
|
19
|
+
if (outer == null) {
|
|
20
|
+
return error;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const providerStreamError = isProviderStreamError(error);
|
|
24
|
+
const details = providerStreamError
|
|
25
|
+
? outer
|
|
26
|
+
: (asRecord(asRecord(outer.response)?.error) ??
|
|
27
|
+
asRecord(outer.error) ??
|
|
28
|
+
outer);
|
|
29
|
+
|
|
30
|
+
if (typeof details.message !== 'string') {
|
|
31
|
+
return error;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const type = getString(details.type) ?? getString(outer.type);
|
|
35
|
+
const code = getStringOrNumber(details.code) ?? getStringOrNumber(outer.code);
|
|
36
|
+
const explicitStatusCode =
|
|
37
|
+
getHttpStatusCode(details.statusCode) ??
|
|
38
|
+
getHttpStatusCode(outer.statusCode) ??
|
|
39
|
+
getHttpStatusCode(details.status_code) ??
|
|
40
|
+
getHttpStatusCode(outer.status_code) ??
|
|
41
|
+
getHttpStatusCode(details.status) ??
|
|
42
|
+
getHttpStatusCode(outer.status) ??
|
|
43
|
+
getHttpStatusCode(details.code) ??
|
|
44
|
+
getHttpStatusCode(outer.code);
|
|
45
|
+
const messageMetadata = inferExactMessageMetadata(details.message);
|
|
46
|
+
const statusCode = explicitStatusCode ?? messageMetadata?.statusCode;
|
|
47
|
+
const explicitRetryability =
|
|
48
|
+
getBoolean(details.isRetryable) ??
|
|
49
|
+
getBoolean(outer.isRetryable) ??
|
|
50
|
+
getBoolean(details.is_retryable) ??
|
|
51
|
+
getBoolean(outer.is_retryable);
|
|
52
|
+
|
|
53
|
+
return new StreamProviderError({
|
|
54
|
+
message: details.message,
|
|
55
|
+
type,
|
|
56
|
+
code,
|
|
57
|
+
statusCode,
|
|
58
|
+
isRetryable:
|
|
59
|
+
explicitRetryability ??
|
|
60
|
+
messageMetadata?.isRetryable ??
|
|
61
|
+
isRetryableStatusCode(statusCode),
|
|
62
|
+
data: providerStreamError ? error.data : error,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function inferExactMessageMetadata(
|
|
67
|
+
message: string,
|
|
68
|
+
): { statusCode: number; isRetryable: true } | undefined {
|
|
69
|
+
switch (message.trim().toLowerCase()) {
|
|
70
|
+
case 'overloaded':
|
|
71
|
+
case 'overloaded error':
|
|
72
|
+
case 'model overloaded':
|
|
73
|
+
return { statusCode: 503, isRetryable: true };
|
|
74
|
+
case 'internal server error':
|
|
75
|
+
return { statusCode: 500, isRetryable: true };
|
|
76
|
+
case 'service unavailable':
|
|
77
|
+
return { statusCode: 503, isRetryable: true };
|
|
78
|
+
default:
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isRetryableStatusCode(statusCode: number | undefined): boolean {
|
|
84
|
+
return (
|
|
85
|
+
statusCode != null &&
|
|
86
|
+
(statusCode === 408 ||
|
|
87
|
+
statusCode === 409 ||
|
|
88
|
+
statusCode === 429 ||
|
|
89
|
+
statusCode >= 500)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
94
|
+
return typeof value === 'object' && value != null
|
|
95
|
+
? (value as Record<string, unknown>)
|
|
96
|
+
: undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// `instanceof` misses Error instances created in another JavaScript realm.
|
|
100
|
+
function isError(value: unknown): value is Error {
|
|
101
|
+
return (
|
|
102
|
+
value instanceof Error ||
|
|
103
|
+
Object.prototype.toString.call(value) === '[object Error]'
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getString(value: unknown): string | undefined {
|
|
108
|
+
return typeof value === 'string' ? value : undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getStringOrNumber(value: unknown): string | number | undefined {
|
|
112
|
+
return typeof value === 'string' || typeof value === 'number'
|
|
113
|
+
? value
|
|
114
|
+
: undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getBoolean(value: unknown): boolean | undefined {
|
|
118
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getHttpStatusCode(value: unknown): number | undefined {
|
|
122
|
+
const statusCode =
|
|
123
|
+
typeof value === 'string' && /^\d{3}$/.test(value) ? Number(value) : value;
|
|
124
|
+
|
|
125
|
+
return typeof statusCode === 'number' &&
|
|
126
|
+
Number.isInteger(statusCode) &&
|
|
127
|
+
statusCode >= 400 &&
|
|
128
|
+
statusCode <= 599
|
|
129
|
+
? statusCode
|
|
130
|
+
: undefined;
|
|
131
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EmbeddingModelV4 } from '@ai-sdk/provider';
|
|
2
|
+
import { EXPERIMENTAL_EMBEDDING_MODEL_MAX_INPUT_BYTES_PER_CALL } from '@ai-sdk/provider-utils';
|
|
2
3
|
import { notImplemented } from './not-implemented';
|
|
3
4
|
|
|
4
5
|
export class MockEmbeddingModelV4 implements EmbeddingModelV4 {
|
|
@@ -7,6 +8,10 @@ export class MockEmbeddingModelV4 implements EmbeddingModelV4 {
|
|
|
7
8
|
readonly provider: EmbeddingModelV4['provider'];
|
|
8
9
|
readonly modelId: EmbeddingModelV4['modelId'];
|
|
9
10
|
readonly maxEmbeddingsPerCall: EmbeddingModelV4['maxEmbeddingsPerCall'];
|
|
11
|
+
readonly [EXPERIMENTAL_EMBEDDING_MODEL_MAX_INPUT_BYTES_PER_CALL]:
|
|
12
|
+
| PromiseLike<number | undefined>
|
|
13
|
+
| number
|
|
14
|
+
| undefined;
|
|
10
15
|
readonly supportsParallelCalls: EmbeddingModelV4['supportsParallelCalls'];
|
|
11
16
|
|
|
12
17
|
doEmbed: EmbeddingModelV4['doEmbed'];
|
|
@@ -17,12 +22,14 @@ export class MockEmbeddingModelV4 implements EmbeddingModelV4 {
|
|
|
17
22
|
provider = 'mock-provider',
|
|
18
23
|
modelId = 'mock-model-id',
|
|
19
24
|
maxEmbeddingsPerCall = 1,
|
|
25
|
+
maxInputBytesPerCall,
|
|
20
26
|
supportsParallelCalls = false,
|
|
21
27
|
doEmbed = notImplemented,
|
|
22
28
|
}: {
|
|
23
29
|
provider?: EmbeddingModelV4['provider'];
|
|
24
30
|
modelId?: EmbeddingModelV4['modelId'];
|
|
25
31
|
maxEmbeddingsPerCall?: EmbeddingModelV4['maxEmbeddingsPerCall'] | null;
|
|
32
|
+
maxInputBytesPerCall?: PromiseLike<number | undefined> | number | undefined;
|
|
26
33
|
supportsParallelCalls?: EmbeddingModelV4['supportsParallelCalls'];
|
|
27
34
|
doEmbed?:
|
|
28
35
|
| EmbeddingModelV4['doEmbed']
|
|
@@ -32,6 +39,8 @@ export class MockEmbeddingModelV4 implements EmbeddingModelV4 {
|
|
|
32
39
|
this.provider = provider;
|
|
33
40
|
this.modelId = modelId;
|
|
34
41
|
this.maxEmbeddingsPerCall = maxEmbeddingsPerCall ?? undefined;
|
|
42
|
+
this[EXPERIMENTAL_EMBEDDING_MODEL_MAX_INPUT_BYTES_PER_CALL] =
|
|
43
|
+
maxInputBytesPerCall;
|
|
35
44
|
this.supportsParallelCalls = supportsParallelCalls;
|
|
36
45
|
this.doEmbed = async options => {
|
|
37
46
|
this.doEmbedCalls.push(options);
|
|
@@ -233,6 +233,9 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
|
|
|
233
233
|
approvalId: part.approval.id,
|
|
234
234
|
toolCallId: part.toolCallId,
|
|
235
235
|
isAutomatic: part.approval.isAutomatic,
|
|
236
|
+
...(part.approval.requestReason != null
|
|
237
|
+
? { reason: part.approval.requestReason }
|
|
238
|
+
: {}),
|
|
236
239
|
...(part.approval.signature != null
|
|
237
240
|
? { signature: part.approval.signature }
|
|
238
241
|
: {}),
|
|
@@ -11,7 +11,7 @@ import type {
|
|
|
11
11
|
InferUITools,
|
|
12
12
|
UIMessage,
|
|
13
13
|
} from './ui-messages';
|
|
14
|
-
import {
|
|
14
|
+
import { validateUIMessagesForAgent } from './validate-ui-messages';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Options for the `DirectChatTransport` class.
|
|
@@ -93,7 +93,7 @@ export class DirectChatTransport<
|
|
|
93
93
|
ReadableStream<UIMessageChunk>
|
|
94
94
|
> {
|
|
95
95
|
// Validate the incoming UI messages
|
|
96
|
-
const validatedMessages = await
|
|
96
|
+
const validatedMessages = await validateUIMessagesForAgent<UI_MESSAGE>({
|
|
97
97
|
messages,
|
|
98
98
|
// tools are compatible; the casting is required because the context param is
|
|
99
99
|
// not available in ui messages
|
|
@@ -748,6 +748,9 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
748
748
|
toolInvocation.state = 'approval-requested';
|
|
749
749
|
toolInvocation.approval = {
|
|
750
750
|
id: chunk.approvalId,
|
|
751
|
+
...(chunk.reason != null
|
|
752
|
+
? { requestReason: chunk.reason }
|
|
753
|
+
: {}),
|
|
751
754
|
...(chunk.isAutomatic === true ? { isAutomatic: true } : {}),
|
|
752
755
|
...(chunk.signature != null
|
|
753
756
|
? { signature: chunk.signature }
|
|
@@ -770,6 +773,9 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
770
773
|
toolInvocation.approval = {
|
|
771
774
|
id: chunk.approvalId,
|
|
772
775
|
approved: chunk.approved,
|
|
776
|
+
...(approval.requestReason != null
|
|
777
|
+
? { requestReason: approval.requestReason }
|
|
778
|
+
: {}),
|
|
773
779
|
...(chunk.reason != null ? { reason: chunk.reason } : {}),
|
|
774
780
|
...(approval.isAutomatic === true ? { isAutomatic: true } : {}),
|
|
775
781
|
...(approval.signature != null
|
package/src/ui/ui-messages.ts
CHANGED
|
@@ -319,6 +319,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
|
|
|
319
319
|
approval: {
|
|
320
320
|
id: string;
|
|
321
321
|
approved?: never;
|
|
322
|
+
requestReason?: string;
|
|
322
323
|
reason?: never;
|
|
323
324
|
isAutomatic?: boolean;
|
|
324
325
|
signature?: string;
|
|
@@ -333,6 +334,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
|
|
|
333
334
|
approval: {
|
|
334
335
|
id: string;
|
|
335
336
|
approved: boolean;
|
|
337
|
+
requestReason?: string;
|
|
336
338
|
reason?: string;
|
|
337
339
|
isAutomatic?: boolean;
|
|
338
340
|
signature?: string;
|
|
@@ -349,6 +351,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
|
|
|
349
351
|
approval?: {
|
|
350
352
|
id: string;
|
|
351
353
|
approved: true;
|
|
354
|
+
requestReason?: string;
|
|
352
355
|
reason?: string;
|
|
353
356
|
isAutomatic?: boolean;
|
|
354
357
|
signature?: string;
|
|
@@ -365,6 +368,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
|
|
|
365
368
|
approval?: {
|
|
366
369
|
id: string;
|
|
367
370
|
approved: true;
|
|
371
|
+
requestReason?: string;
|
|
368
372
|
reason?: string;
|
|
369
373
|
isAutomatic?: boolean;
|
|
370
374
|
signature?: string;
|
|
@@ -379,6 +383,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
|
|
|
379
383
|
approval: {
|
|
380
384
|
id: string;
|
|
381
385
|
approved: false;
|
|
386
|
+
requestReason?: string;
|
|
382
387
|
reason?: string;
|
|
383
388
|
isAutomatic?: boolean;
|
|
384
389
|
signature?: string;
|
|
@@ -437,6 +442,7 @@ export type DynamicToolUIPart = {
|
|
|
437
442
|
approval: {
|
|
438
443
|
id: string;
|
|
439
444
|
approved?: never;
|
|
445
|
+
requestReason?: string;
|
|
440
446
|
reason?: never;
|
|
441
447
|
isAutomatic?: boolean;
|
|
442
448
|
signature?: string;
|
|
@@ -451,6 +457,7 @@ export type DynamicToolUIPart = {
|
|
|
451
457
|
approval: {
|
|
452
458
|
id: string;
|
|
453
459
|
approved: boolean;
|
|
460
|
+
requestReason?: string;
|
|
454
461
|
reason?: string;
|
|
455
462
|
isAutomatic?: boolean;
|
|
456
463
|
signature?: string;
|
|
@@ -467,6 +474,7 @@ export type DynamicToolUIPart = {
|
|
|
467
474
|
approval?: {
|
|
468
475
|
id: string;
|
|
469
476
|
approved: true;
|
|
477
|
+
requestReason?: string;
|
|
470
478
|
reason?: string;
|
|
471
479
|
isAutomatic?: boolean;
|
|
472
480
|
signature?: string;
|
|
@@ -482,6 +490,7 @@ export type DynamicToolUIPart = {
|
|
|
482
490
|
approval?: {
|
|
483
491
|
id: string;
|
|
484
492
|
approved: true;
|
|
493
|
+
requestReason?: string;
|
|
485
494
|
reason?: string;
|
|
486
495
|
isAutomatic?: boolean;
|
|
487
496
|
signature?: string;
|
|
@@ -496,6 +505,7 @@ export type DynamicToolUIPart = {
|
|
|
496
505
|
approval: {
|
|
497
506
|
id: string;
|
|
498
507
|
approved: false;
|
|
508
|
+
requestReason?: string;
|
|
499
509
|
reason?: string;
|
|
500
510
|
isAutomatic?: boolean;
|
|
501
511
|
signature?: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TypeValidationError, type JSONObject } from '@ai-sdk/provider';
|
|
2
2
|
import {
|
|
3
3
|
lazySchema,
|
|
4
|
+
safeValidateTypes,
|
|
4
5
|
validateTypes,
|
|
5
6
|
zodSchema,
|
|
6
7
|
type FlexibleSchema,
|
|
@@ -13,6 +14,7 @@ import { providerMetadataSchema } from '../types/provider-metadata';
|
|
|
13
14
|
import { z, type ZodType } from '../util/zod';
|
|
14
15
|
import type {
|
|
15
16
|
DataUIPart,
|
|
17
|
+
DynamicToolUIPart,
|
|
16
18
|
InferUIMessageData,
|
|
17
19
|
InferUIMessageTools,
|
|
18
20
|
ToolUIPart,
|
|
@@ -26,6 +28,25 @@ const toolMetadataSchema: ZodType<JSONObject> = z.record(
|
|
|
26
28
|
|
|
27
29
|
const providerReferenceSchema = z.record(z.string(), z.string());
|
|
28
30
|
|
|
31
|
+
function isEmptyObject(value: unknown): value is Record<string, never> {
|
|
32
|
+
return (
|
|
33
|
+
value != null &&
|
|
34
|
+
typeof value === 'object' &&
|
|
35
|
+
!Array.isArray(value) &&
|
|
36
|
+
Object.keys(value).length === 0
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function asDynamicToolPart(toolPart: ToolUIPart): DynamicToolUIPart {
|
|
41
|
+
const { type, ...part } = toolPart;
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
...part,
|
|
45
|
+
type: 'dynamic-tool',
|
|
46
|
+
toolName: type.slice(5),
|
|
47
|
+
} as DynamicToolUIPart;
|
|
48
|
+
}
|
|
49
|
+
|
|
29
50
|
const uiMessagesSchema = lazySchema(() =>
|
|
30
51
|
zodSchema(
|
|
31
52
|
z
|
|
@@ -132,6 +153,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
132
153
|
approval: z.object({
|
|
133
154
|
id: z.string(),
|
|
134
155
|
approved: z.never().optional(),
|
|
156
|
+
requestReason: z.string().optional(),
|
|
135
157
|
reason: z.never().optional(),
|
|
136
158
|
isAutomatic: z.boolean().optional(),
|
|
137
159
|
signature: z.string().optional(),
|
|
@@ -151,6 +173,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
151
173
|
approval: z.object({
|
|
152
174
|
id: z.string(),
|
|
153
175
|
approved: z.boolean(),
|
|
176
|
+
requestReason: z.string().optional(),
|
|
154
177
|
reason: z.string().optional(),
|
|
155
178
|
isAutomatic: z.boolean().optional(),
|
|
156
179
|
signature: z.string().optional(),
|
|
@@ -173,6 +196,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
173
196
|
.object({
|
|
174
197
|
id: z.string(),
|
|
175
198
|
approved: z.literal(true),
|
|
199
|
+
requestReason: z.string().optional(),
|
|
176
200
|
reason: z.string().optional(),
|
|
177
201
|
isAutomatic: z.boolean().optional(),
|
|
178
202
|
signature: z.string().optional(),
|
|
@@ -196,6 +220,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
196
220
|
.object({
|
|
197
221
|
id: z.string(),
|
|
198
222
|
approved: z.literal(true),
|
|
223
|
+
requestReason: z.string().optional(),
|
|
199
224
|
reason: z.string().optional(),
|
|
200
225
|
isAutomatic: z.boolean().optional(),
|
|
201
226
|
signature: z.string().optional(),
|
|
@@ -216,6 +241,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
216
241
|
approval: z.object({
|
|
217
242
|
id: z.string(),
|
|
218
243
|
approved: z.literal(false),
|
|
244
|
+
requestReason: z.string().optional(),
|
|
219
245
|
reason: z.string().optional(),
|
|
220
246
|
isAutomatic: z.boolean().optional(),
|
|
221
247
|
signature: z.string().optional(),
|
|
@@ -258,6 +284,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
258
284
|
approval: z.object({
|
|
259
285
|
id: z.string(),
|
|
260
286
|
approved: z.never().optional(),
|
|
287
|
+
requestReason: z.string().optional(),
|
|
261
288
|
reason: z.never().optional(),
|
|
262
289
|
isAutomatic: z.boolean().optional(),
|
|
263
290
|
signature: z.string().optional(),
|
|
@@ -276,6 +303,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
276
303
|
approval: z.object({
|
|
277
304
|
id: z.string(),
|
|
278
305
|
approved: z.boolean(),
|
|
306
|
+
requestReason: z.string().optional(),
|
|
279
307
|
reason: z.string().optional(),
|
|
280
308
|
isAutomatic: z.boolean().optional(),
|
|
281
309
|
signature: z.string().optional(),
|
|
@@ -297,6 +325,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
297
325
|
.object({
|
|
298
326
|
id: z.string(),
|
|
299
327
|
approved: z.literal(true),
|
|
328
|
+
requestReason: z.string().optional(),
|
|
300
329
|
reason: z.string().optional(),
|
|
301
330
|
isAutomatic: z.boolean().optional(),
|
|
302
331
|
signature: z.string().optional(),
|
|
@@ -319,6 +348,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
319
348
|
.object({
|
|
320
349
|
id: z.string(),
|
|
321
350
|
approved: z.literal(true),
|
|
351
|
+
requestReason: z.string().optional(),
|
|
322
352
|
reason: z.string().optional(),
|
|
323
353
|
isAutomatic: z.boolean().optional(),
|
|
324
354
|
signature: z.string().optional(),
|
|
@@ -338,6 +368,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
338
368
|
approval: z.object({
|
|
339
369
|
id: z.string(),
|
|
340
370
|
approved: z.literal(false),
|
|
371
|
+
requestReason: z.string().optional(),
|
|
341
372
|
reason: z.string().optional(),
|
|
342
373
|
isAutomatic: z.boolean().optional(),
|
|
343
374
|
signature: z.string().optional(),
|
|
@@ -374,17 +405,7 @@ export type SafeValidateUIMessagesResult<UI_MESSAGE extends UIMessage> =
|
|
|
374
405
|
error: Error;
|
|
375
406
|
};
|
|
376
407
|
|
|
377
|
-
|
|
378
|
-
* Validates a list of UI messages like `validateUIMessages`,
|
|
379
|
-
* but instead of throwing it returns `{ success: true, data }`
|
|
380
|
-
* or `{ success: false, error }`.
|
|
381
|
-
*/
|
|
382
|
-
export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>({
|
|
383
|
-
messages,
|
|
384
|
-
metadataSchema,
|
|
385
|
-
dataSchemas,
|
|
386
|
-
tools,
|
|
387
|
-
}: {
|
|
408
|
+
type ValidateUIMessagesOptions<UI_MESSAGE extends UIMessage> = {
|
|
388
409
|
messages: unknown;
|
|
389
410
|
metadataSchema?: FlexibleSchema<UIMessage['metadata']>;
|
|
390
411
|
dataSchemas?: {
|
|
@@ -398,7 +419,21 @@ export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>({
|
|
|
398
419
|
InferUIMessageTools<UI_MESSAGE>[NAME]['output']
|
|
399
420
|
>;
|
|
400
421
|
};
|
|
401
|
-
}
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
async function safeValidateUIMessagesInternal<UI_MESSAGE extends UIMessage>(
|
|
425
|
+
{
|
|
426
|
+
messages,
|
|
427
|
+
metadataSchema,
|
|
428
|
+
dataSchemas,
|
|
429
|
+
tools,
|
|
430
|
+
}: ValidateUIMessagesOptions<UI_MESSAGE>,
|
|
431
|
+
{
|
|
432
|
+
convertMissingTerminalToolsToDynamic,
|
|
433
|
+
}: {
|
|
434
|
+
convertMissingTerminalToolsToDynamic: boolean;
|
|
435
|
+
},
|
|
436
|
+
): Promise<SafeValidateUIMessagesResult<UI_MESSAGE>> {
|
|
402
437
|
try {
|
|
403
438
|
if (messages == null) {
|
|
404
439
|
return {
|
|
@@ -429,7 +464,10 @@ export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>({
|
|
|
429
464
|
}
|
|
430
465
|
}
|
|
431
466
|
|
|
432
|
-
|
|
467
|
+
const shouldValidateToolParts =
|
|
468
|
+
tools != null || convertMissingTerminalToolsToDynamic;
|
|
469
|
+
|
|
470
|
+
if (dataSchemas || shouldValidateToolParts) {
|
|
433
471
|
for (const [msgIdx, message] of validatedMessages.entries()) {
|
|
434
472
|
for (const [partIdx, part] of message.parts.entries()) {
|
|
435
473
|
// Data part validation
|
|
@@ -465,19 +503,26 @@ export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>({
|
|
|
465
503
|
}
|
|
466
504
|
|
|
467
505
|
// Tool part validation
|
|
468
|
-
if (
|
|
506
|
+
if (shouldValidateToolParts && part.type.startsWith('tool-')) {
|
|
469
507
|
const toolPart = part as ToolUIPart<
|
|
470
508
|
InferUIMessageTools<UI_MESSAGE>
|
|
471
509
|
>;
|
|
472
510
|
const toolName = toolPart.type.slice(5);
|
|
473
|
-
const tool = getOwn(tools, toolName);
|
|
511
|
+
const tool = tools == null ? undefined : getOwn(tools, toolName);
|
|
512
|
+
const isTerminal =
|
|
513
|
+
toolPart.state === 'output-available' ||
|
|
514
|
+
toolPart.state === 'output-error' ||
|
|
515
|
+
toolPart.state === 'output-denied';
|
|
474
516
|
|
|
475
|
-
if (
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
517
|
+
if (!tool && isTerminal) {
|
|
518
|
+
if (tools != null || convertMissingTerminalToolsToDynamic) {
|
|
519
|
+
// Persisted terminal history can reference tools that are no
|
|
520
|
+
// longer registered. Normalize those parts so callers do not
|
|
521
|
+
// receive unvalidated values under current static tool types.
|
|
522
|
+
message.parts[partIdx] = asDynamicToolPart(
|
|
523
|
+
toolPart,
|
|
524
|
+
) as (typeof message.parts)[number];
|
|
525
|
+
}
|
|
481
526
|
continue;
|
|
482
527
|
}
|
|
483
528
|
|
|
@@ -497,19 +542,53 @@ export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>({
|
|
|
497
542
|
};
|
|
498
543
|
}
|
|
499
544
|
|
|
545
|
+
const inputValidationContext = {
|
|
546
|
+
field: `messages[${msgIdx}].parts[${partIdx}].input`,
|
|
547
|
+
entityName: toolName,
|
|
548
|
+
entityId: toolPart.toolCallId,
|
|
549
|
+
};
|
|
550
|
+
let convertToDynamic = false;
|
|
551
|
+
|
|
500
552
|
// Tool input validation
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
553
|
+
if (toolPart.state === 'output-error') {
|
|
554
|
+
// Failed calls can retain invalid input. Keep them loadable, but
|
|
555
|
+
// expose incompatible input as unknown instead of the current
|
|
556
|
+
// static tool input type.
|
|
557
|
+
if (toolPart.input !== undefined) {
|
|
558
|
+
const result = await safeValidateTypes({
|
|
559
|
+
value: toolPart.input,
|
|
560
|
+
schema: tool.inputSchema,
|
|
561
|
+
context: inputValidationContext,
|
|
562
|
+
});
|
|
563
|
+
convertToDynamic = !result.success;
|
|
564
|
+
}
|
|
565
|
+
} else if (toolPart.state === 'output-available') {
|
|
566
|
+
const result = await safeValidateTypes({
|
|
567
|
+
value: toolPart.input,
|
|
568
|
+
schema: tool.inputSchema,
|
|
569
|
+
context: inputValidationContext,
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
if (!result.success) {
|
|
573
|
+
// Empty terminal input can represent aborted or incomplete
|
|
574
|
+
// history whose input was never streamed. Preserve it without
|
|
575
|
+
// claiming that it matches the current static input type.
|
|
576
|
+
if (isEmptyObject(toolPart.input)) {
|
|
577
|
+
convertToDynamic = true;
|
|
578
|
+
} else {
|
|
579
|
+
throw result.error;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
} else if (
|
|
583
|
+
toolPart.state === 'input-available' ||
|
|
584
|
+
toolPart.state === 'approval-requested' ||
|
|
585
|
+
toolPart.state === 'approval-responded' ||
|
|
586
|
+
toolPart.state === 'output-denied'
|
|
587
|
+
) {
|
|
505
588
|
await validateTypes({
|
|
506
589
|
value: toolPart.input,
|
|
507
590
|
schema: tool.inputSchema,
|
|
508
|
-
context:
|
|
509
|
-
field: `messages[${msgIdx}].parts[${partIdx}].input`,
|
|
510
|
-
entityName: toolName,
|
|
511
|
-
entityId: toolPart.toolCallId,
|
|
512
|
-
},
|
|
591
|
+
context: inputValidationContext,
|
|
513
592
|
});
|
|
514
593
|
}
|
|
515
594
|
|
|
@@ -525,6 +604,12 @@ export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>({
|
|
|
525
604
|
},
|
|
526
605
|
});
|
|
527
606
|
}
|
|
607
|
+
|
|
608
|
+
if (convertToDynamic) {
|
|
609
|
+
message.parts[partIdx] = asDynamicToolPart(
|
|
610
|
+
toolPart,
|
|
611
|
+
) as (typeof message.parts)[number];
|
|
612
|
+
}
|
|
528
613
|
}
|
|
529
614
|
}
|
|
530
615
|
}
|
|
@@ -544,6 +629,19 @@ export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>({
|
|
|
544
629
|
}
|
|
545
630
|
}
|
|
546
631
|
|
|
632
|
+
/**
|
|
633
|
+
* Validates a list of UI messages like `validateUIMessages`,
|
|
634
|
+
* but instead of throwing it returns `{ success: true, data }`
|
|
635
|
+
* or `{ success: false, error }`.
|
|
636
|
+
*/
|
|
637
|
+
export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>(
|
|
638
|
+
options: ValidateUIMessagesOptions<UI_MESSAGE>,
|
|
639
|
+
): Promise<SafeValidateUIMessagesResult<UI_MESSAGE>> {
|
|
640
|
+
return safeValidateUIMessagesInternal(options, {
|
|
641
|
+
convertMissingTerminalToolsToDynamic: false,
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
|
|
547
645
|
/**
|
|
548
646
|
* Validates a list of UI messages.
|
|
549
647
|
*
|
|
@@ -551,31 +649,24 @@ export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>({
|
|
|
551
649
|
* the corresponding schemas are provided. Otherwise, they are assumed to be
|
|
552
650
|
* valid.
|
|
553
651
|
*/
|
|
554
|
-
export async function validateUIMessages<UI_MESSAGE extends UIMessage>(
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
};
|
|
573
|
-
}): Promise<Array<UI_MESSAGE>> {
|
|
574
|
-
const response = await safeValidateUIMessages({
|
|
575
|
-
messages,
|
|
576
|
-
metadataSchema,
|
|
577
|
-
dataSchemas,
|
|
578
|
-
tools,
|
|
652
|
+
export async function validateUIMessages<UI_MESSAGE extends UIMessage>(
|
|
653
|
+
options: ValidateUIMessagesOptions<UI_MESSAGE>,
|
|
654
|
+
): Promise<Array<UI_MESSAGE>> {
|
|
655
|
+
const response = await safeValidateUIMessages(options);
|
|
656
|
+
|
|
657
|
+
if (!response.success) throw response.error;
|
|
658
|
+
|
|
659
|
+
return response.data;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export async function validateUIMessagesForAgent<UI_MESSAGE extends UIMessage>(
|
|
663
|
+
options: ValidateUIMessagesOptions<UI_MESSAGE>,
|
|
664
|
+
): Promise<Array<UI_MESSAGE>> {
|
|
665
|
+
const response = await safeValidateUIMessagesInternal(options, {
|
|
666
|
+
// Agent tool sets can include ephemeral tools (for example, tools from a
|
|
667
|
+
// disconnected MCP server), so terminal history is converted to dynamic
|
|
668
|
+
// tool parts when those tools are no longer registered.
|
|
669
|
+
convertMissingTerminalToolsToDynamic: true,
|
|
579
670
|
});
|
|
580
671
|
|
|
581
672
|
if (!response.success) throw response.error;
|