@convex-dev/agent 0.7.0 → 0.7.2
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/README.md +5 -8
- package/dist/client/messages.d.ts +10 -0
- package/dist/client/messages.d.ts.map +1 -1
- package/dist/client/messages.js +1 -0
- package/dist/client/messages.js.map +1 -1
- package/dist/component/_generated/component.d.ts +15 -8
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/files.d.ts +6 -4
- package/dist/component/files.d.ts.map +1 -1
- package/dist/component/files.js +53 -42
- package/dist/component/files.js.map +1 -1
- package/dist/component/messages.d.ts +340 -338
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/messages.js +80 -16
- package/dist/component/messages.js.map +1 -1
- package/dist/component/schema.d.ts +782 -626
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +12 -2
- package/dist/component/schema.js.map +1 -1
- package/dist/component/streams.d.ts +16 -2
- package/dist/component/streams.d.ts.map +1 -1
- package/dist/component/streams.js +86 -30
- package/dist/component/streams.js.map +1 -1
- package/dist/component/vector/index.d.ts +9 -9
- package/dist/component/vector/index.d.ts.map +1 -1
- package/dist/component/vector/tables.d.ts +5 -5
- package/dist/component/vector/tables.d.ts.map +1 -1
- package/dist/component/vector/tables.js +1 -1
- package/dist/component/vector/tables.js.map +1 -1
- package/dist/errors.d.ts +2 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +106 -0
- package/dist/errors.js.map +1 -0
- package/dist/streaming/materializePersistedUIMessageChunks.d.ts +8 -2
- package/dist/streaming/materializePersistedUIMessageChunks.d.ts.map +1 -1
- package/dist/streaming/materializePersistedUIMessageChunks.js +34 -3
- package/dist/streaming/materializePersistedUIMessageChunks.js.map +1 -1
- package/dist/validators.d.ts +1952 -1952
- package/dist/vercel/UIMessages.js +1 -1
- package/dist/vercel/UIMessages.js.map +1 -1
- package/dist/vercel/client/definePlaygroundAPI.d.ts +132 -132
- package/dist/vercel/client/files.d.ts.map +1 -1
- package/dist/vercel/client/files.js +51 -27
- package/dist/vercel/client/files.js.map +1 -1
- package/dist/vercel/client/messages.d.ts +50 -32
- package/dist/vercel/client/messages.d.ts.map +1 -1
- package/dist/vercel/client/messages.js +2 -0
- package/dist/vercel/client/messages.js.map +1 -1
- package/dist/vercel/client/search.d.ts +475 -32
- package/dist/vercel/client/search.d.ts.map +1 -1
- package/dist/vercel/client/search.js +34 -9
- package/dist/vercel/client/search.js.map +1 -1
- package/dist/vercel/client/start.d.ts.map +1 -1
- package/dist/vercel/client/start.js +1 -0
- package/dist/vercel/client/start.js.map +1 -1
- package/dist/vercel/client/streamText.d.ts +1 -2
- package/dist/vercel/client/streamText.d.ts.map +1 -1
- package/dist/vercel/client/streamText.js +49 -17
- package/dist/vercel/client/streamText.js.map +1 -1
- package/dist/vercel/client/streaming.d.ts +318 -301
- package/dist/vercel/client/streaming.d.ts.map +1 -1
- package/dist/vercel/client/streaming.js +56 -21
- package/dist/vercel/client/streaming.js.map +1 -1
- package/dist/vercel/client/types.d.ts +2 -0
- package/dist/vercel/client/types.d.ts.map +1 -1
- package/dist/vercel/client/types.js.map +1 -1
- package/dist/vercel/client/utils.d.ts +9 -5
- package/dist/vercel/client/utils.d.ts.map +1 -1
- package/dist/vercel/client/utils.js +13 -13
- package/dist/vercel/client/utils.js.map +1 -1
- package/dist/vercel/fileMaterialization.d.ts +24 -0
- package/dist/vercel/fileMaterialization.d.ts.map +1 -0
- package/dist/vercel/fileMaterialization.js +118 -0
- package/dist/vercel/fileMaterialization.js.map +1 -0
- package/dist/vercel/index.d.ts +91 -90
- package/dist/vercel/index.d.ts.map +1 -1
- package/dist/vercel/index.js +2 -0
- package/dist/vercel/index.js.map +1 -1
- package/dist/vercel/mapping.d.ts.map +1 -1
- package/dist/vercel/mapping.js +7 -1
- package/dist/vercel/mapping.js.map +1 -1
- package/package.json +16 -9
- package/src/client/messages.ts +12 -0
- package/src/component/_generated/component.ts +18 -2
- package/src/component/files.test.ts +81 -6
- package/src/component/files.ts +69 -41
- package/src/component/messages.test.ts +496 -0
- package/src/component/messages.ts +105 -15
- package/src/component/schema.ts +16 -2
- package/src/component/streams.test.ts +109 -0
- package/src/component/streams.ts +123 -35
- package/src/component/vector/tables.ts +1 -1
- package/src/errors.test.ts +76 -0
- package/src/errors.ts +113 -0
- package/src/streaming/materializePersistedUIMessageChunks.test.ts +33 -0
- package/src/streaming/materializePersistedUIMessageChunks.ts +37 -0
- package/src/vercel/UIMessages.ts +1 -1
- package/src/vercel/client/files.test.ts +56 -0
- package/src/vercel/client/files.ts +53 -28
- package/src/vercel/client/index.test.ts +47 -0
- package/src/vercel/client/messages.ts +20 -0
- package/src/vercel/client/search.test.ts +183 -8
- package/src/vercel/client/search.ts +46 -12
- package/src/vercel/client/start.ts +1 -0
- package/src/vercel/client/streamText.test.ts +113 -17
- package/src/vercel/client/streamText.ts +55 -28
- package/src/vercel/client/streaming.test.ts +142 -7
- package/src/vercel/client/streaming.ts +73 -19
- package/src/vercel/client/types.ts +2 -0
- package/src/vercel/client/utils.test.ts +97 -0
- package/src/vercel/client/utils.ts +15 -15
- package/src/vercel/fileMaterialization.ts +174 -0
- package/src/vercel/index.ts +2 -0
- package/src/vercel/mapping.test.ts +117 -0
- package/src/vercel/mapping.ts +15 -1
- package/src/vercel/toUIMessages.test.ts +7 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { UIMessageChunk } from "ai";
|
|
2
|
+
import { MAX_FILE_SIZE, storeFile } from "./client/files.js";
|
|
3
|
+
import type { ActionCtx, AgentComponent, MutationCtx } from "./client/types.js";
|
|
4
|
+
|
|
5
|
+
export type MaterializedFileRef = { url: string; fileId: string };
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Replaces oversized inline files in persisted UI chunks with storage URLs.
|
|
9
|
+
* Recovery uses the accompanying URL-to-file ownership references to attach
|
|
10
|
+
* the stored files to the durable messages it creates.
|
|
11
|
+
*/
|
|
12
|
+
export async function materializeUIMessageChunkFiles(
|
|
13
|
+
ctx: ActionCtx,
|
|
14
|
+
component: AgentComponent,
|
|
15
|
+
parts: readonly UIMessageChunk[],
|
|
16
|
+
): Promise<{ parts: UIMessageChunk[]; fileRefs: MaterializedFileRef[] }> {
|
|
17
|
+
const fileRefs: MaterializedFileRef[] = [];
|
|
18
|
+
const materialized = await Promise.all(
|
|
19
|
+
parts.map(async (part): Promise<UIMessageChunk> => {
|
|
20
|
+
if (part.type === "tool-output-available") {
|
|
21
|
+
const result = await materializeCanonicalToolResultContentFiles(
|
|
22
|
+
ctx,
|
|
23
|
+
component,
|
|
24
|
+
part.output,
|
|
25
|
+
);
|
|
26
|
+
fileRefs.push(...result.fileRefs);
|
|
27
|
+
return { ...part, output: result.output };
|
|
28
|
+
}
|
|
29
|
+
if (part.type !== "file" && part.type !== "reasoning-file") {
|
|
30
|
+
return { ...part };
|
|
31
|
+
}
|
|
32
|
+
const file = await materializeInlineFile(
|
|
33
|
+
ctx,
|
|
34
|
+
component,
|
|
35
|
+
part.url,
|
|
36
|
+
part.mediaType,
|
|
37
|
+
);
|
|
38
|
+
if (!file) {
|
|
39
|
+
return { ...part };
|
|
40
|
+
}
|
|
41
|
+
fileRefs.push({ url: file.url, fileId: file.fileId });
|
|
42
|
+
return { ...part, url: file.url };
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
return { parts: materialized, fileRefs };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Materializes files in the AI SDK's canonical tool-result content output.
|
|
50
|
+
* Other tool outputs are application-defined and intentionally remain opaque.
|
|
51
|
+
*/
|
|
52
|
+
export async function materializeCanonicalToolResultContentFiles(
|
|
53
|
+
ctx: ActionCtx | MutationCtx,
|
|
54
|
+
component: AgentComponent,
|
|
55
|
+
output: unknown,
|
|
56
|
+
): Promise<{ output: unknown; fileRefs: MaterializedFileRef[] }> {
|
|
57
|
+
if (!isCanonicalToolResultContent(output)) {
|
|
58
|
+
return { output, fileRefs: [] };
|
|
59
|
+
}
|
|
60
|
+
const fileRefs: MaterializedFileRef[] = [];
|
|
61
|
+
const value = await Promise.all(
|
|
62
|
+
output.value.map(async (part): Promise<unknown> => {
|
|
63
|
+
if (!isCanonicalToolResultFile(part)) return part;
|
|
64
|
+
const file = await materializeInlineFile(
|
|
65
|
+
ctx,
|
|
66
|
+
component,
|
|
67
|
+
part.data.type === "url"
|
|
68
|
+
? part.data.url
|
|
69
|
+
: part.data.type === "data"
|
|
70
|
+
? part.data.data
|
|
71
|
+
: new TextEncoder().encode(part.data.text),
|
|
72
|
+
part.mediaType,
|
|
73
|
+
part.filename,
|
|
74
|
+
);
|
|
75
|
+
if (!file) return part;
|
|
76
|
+
fileRefs.push({ url: file.url, fileId: file.fileId });
|
|
77
|
+
return {
|
|
78
|
+
...part,
|
|
79
|
+
data: { type: "url", url: file.url },
|
|
80
|
+
};
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
return { output: { ...output, value }, fileRefs };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function materializeInlineFile(
|
|
87
|
+
ctx: ActionCtx | MutationCtx,
|
|
88
|
+
component: AgentComponent,
|
|
89
|
+
data: unknown,
|
|
90
|
+
mediaType: string,
|
|
91
|
+
filename?: string,
|
|
92
|
+
) {
|
|
93
|
+
const bytes = decodeInlineFileData(data);
|
|
94
|
+
if (!bytes) return undefined;
|
|
95
|
+
if (bytes.byteLength <= MAX_FILE_SIZE) return undefined;
|
|
96
|
+
const blobBytes = bytes.buffer.slice(
|
|
97
|
+
bytes.byteOffset,
|
|
98
|
+
bytes.byteOffset + bytes.byteLength,
|
|
99
|
+
) as ArrayBuffer;
|
|
100
|
+
const { file } = await storeFile(
|
|
101
|
+
ctx,
|
|
102
|
+
component,
|
|
103
|
+
new Blob([blobBytes], {
|
|
104
|
+
type: mediaType || "application/octet-stream",
|
|
105
|
+
}),
|
|
106
|
+
filename ? { filename } : {},
|
|
107
|
+
);
|
|
108
|
+
return file;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isCanonicalToolResultContent(
|
|
112
|
+
value: unknown,
|
|
113
|
+
): value is { type: "content"; value: unknown[] } {
|
|
114
|
+
return (
|
|
115
|
+
isRecord(value) && value.type === "content" && Array.isArray(value.value)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isCanonicalToolResultFile(value: unknown): value is {
|
|
120
|
+
type: "file";
|
|
121
|
+
data:
|
|
122
|
+
| { type: "url"; url: string }
|
|
123
|
+
| { type: "data"; data: unknown }
|
|
124
|
+
| { type: "text"; text: string };
|
|
125
|
+
mediaType: string;
|
|
126
|
+
filename?: string;
|
|
127
|
+
} {
|
|
128
|
+
return (
|
|
129
|
+
isRecord(value) &&
|
|
130
|
+
value.type === "file" &&
|
|
131
|
+
typeof value.mediaType === "string" &&
|
|
132
|
+
isRecord(value.data) &&
|
|
133
|
+
((value.data.type === "url" && typeof value.data.url === "string") ||
|
|
134
|
+
value.data.type === "data" ||
|
|
135
|
+
(value.data.type === "text" && typeof value.data.text === "string"))
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
140
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function decodeInlineFileData(value: unknown): Uint8Array | undefined {
|
|
144
|
+
if (typeof value === "string") {
|
|
145
|
+
if (!value.startsWith("data:")) {
|
|
146
|
+
// Remote and already-stored files are references, not inline payloads.
|
|
147
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(value)) return undefined;
|
|
148
|
+
return decodeBase64(value);
|
|
149
|
+
}
|
|
150
|
+
const separator = value.indexOf(",");
|
|
151
|
+
if (separator === -1) return undefined;
|
|
152
|
+
const metadata = value.slice(5, separator);
|
|
153
|
+
const payload = value.slice(separator + 1);
|
|
154
|
+
try {
|
|
155
|
+
return metadata.includes(";base64")
|
|
156
|
+
? decodeBase64(payload)
|
|
157
|
+
: new TextEncoder().encode(decodeURIComponent(payload));
|
|
158
|
+
} catch {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (value instanceof Uint8Array) return value;
|
|
163
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function decodeBase64(value: string): Uint8Array | undefined {
|
|
168
|
+
try {
|
|
169
|
+
const binary = atob(value);
|
|
170
|
+
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
171
|
+
} catch {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
}
|
package/src/vercel/index.ts
CHANGED
|
@@ -818,6 +818,7 @@ export class Agent<
|
|
|
818
818
|
metadata: args.metadata ? [args.metadata] : undefined,
|
|
819
819
|
skipEmbeddings: args.skipEmbeddings,
|
|
820
820
|
promptMessageId: args.promptMessageId,
|
|
821
|
+
order: args.order,
|
|
821
822
|
pendingMessageId: args.pendingMessageId,
|
|
822
823
|
});
|
|
823
824
|
const message = messages.at(-1)!;
|
|
@@ -1765,6 +1766,7 @@ export class Agent<
|
|
|
1765
1766
|
threadId: v.string(),
|
|
1766
1767
|
userId: v.optional(v.string()),
|
|
1767
1768
|
promptMessageId: v.optional(v.string()),
|
|
1769
|
+
order: v.optional(v.union(v.number(), v.literal("next"))),
|
|
1768
1770
|
messages: v.array(vMessageWithMetadata),
|
|
1769
1771
|
failPendingSteps: v.optional(v.boolean()),
|
|
1770
1772
|
embeddings: v.optional(vMessageEmbeddings),
|
|
@@ -465,6 +465,123 @@ describe("mapping", () => {
|
|
|
465
465
|
);
|
|
466
466
|
});
|
|
467
467
|
|
|
468
|
+
test("materializes oversized canonical tool-result files", async () => {
|
|
469
|
+
const bytes = new Uint8Array(1024 * 65).fill(1);
|
|
470
|
+
const ctx = {
|
|
471
|
+
runAction: async () => undefined,
|
|
472
|
+
runMutation: async () => ({
|
|
473
|
+
fileId: "file-123",
|
|
474
|
+
storageId: "storage-123",
|
|
475
|
+
}),
|
|
476
|
+
storage: {
|
|
477
|
+
getUrl: async () => "https://example.com/file",
|
|
478
|
+
},
|
|
479
|
+
} as unknown as ActionCtx;
|
|
480
|
+
const result = {
|
|
481
|
+
type: "tool-result" as const,
|
|
482
|
+
toolCallId: "call-1",
|
|
483
|
+
toolName: "render",
|
|
484
|
+
output: {
|
|
485
|
+
type: "content" as const,
|
|
486
|
+
value: [
|
|
487
|
+
{
|
|
488
|
+
type: "file" as const,
|
|
489
|
+
data: { type: "data" as const, data: bytes },
|
|
490
|
+
mediaType: "application/octet-stream",
|
|
491
|
+
filename: "result.bin",
|
|
492
|
+
},
|
|
493
|
+
],
|
|
494
|
+
},
|
|
495
|
+
} satisfies ToolResultPart;
|
|
496
|
+
|
|
497
|
+
const { content, fileIds } = await serializeContent(
|
|
498
|
+
ctx,
|
|
499
|
+
api as unknown as AgentComponent,
|
|
500
|
+
[result],
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
expect(fileIds).toEqual(["file-123"]);
|
|
504
|
+
expect(content).toMatchObject([
|
|
505
|
+
{
|
|
506
|
+
type: "tool-result",
|
|
507
|
+
output: {
|
|
508
|
+
type: "content",
|
|
509
|
+
value: [
|
|
510
|
+
{
|
|
511
|
+
type: "file",
|
|
512
|
+
data: { type: "url", url: "https://example.com/file" },
|
|
513
|
+
},
|
|
514
|
+
],
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
]);
|
|
518
|
+
|
|
519
|
+
const opaque = { attachment: "data:application/octet-stream,opaque" };
|
|
520
|
+
await expect(
|
|
521
|
+
serializeContent(ctx, api as unknown as AgentComponent, [
|
|
522
|
+
{
|
|
523
|
+
type: "tool-result",
|
|
524
|
+
toolCallId: "call-2",
|
|
525
|
+
toolName: "render",
|
|
526
|
+
result: opaque,
|
|
527
|
+
} satisfies Infer<typeof vToolResultPart>,
|
|
528
|
+
]),
|
|
529
|
+
).resolves.toMatchObject({
|
|
530
|
+
content: [{ output: { type: "json", value: opaque } }],
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
test("materializes oversized canonical tool-result text files", async () => {
|
|
535
|
+
const ctx = {
|
|
536
|
+
runAction: async () => undefined,
|
|
537
|
+
runMutation: async () => ({
|
|
538
|
+
fileId: "file-123",
|
|
539
|
+
storageId: "storage-123",
|
|
540
|
+
}),
|
|
541
|
+
storage: {
|
|
542
|
+
getUrl: async () => "https://example.com/file",
|
|
543
|
+
},
|
|
544
|
+
} as unknown as ActionCtx;
|
|
545
|
+
const result = {
|
|
546
|
+
type: "tool-result" as const,
|
|
547
|
+
toolCallId: "call-1",
|
|
548
|
+
toolName: "render",
|
|
549
|
+
output: {
|
|
550
|
+
type: "content" as const,
|
|
551
|
+
value: [
|
|
552
|
+
{
|
|
553
|
+
type: "file" as const,
|
|
554
|
+
data: { type: "text" as const, text: "x".repeat(1024 * 65) },
|
|
555
|
+
mediaType: "text/plain",
|
|
556
|
+
filename: "result.txt",
|
|
557
|
+
},
|
|
558
|
+
],
|
|
559
|
+
},
|
|
560
|
+
} satisfies ToolResultPart;
|
|
561
|
+
|
|
562
|
+
const { content, fileIds } = await serializeContent(
|
|
563
|
+
ctx,
|
|
564
|
+
api as unknown as AgentComponent,
|
|
565
|
+
[result],
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
expect(fileIds).toEqual(["file-123"]);
|
|
569
|
+
expect(content).toMatchObject([
|
|
570
|
+
{
|
|
571
|
+
type: "tool-result",
|
|
572
|
+
output: {
|
|
573
|
+
type: "content",
|
|
574
|
+
value: [
|
|
575
|
+
{
|
|
576
|
+
type: "file",
|
|
577
|
+
data: { type: "url", url: "https://example.com/file" },
|
|
578
|
+
},
|
|
579
|
+
],
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
]);
|
|
583
|
+
});
|
|
584
|
+
|
|
468
585
|
test("sanity: fileIds are not returned for small files", async () => {
|
|
469
586
|
const arr = new Uint8Array([1, 2, 3, 4, 5]);
|
|
470
587
|
const ab = arr.buffer.slice(
|
package/src/vercel/mapping.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
import type { ActionCtx, AgentComponent } from "./client/types.js";
|
|
45
45
|
import type { MutationCtx } from "./client/types.js";
|
|
46
46
|
import { MAX_FILE_SIZE, storeFile } from "./client/files.js";
|
|
47
|
+
import { materializeCanonicalToolResultContentFiles } from "./fileMaterialization.js";
|
|
47
48
|
import type { Infer } from "convex/values";
|
|
48
49
|
import {
|
|
49
50
|
convertUint8ArrayToBase64,
|
|
@@ -466,7 +467,20 @@ export async function serializeContent(
|
|
|
466
467
|
} satisfies Infer<typeof vToolCallPart>;
|
|
467
468
|
}
|
|
468
469
|
case "tool-result": {
|
|
469
|
-
|
|
470
|
+
const output =
|
|
471
|
+
"output" in part && part.output !== undefined
|
|
472
|
+
? part.output
|
|
473
|
+
: normalizeToolOutput("result" in part ? part.result : undefined);
|
|
474
|
+
const materialized = await materializeCanonicalToolResultContentFiles(
|
|
475
|
+
ctx,
|
|
476
|
+
component,
|
|
477
|
+
output,
|
|
478
|
+
);
|
|
479
|
+
fileIds.push(...materialized.fileRefs.map((ref) => ref.fileId));
|
|
480
|
+
return serializeToolResult(
|
|
481
|
+
{ ...part, output: materialized.output } as ToolResultPart,
|
|
482
|
+
metadata,
|
|
483
|
+
);
|
|
470
484
|
}
|
|
471
485
|
case "reasoning": {
|
|
472
486
|
return {
|
|
@@ -26,13 +26,19 @@ describe("toUIMessages", () => {
|
|
|
26
26
|
content: "Hello!",
|
|
27
27
|
},
|
|
28
28
|
text: "Hello!",
|
|
29
|
+
providerMetadata: { testProvider: { traceId: "trace-123" } },
|
|
29
30
|
}),
|
|
30
31
|
];
|
|
31
32
|
const uiMessages = toUIMessages(messages);
|
|
32
33
|
expect(uiMessages).toHaveLength(1);
|
|
33
34
|
expect(uiMessages[0].role).toBe("user");
|
|
34
35
|
expect(uiMessages[0].text).toBe("Hello!");
|
|
35
|
-
expect(uiMessages[0].parts[0]).toEqual({
|
|
36
|
+
expect(uiMessages[0].parts[0]).toEqual({
|
|
37
|
+
type: "text",
|
|
38
|
+
text: "Hello!",
|
|
39
|
+
state: "done",
|
|
40
|
+
providerMetadata: { testProvider: { traceId: "trace-123" } },
|
|
41
|
+
});
|
|
36
42
|
});
|
|
37
43
|
|
|
38
44
|
it("handles assistant message", () => {
|