@convex-dev/agent 0.1.18 → 0.2.0
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/dist/client/createTool.d.ts +30 -11
- package/dist/client/createTool.d.ts.map +1 -1
- package/dist/client/createTool.js +24 -10
- package/dist/client/createTool.js.map +1 -1
- package/dist/client/defaultComponent.d.ts +11 -0
- package/dist/client/defaultComponent.d.ts.map +1 -0
- package/dist/client/defaultComponent.js +7 -0
- package/dist/client/defaultComponent.js.map +1 -0
- package/dist/client/definePlaygroundAPI.d.ts +68 -23
- package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
- package/dist/client/definePlaygroundAPI.js +7 -20
- package/dist/client/definePlaygroundAPI.js.map +1 -1
- package/dist/client/files.d.ts +14 -4
- package/dist/client/files.d.ts.map +1 -1
- package/dist/client/files.js +66 -11
- package/dist/client/files.js.map +1 -1
- package/dist/client/index.d.ts +323 -780
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +445 -543
- package/dist/client/index.js.map +1 -1
- package/dist/client/messages.d.ts +256 -0
- package/dist/client/messages.d.ts.map +1 -0
- package/dist/client/messages.js +97 -0
- package/dist/client/messages.js.map +1 -0
- package/dist/client/search.d.ts +35 -12
- package/dist/client/search.d.ts.map +1 -1
- package/dist/client/search.js +21 -4
- package/dist/client/search.js.map +1 -1
- package/dist/client/streaming.d.ts +21 -21
- package/dist/client/streaming.d.ts.map +1 -1
- package/dist/client/streaming.js +35 -40
- package/dist/client/streaming.js.map +1 -1
- package/dist/client/threads.d.ts +46 -0
- package/dist/client/threads.d.ts.map +1 -0
- package/dist/client/threads.js +49 -0
- package/dist/client/threads.js.map +1 -0
- package/dist/client/types.d.ts +159 -75
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/_generated/api.d.ts +261 -209
- package/dist/component/messages.d.ts +314 -105
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/messages.js +141 -83
- package/dist/component/messages.js.map +1 -1
- package/dist/component/schema.d.ts +631 -870
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +11 -16
- package/dist/component/schema.js.map +1 -1
- package/dist/component/streams.d.ts +29 -149
- package/dist/component/streams.d.ts.map +1 -1
- package/dist/component/streams.js +104 -60
- package/dist/component/streams.js.map +1 -1
- package/dist/component/threads.d.ts +4 -4
- package/dist/component/users.d.ts +3 -3
- package/dist/mapping.d.ts +15 -16
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +208 -62
- package/dist/mapping.js.map +1 -1
- package/dist/parts.d.ts +3 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +34 -0
- package/dist/parts.js.map +1 -0
- package/dist/react/SmoothText.d.ts +5 -0
- package/dist/react/SmoothText.d.ts.map +1 -0
- package/dist/react/SmoothText.js +6 -0
- package/dist/react/SmoothText.js.map +1 -0
- package/dist/react/deltas.d.ts +3 -5
- package/dist/react/deltas.d.ts.map +1 -1
- package/dist/react/deltas.js +89 -53
- package/dist/react/deltas.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +31 -11
- package/dist/react/index.js.map +1 -1
- package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
- package/dist/react/optimisticallySendMessage.js +2 -8
- package/dist/react/optimisticallySendMessage.js.map +1 -1
- package/dist/react/toUIMessages.d.ts +5 -4
- package/dist/react/toUIMessages.d.ts.map +1 -1
- package/dist/react/toUIMessages.js +103 -40
- package/dist/react/toUIMessages.js.map +1 -1
- package/dist/react/useSmoothText.d.ts +13 -12
- package/dist/react/useSmoothText.d.ts.map +1 -1
- package/dist/react/useSmoothText.js +1 -1
- package/dist/react/useSmoothText.js.map +1 -1
- package/dist/validators.d.ts +751 -1464
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +39 -80
- package/dist/validators.js.map +1 -1
- package/package.json +34 -30
- package/src/client/createTool.ts +66 -40
- package/src/client/defaultComponent.ts +17 -0
- package/src/client/definePlaygroundAPI.ts +17 -31
- package/src/client/files.ts +94 -17
- package/src/client/index.test.ts +28 -38
- package/src/client/index.ts +785 -977
- package/src/client/messages.ts +205 -0
- package/src/client/search.ts +37 -10
- package/src/client/streaming.ts +53 -56
- package/src/client/threads.ts +78 -0
- package/src/client/types.ts +239 -131
- package/src/component/_generated/api.d.ts +261 -209
- package/src/component/messages.test.ts +182 -40
- package/src/component/messages.ts +165 -108
- package/src/component/schema.ts +11 -17
- package/src/component/streams.ts +180 -69
- package/src/mapping.ts +253 -96
- package/src/parts.ts +39 -0
- package/src/react/SmoothText.tsx +9 -0
- package/src/react/deltas.test.ts +38 -42
- package/src/react/deltas.ts +116 -65
- package/src/react/index.ts +35 -13
- package/src/react/optimisticallySendMessage.ts +2 -8
- package/src/react/toUIMessages.test.ts +154 -36
- package/src/react/toUIMessages.ts +136 -57
- package/src/react/useSmoothText.ts +14 -15
- package/src/validators.test.ts +1 -117
- package/src/validators.ts +57 -107
- package/dist/client/listMessages.d.ts +0 -22
- package/dist/client/listMessages.d.ts.map +0 -1
- package/dist/client/listMessages.js +0 -25
- package/dist/client/listMessages.js.map +0 -1
- package/src/client/listMessages.ts +0 -38
|
@@ -31,7 +31,7 @@ describe("toUIMessages", () => {
|
|
|
31
31
|
const uiMessages = toUIMessages(messages);
|
|
32
32
|
expect(uiMessages).toHaveLength(1);
|
|
33
33
|
expect(uiMessages[0].role).toBe("user");
|
|
34
|
-
expect(uiMessages[0].
|
|
34
|
+
expect(uiMessages[0].text).toBe("Hello!");
|
|
35
35
|
expect(uiMessages[0].parts[0]).toEqual({ type: "text", text: "Hello!" });
|
|
36
36
|
});
|
|
37
37
|
|
|
@@ -48,10 +48,11 @@ describe("toUIMessages", () => {
|
|
|
48
48
|
const uiMessages = toUIMessages(messages);
|
|
49
49
|
expect(uiMessages).toHaveLength(1);
|
|
50
50
|
expect(uiMessages[0].role).toBe("assistant");
|
|
51
|
-
expect(uiMessages[0].
|
|
51
|
+
expect(uiMessages[0].text).toBe("Hi, how can I help?");
|
|
52
52
|
expect(uiMessages[0].parts[0]).toEqual({
|
|
53
53
|
type: "text",
|
|
54
54
|
text: "Hi, how can I help?",
|
|
55
|
+
state: "done",
|
|
55
56
|
});
|
|
56
57
|
});
|
|
57
58
|
|
|
@@ -83,13 +84,13 @@ describe("toUIMessages", () => {
|
|
|
83
84
|
{
|
|
84
85
|
type: "file",
|
|
85
86
|
mimeType: "text/plain",
|
|
86
|
-
data: "
|
|
87
|
+
data: "https://example.com/file.txt",
|
|
87
88
|
},
|
|
88
89
|
{
|
|
89
90
|
type: "tool-call",
|
|
90
91
|
toolName: "myTool",
|
|
91
92
|
toolCallId: "call1",
|
|
92
|
-
args: "",
|
|
93
|
+
args: "an arg",
|
|
93
94
|
},
|
|
94
95
|
],
|
|
95
96
|
},
|
|
@@ -109,7 +110,6 @@ describe("toUIMessages", () => {
|
|
|
109
110
|
},
|
|
110
111
|
],
|
|
111
112
|
},
|
|
112
|
-
text: "42",
|
|
113
113
|
tool: true,
|
|
114
114
|
}),
|
|
115
115
|
];
|
|
@@ -121,18 +121,15 @@ describe("toUIMessages", () => {
|
|
|
121
121
|
);
|
|
122
122
|
expect(uiMessages[1].role).toBe("assistant");
|
|
123
123
|
expect(
|
|
124
|
-
uiMessages[1].parts.filter((p) => p.type === "tool-
|
|
124
|
+
uiMessages[1].parts.filter((p) => p.type === "tool-myTool"),
|
|
125
125
|
).toHaveLength(1);
|
|
126
126
|
expect(
|
|
127
|
-
uiMessages[1].parts.filter((p) => p.type === "tool-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
toolName: "myTool",
|
|
127
|
+
uiMessages[1].parts.filter((p) => p.type === "tool-myTool")[0],
|
|
128
|
+
).toMatchObject({
|
|
129
|
+
type: "tool-myTool",
|
|
131
130
|
toolCallId: "call1",
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
result: "42",
|
|
135
|
-
step: 0,
|
|
131
|
+
state: "output-available",
|
|
132
|
+
output: "42",
|
|
136
133
|
});
|
|
137
134
|
});
|
|
138
135
|
|
|
@@ -167,21 +164,39 @@ describe("toUIMessages", () => {
|
|
|
167
164
|
const uiMessages = toUIMessages(messages);
|
|
168
165
|
expect(uiMessages).toHaveLength(1);
|
|
169
166
|
expect(uiMessages[0].role).toBe("assistant");
|
|
170
|
-
expect(uiMessages[0].
|
|
171
|
-
expect(
|
|
172
|
-
|
|
173
|
-
|
|
167
|
+
expect(uiMessages[0].text).toBe("Here's one idea. Here's another idea.");
|
|
168
|
+
expect(uiMessages[0].parts.filter((p) => p.type === "reasoning")).toEqual([
|
|
169
|
+
{
|
|
170
|
+
providerMetadata: undefined,
|
|
171
|
+
state: undefined,
|
|
172
|
+
text: "I'm thinking...",
|
|
173
|
+
type: "reasoning",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
providerMetadata: undefined,
|
|
177
|
+
state: undefined,
|
|
178
|
+
text: "I'm thinking...",
|
|
179
|
+
type: "reasoning",
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
174
182
|
expect(uiMessages[0].parts[0].type).toBe("reasoning");
|
|
175
183
|
assert(uiMessages[0].parts[0].type === "reasoning");
|
|
176
|
-
expect(uiMessages[0].parts[0].
|
|
177
|
-
|
|
178
|
-
);
|
|
184
|
+
expect(uiMessages[0].parts[0].text).toBe("I'm thinking...");
|
|
185
|
+
expect(uiMessages[0].parts[1].type).toBe("text");
|
|
186
|
+
assert(uiMessages[0].parts[1].type === "text");
|
|
187
|
+
expect(uiMessages[0].parts[1].text).toBe("Here's one idea.");
|
|
188
|
+
expect(uiMessages[0].parts[2].type).toBe("reasoning");
|
|
189
|
+
assert(uiMessages[0].parts[2].type === "reasoning");
|
|
190
|
+
expect(uiMessages[0].parts[2].text).toBe("I'm thinking...");
|
|
179
191
|
|
|
180
192
|
expect(uiMessages[0].parts.filter((p) => p.type === "text")).toHaveLength(
|
|
181
|
-
|
|
193
|
+
2,
|
|
182
194
|
);
|
|
183
195
|
expect(uiMessages[0].parts.filter((p) => p.type === "text")[0].text).toBe(
|
|
184
|
-
"Here's one idea.
|
|
196
|
+
"Here's one idea.",
|
|
197
|
+
);
|
|
198
|
+
expect(uiMessages[0].parts.filter((p) => p.type === "text")[1].text).toBe(
|
|
199
|
+
"Here's another idea.",
|
|
185
200
|
);
|
|
186
201
|
});
|
|
187
202
|
|
|
@@ -198,10 +213,58 @@ describe("toUIMessages", () => {
|
|
|
198
213
|
const uiMessages = toUIMessages(messages);
|
|
199
214
|
expect(uiMessages).toHaveLength(1);
|
|
200
215
|
expect(uiMessages[0].role).toBe("system");
|
|
201
|
-
expect(uiMessages[0].
|
|
216
|
+
expect(uiMessages[0].text).toBe("System message here");
|
|
202
217
|
expect(uiMessages[0].parts[0]).toEqual({
|
|
203
218
|
type: "text",
|
|
204
219
|
text: "System message here",
|
|
220
|
+
state: "done",
|
|
221
|
+
providerMetadata: undefined,
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("handles wrapped JSON tool output", () => {
|
|
226
|
+
const messages = [
|
|
227
|
+
baseMessageDoc({
|
|
228
|
+
message: {
|
|
229
|
+
role: "assistant",
|
|
230
|
+
content: [
|
|
231
|
+
{
|
|
232
|
+
type: "tool-call",
|
|
233
|
+
toolName: "myTool",
|
|
234
|
+
toolCallId: "call1",
|
|
235
|
+
args: { query: "test" },
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
},
|
|
239
|
+
tool: true,
|
|
240
|
+
}),
|
|
241
|
+
baseMessageDoc({
|
|
242
|
+
message: {
|
|
243
|
+
role: "tool",
|
|
244
|
+
content: [
|
|
245
|
+
{
|
|
246
|
+
type: "tool-result",
|
|
247
|
+
toolName: "myTool",
|
|
248
|
+
toolCallId: "call1",
|
|
249
|
+
result: {
|
|
250
|
+
type: "json",
|
|
251
|
+
value: { data: "wrapped result", success: true },
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
tool: true,
|
|
257
|
+
}),
|
|
258
|
+
];
|
|
259
|
+
const uiMessages = toUIMessages(messages);
|
|
260
|
+
expect(uiMessages).toHaveLength(1);
|
|
261
|
+
const toolPart = uiMessages[0].parts.find((p) => p.type === "tool-myTool");
|
|
262
|
+
expect(toolPart).toMatchObject({
|
|
263
|
+
type: "tool-myTool",
|
|
264
|
+
toolCallId: "call1",
|
|
265
|
+
state: "output-available",
|
|
266
|
+
input: { query: "test" },
|
|
267
|
+
output: { data: "wrapped result", success: true }, // Should be unwrapped
|
|
205
268
|
});
|
|
206
269
|
});
|
|
207
270
|
|
|
@@ -215,7 +278,7 @@ describe("toUIMessages", () => {
|
|
|
215
278
|
type: "tool-call",
|
|
216
279
|
toolName: "myTool",
|
|
217
280
|
toolCallId: "call1",
|
|
218
|
-
args: "",
|
|
281
|
+
args: "hi",
|
|
219
282
|
},
|
|
220
283
|
],
|
|
221
284
|
},
|
|
@@ -226,17 +289,12 @@ describe("toUIMessages", () => {
|
|
|
226
289
|
expect(uiMessages).toHaveLength(1);
|
|
227
290
|
expect(uiMessages[0].role).toBe("assistant");
|
|
228
291
|
expect(
|
|
229
|
-
uiMessages[0].parts.filter((p) => p.type === "tool-
|
|
230
|
-
).
|
|
231
|
-
|
|
232
|
-
uiMessages[0].parts.filter((p) => p.type === "tool-invocation")[0]
|
|
233
|
-
.toolInvocation,
|
|
234
|
-
).toEqual({
|
|
235
|
-
toolName: "myTool",
|
|
292
|
+
uiMessages[0].parts.filter((p) => p.type === "tool-myTool")[0],
|
|
293
|
+
).toMatchObject({
|
|
294
|
+
type: "tool-myTool",
|
|
236
295
|
toolCallId: "call1",
|
|
237
|
-
|
|
238
|
-
state: "
|
|
239
|
-
step: 0,
|
|
296
|
+
input: "hi",
|
|
297
|
+
state: "input-available",
|
|
240
298
|
});
|
|
241
299
|
});
|
|
242
300
|
|
|
@@ -276,7 +334,7 @@ describe("toUIMessages", () => {
|
|
|
276
334
|
expect(uiMessages).toHaveLength(1);
|
|
277
335
|
expect(uiMessages[0].role).toBe("assistant");
|
|
278
336
|
// Should have a tool-invocation part
|
|
279
|
-
expect(uiMessages[0].parts.some((p) => p.type === "tool-
|
|
337
|
+
expect(uiMessages[0].parts.some((p) => p.type === "tool-myTool")).toBe(
|
|
280
338
|
true,
|
|
281
339
|
);
|
|
282
340
|
});
|
|
@@ -299,4 +357,64 @@ describe("toUIMessages", () => {
|
|
|
299
357
|
});
|
|
300
358
|
|
|
301
359
|
// Add more tests for array content, tool calls, etc. as needed
|
|
360
|
+
|
|
361
|
+
it("should update tool call state from input-available to output-available", () => {
|
|
362
|
+
const messages = [
|
|
363
|
+
baseMessageDoc({
|
|
364
|
+
message: {
|
|
365
|
+
role: "assistant",
|
|
366
|
+
content: [
|
|
367
|
+
{
|
|
368
|
+
type: "tool-call",
|
|
369
|
+
toolName: "calculator",
|
|
370
|
+
toolCallId: "call1",
|
|
371
|
+
args: { operation: "add", a: 1, b: 2 },
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
},
|
|
375
|
+
tool: true,
|
|
376
|
+
}),
|
|
377
|
+
baseMessageDoc({
|
|
378
|
+
message: {
|
|
379
|
+
role: "tool",
|
|
380
|
+
content: [
|
|
381
|
+
{
|
|
382
|
+
type: "tool-result",
|
|
383
|
+
toolCallId: "call1",
|
|
384
|
+
toolName: "calculator",
|
|
385
|
+
result: { sum: 3 },
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
},
|
|
389
|
+
tool: true,
|
|
390
|
+
}),
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
const uiMessages = toUIMessages(messages);
|
|
394
|
+
|
|
395
|
+
// Should have one assistant message
|
|
396
|
+
expect(uiMessages).toHaveLength(1);
|
|
397
|
+
expect(uiMessages[0].role).toBe("assistant");
|
|
398
|
+
|
|
399
|
+
// Should have a single tool-calculator part (not separate tool-call and tool-result parts)
|
|
400
|
+
const toolParts = uiMessages[0].parts.filter(
|
|
401
|
+
(p) => p.type === "tool-calculator",
|
|
402
|
+
);
|
|
403
|
+
expect(toolParts).toHaveLength(1);
|
|
404
|
+
|
|
405
|
+
const toolPart = toolParts[0];
|
|
406
|
+
expect(toolPart).toMatchObject({
|
|
407
|
+
type: "tool-calculator",
|
|
408
|
+
toolCallId: "call1",
|
|
409
|
+
state: "output-available",
|
|
410
|
+
input: { operation: "add", a: 1, b: 2 },
|
|
411
|
+
output: { sum: 3 },
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Should NOT have a tool-call part (which is what currently happens)
|
|
415
|
+
const toolCallParts = uiMessages[0].parts.filter(
|
|
416
|
+
(p) => p.type === "tool-call",
|
|
417
|
+
);
|
|
418
|
+
expect(toolCallParts).toHaveLength(0);
|
|
419
|
+
});
|
|
302
420
|
});
|
|
@@ -1,22 +1,41 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
UIMessage as AIUIMessage,
|
|
3
|
+
DeepPartial,
|
|
4
|
+
ReasoningUIPart,
|
|
5
|
+
SourceDocumentUIPart,
|
|
6
|
+
SourceUrlUIPart,
|
|
7
|
+
StepStartUIPart,
|
|
8
|
+
TextUIPart,
|
|
9
|
+
ToolUIPart,
|
|
10
|
+
UIDataTypes,
|
|
11
|
+
UITools,
|
|
12
|
+
} from "ai";
|
|
3
13
|
import type { MessageDoc } from "../client/index.js";
|
|
4
14
|
import { deserializeMessage, toUIFilePart } from "../mapping.js";
|
|
5
15
|
import type { MessageStatus } from "../validators.js";
|
|
6
16
|
|
|
7
|
-
export type UIMessage
|
|
17
|
+
export type UIMessage<
|
|
18
|
+
METADATA = unknown,
|
|
19
|
+
DATA_PARTS extends UIDataTypes = UIDataTypes,
|
|
20
|
+
TOOLS extends UITools = UITools,
|
|
21
|
+
> = AIUIMessage<METADATA, DATA_PARTS, TOOLS> & {
|
|
8
22
|
key: string;
|
|
9
23
|
order: number;
|
|
10
24
|
stepOrder: number;
|
|
11
25
|
status: "streaming" | MessageStatus;
|
|
12
26
|
agentName?: string;
|
|
27
|
+
text: string;
|
|
13
28
|
};
|
|
14
29
|
|
|
15
|
-
export function toUIMessages
|
|
30
|
+
export function toUIMessages<
|
|
31
|
+
METADATA = unknown,
|
|
32
|
+
DATA_PARTS extends UIDataTypes = UIDataTypes,
|
|
33
|
+
TOOLS extends UITools = UITools,
|
|
34
|
+
>(
|
|
16
35
|
messages: (MessageDoc & { streaming?: boolean })[],
|
|
17
|
-
): UIMessage[] {
|
|
18
|
-
const uiMessages: UIMessage[] = [];
|
|
19
|
-
let assistantMessage: UIMessage | undefined;
|
|
36
|
+
): UIMessage<METADATA, DATA_PARTS, TOOLS>[] {
|
|
37
|
+
const uiMessages: UIMessage<METADATA, DATA_PARTS, TOOLS>[] = [];
|
|
38
|
+
let assistantMessage: UIMessage<METADATA, DATA_PARTS, TOOLS> | undefined;
|
|
20
39
|
for (const message of messages) {
|
|
21
40
|
const coreMessage = message.message && deserializeMessage(message.message);
|
|
22
41
|
const text = message.text ?? "";
|
|
@@ -31,17 +50,23 @@ export function toUIMessages(
|
|
|
31
50
|
stepOrder: message.stepOrder,
|
|
32
51
|
status: message.streaming ? ("streaming" as const) : message.status,
|
|
33
52
|
key: `${message.threadId}-${message.order}-${message.stepOrder}`,
|
|
53
|
+
text,
|
|
54
|
+
};
|
|
55
|
+
const partCommon = {
|
|
56
|
+
state: message.streaming ? ("streaming" as const) : ("done" as const),
|
|
57
|
+
...(message.providerMetadata
|
|
58
|
+
? { providerMetadata: message.providerMetadata }
|
|
59
|
+
: {}),
|
|
34
60
|
};
|
|
35
61
|
if (coreMessage.role === "system") {
|
|
36
62
|
uiMessages.push({
|
|
37
63
|
...common,
|
|
38
64
|
role: "system",
|
|
39
|
-
content: text,
|
|
40
65
|
agentName: message.agentName,
|
|
41
|
-
parts: [{ type: "text", text }],
|
|
66
|
+
parts: [{ type: "text", text, ...partCommon } satisfies TextUIPart],
|
|
42
67
|
});
|
|
43
68
|
} else if (coreMessage.role === "user") {
|
|
44
|
-
const parts: UIMessage["parts"] = [];
|
|
69
|
+
const parts: UIMessage<METADATA, DATA_PARTS, TOOLS>["parts"] = [];
|
|
45
70
|
if (text) {
|
|
46
71
|
parts.push({ type: "text", text });
|
|
47
72
|
}
|
|
@@ -56,7 +81,6 @@ export function toUIMessages(
|
|
|
56
81
|
uiMessages.push({
|
|
57
82
|
...common,
|
|
58
83
|
role: "user",
|
|
59
|
-
content: message.text ?? "",
|
|
60
84
|
parts,
|
|
61
85
|
});
|
|
62
86
|
} else {
|
|
@@ -72,7 +96,6 @@ export function toUIMessages(
|
|
|
72
96
|
...common,
|
|
73
97
|
role: "assistant",
|
|
74
98
|
agentName: message.agentName,
|
|
75
|
-
content: "",
|
|
76
99
|
parts: [],
|
|
77
100
|
};
|
|
78
101
|
uiMessages.push(assistantMessage);
|
|
@@ -83,78 +106,134 @@ export function toUIMessages(
|
|
|
83
106
|
}
|
|
84
107
|
// update it to the last message's id
|
|
85
108
|
assistantMessage.id = message._id;
|
|
86
|
-
if (
|
|
109
|
+
if (
|
|
110
|
+
message.reasoning &&
|
|
111
|
+
!nonStringContent.some((c) => c.type === "reasoning")
|
|
112
|
+
) {
|
|
87
113
|
assistantMessage.parts.push({
|
|
88
114
|
type: "reasoning",
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
});
|
|
115
|
+
text: message.reasoning,
|
|
116
|
+
...partCommon,
|
|
117
|
+
} satisfies ReasoningUIPart);
|
|
92
118
|
}
|
|
93
|
-
if (message.text) {
|
|
119
|
+
if (message.text && !nonStringContent.length) {
|
|
94
120
|
assistantMessage.parts.push({
|
|
95
121
|
type: "text",
|
|
96
122
|
text: message.text,
|
|
97
|
-
|
|
98
|
-
|
|
123
|
+
...partCommon,
|
|
124
|
+
} satisfies TextUIPart);
|
|
99
125
|
}
|
|
100
126
|
for (const source of message.sources ?? []) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
127
|
+
if (source.sourceType === "url") {
|
|
128
|
+
assistantMessage.parts.push({
|
|
129
|
+
type: "source-url",
|
|
130
|
+
url: source.url!,
|
|
131
|
+
sourceId: source.id,
|
|
132
|
+
providerMetadata: message.providerMetadata,
|
|
133
|
+
title: source.title,
|
|
134
|
+
} satisfies SourceUrlUIPart);
|
|
135
|
+
} else {
|
|
136
|
+
assistantMessage.parts.push({
|
|
137
|
+
type: "source-document",
|
|
138
|
+
mediaType: source.mediaType,
|
|
139
|
+
sourceId: source.id,
|
|
140
|
+
title: source.title,
|
|
141
|
+
filename: source.filename,
|
|
142
|
+
providerMetadata: message.providerMetadata,
|
|
143
|
+
} satisfies SourceDocumentUIPart);
|
|
144
|
+
}
|
|
105
145
|
}
|
|
106
146
|
for (const contentPart of nonStringContent) {
|
|
107
147
|
switch (contentPart.type) {
|
|
148
|
+
case "text":
|
|
149
|
+
assistantMessage.parts.push({
|
|
150
|
+
...partCommon,
|
|
151
|
+
...contentPart,
|
|
152
|
+
} satisfies TextUIPart);
|
|
153
|
+
break;
|
|
154
|
+
case "reasoning":
|
|
155
|
+
assistantMessage.parts.push({
|
|
156
|
+
...partCommon,
|
|
157
|
+
...contentPart,
|
|
158
|
+
} satisfies ReasoningUIPart);
|
|
159
|
+
break;
|
|
108
160
|
case "file":
|
|
109
161
|
case "image":
|
|
110
162
|
assistantMessage.parts.push(toUIFilePart(contentPart));
|
|
111
163
|
break;
|
|
112
|
-
case "tool-call":
|
|
164
|
+
case "tool-call": {
|
|
113
165
|
assistantMessage.parts.push({
|
|
114
166
|
type: "step-start",
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
type:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
167
|
+
} satisfies StepStartUIPart);
|
|
168
|
+
const toolPart: ToolUIPart<TOOLS> = {
|
|
169
|
+
type: `tool-${contentPart.toolName as keyof TOOLS & string}`,
|
|
170
|
+
toolCallId: contentPart.toolCallId,
|
|
171
|
+
input: contentPart.input as DeepPartial<
|
|
172
|
+
TOOLS[keyof TOOLS & string]["input"]
|
|
173
|
+
>,
|
|
174
|
+
providerExecuted: contentPart.providerExecuted,
|
|
175
|
+
...(message.streaming
|
|
176
|
+
? { state: "input-streaming" }
|
|
177
|
+
: {
|
|
178
|
+
state: "input-available",
|
|
179
|
+
callProviderMetadata: message.providerMetadata,
|
|
180
|
+
}),
|
|
181
|
+
};
|
|
182
|
+
assistantMessage.parts.push(toolPart);
|
|
128
183
|
break;
|
|
184
|
+
}
|
|
129
185
|
case "tool-result": {
|
|
130
186
|
const call = assistantMessage.parts.find(
|
|
131
187
|
(part) =>
|
|
132
|
-
part.type ===
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
state: "result",
|
|
137
|
-
toolCallId: contentPart.toolCallId,
|
|
138
|
-
toolName: contentPart.toolName,
|
|
139
|
-
args: call?.toolInvocation.args,
|
|
140
|
-
result: contentPart.result,
|
|
141
|
-
step:
|
|
142
|
-
call?.toolInvocation.step ??
|
|
143
|
-
assistantMessage.parts.filter(
|
|
144
|
-
(part) => part.type === "tool-invocation",
|
|
145
|
-
).length,
|
|
146
|
-
};
|
|
188
|
+
part.type === `tool-${contentPart.toolName}` &&
|
|
189
|
+
"toolCallId" in part &&
|
|
190
|
+
part.toolCallId === contentPart.toolCallId,
|
|
191
|
+
) as ToolUIPart | undefined;
|
|
147
192
|
if (call) {
|
|
148
|
-
(
|
|
193
|
+
if (message.error) {
|
|
194
|
+
call.state = "output-error";
|
|
195
|
+
call.errorText = message.error;
|
|
196
|
+
call.output = contentPart.output;
|
|
197
|
+
} else {
|
|
198
|
+
call.state = "output-available";
|
|
199
|
+
call.output =
|
|
200
|
+
contentPart.output?.type === "json"
|
|
201
|
+
? contentPart.output.value
|
|
202
|
+
: contentPart.output;
|
|
203
|
+
// Technically we could pull this from the doc.message
|
|
204
|
+
// but the ModelMessage doesn't have it
|
|
205
|
+
// call.providerExecuted = contentPart.providerExecuted;
|
|
206
|
+
}
|
|
149
207
|
} else {
|
|
150
208
|
console.warn(
|
|
151
209
|
"Tool result without preceding tool call.. adding anyways",
|
|
152
210
|
contentPart,
|
|
153
211
|
);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
212
|
+
if (message.error) {
|
|
213
|
+
assistantMessage.parts.push({
|
|
214
|
+
type: `tool-${contentPart.toolName}`,
|
|
215
|
+
toolCallId: contentPart.toolCallId,
|
|
216
|
+
state: "output-error",
|
|
217
|
+
input: undefined,
|
|
218
|
+
errorText: message.error,
|
|
219
|
+
// Technically we could pull this from the doc.message
|
|
220
|
+
// but the ModelMessage doesn't have it
|
|
221
|
+
// providerExecuted: contentPart.providerExecuted,
|
|
222
|
+
callProviderMetadata: message.providerMetadata,
|
|
223
|
+
} satisfies ToolUIPart<TOOLS>);
|
|
224
|
+
} else {
|
|
225
|
+
assistantMessage.parts.push({
|
|
226
|
+
type: `tool-${contentPart.toolName}`,
|
|
227
|
+
toolCallId: contentPart.toolCallId,
|
|
228
|
+
state: "output-available",
|
|
229
|
+
input: undefined,
|
|
230
|
+
output:
|
|
231
|
+
contentPart.output?.type === "json"
|
|
232
|
+
? contentPart.output.value
|
|
233
|
+
: contentPart.output,
|
|
234
|
+
callProviderMetadata: message.providerMetadata,
|
|
235
|
+
} satisfies ToolUIPart<TOOLS>);
|
|
236
|
+
}
|
|
158
237
|
}
|
|
159
238
|
break;
|
|
160
239
|
}
|
|
@@ -3,6 +3,19 @@ import { useEffect, useRef, useState } from "react";
|
|
|
3
3
|
const FPS = 20;
|
|
4
4
|
const MS_PER_FRAME = 1000 / FPS;
|
|
5
5
|
const MAX_TIME_JUMP_MS = 250;
|
|
6
|
+
|
|
7
|
+
export type SmoothTextOptions = {
|
|
8
|
+
/**
|
|
9
|
+
* The number of characters to display per second.
|
|
10
|
+
*/
|
|
11
|
+
charsPerSec?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Whether to initially start streaming.
|
|
14
|
+
* If this later turns to false, it'll continue streaming.
|
|
15
|
+
* This will start streaming the first value it sees.
|
|
16
|
+
*/
|
|
17
|
+
startStreaming?: boolean;
|
|
18
|
+
};
|
|
6
19
|
/**
|
|
7
20
|
* A hook that smoothly displays text as it is streamed.
|
|
8
21
|
*
|
|
@@ -15,21 +28,7 @@ const MAX_TIME_JUMP_MS = 250;
|
|
|
15
28
|
*/
|
|
16
29
|
export function useSmoothText(
|
|
17
30
|
text: string,
|
|
18
|
-
{
|
|
19
|
-
charsPerSec = 256,
|
|
20
|
-
startStreaming = false,
|
|
21
|
-
}: {
|
|
22
|
-
/**
|
|
23
|
-
* The number of characters to display per second.
|
|
24
|
-
*/
|
|
25
|
-
charsPerSec?: number;
|
|
26
|
-
/**
|
|
27
|
-
* Whether to initially start streaming.
|
|
28
|
-
* If this later turns to false, it'll continue streaming.
|
|
29
|
-
* This will start streaming the first value it sees.
|
|
30
|
-
*/
|
|
31
|
-
startStreaming?: boolean;
|
|
32
|
-
} = {},
|
|
31
|
+
{ charsPerSec = 256, startStreaming = false }: SmoothTextOptions = {},
|
|
33
32
|
): [string, { cursor: number; isStreaming: boolean }] {
|
|
34
33
|
const [visibleText, setVisibleText] = useState(startStreaming ? "" : text);
|
|
35
34
|
const smoothState = useRef({
|