@convex-dev/agent 0.2.6 → 0.2.8-alpha.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.
Files changed (112) hide show
  1. package/dist/UIMessages.d.ts +43 -0
  2. package/dist/UIMessages.d.ts.map +1 -0
  3. package/dist/{react/toUIMessages.js → UIMessages.js} +97 -4
  4. package/dist/UIMessages.js.map +1 -0
  5. package/dist/client/createTool.js +1 -1
  6. package/dist/client/createTool.js.map +1 -1
  7. package/dist/client/index.d.ts +39 -19
  8. package/dist/client/index.d.ts.map +1 -1
  9. package/dist/client/index.js +22 -19
  10. package/dist/client/index.js.map +1 -1
  11. package/dist/client/mockModel.d.ts +2 -0
  12. package/dist/client/mockModel.d.ts.map +1 -1
  13. package/dist/client/mockModel.js +11 -7
  14. package/dist/client/mockModel.js.map +1 -1
  15. package/dist/client/search.d.ts.map +1 -1
  16. package/dist/client/search.js +2 -0
  17. package/dist/client/search.js.map +1 -1
  18. package/dist/client/start.d.ts +1 -0
  19. package/dist/client/start.d.ts.map +1 -1
  20. package/dist/client/start.js +0 -1
  21. package/dist/client/start.js.map +1 -1
  22. package/dist/client/streaming.d.ts +23 -4
  23. package/dist/client/streaming.d.ts.map +1 -1
  24. package/dist/client/streaming.js +32 -6
  25. package/dist/client/streaming.js.map +1 -1
  26. package/dist/client/types.d.ts +19 -13
  27. package/dist/client/types.d.ts.map +1 -1
  28. package/dist/component/_generated/api.d.ts +2 -0
  29. package/dist/component/messages.js +1 -1
  30. package/dist/component/messages.js.map +1 -1
  31. package/dist/component/schema.d.ts +10 -4
  32. package/dist/component/schema.d.ts.map +1 -1
  33. package/dist/component/schema.js +4 -0
  34. package/dist/component/schema.js.map +1 -1
  35. package/dist/component/streams.d.ts +2 -4
  36. package/dist/component/streams.d.ts.map +1 -1
  37. package/dist/component/streams.js +42 -43
  38. package/dist/component/streams.js.map +1 -1
  39. package/dist/deltas.d.ts +48 -0
  40. package/dist/deltas.d.ts.map +1 -0
  41. package/dist/deltas.js +468 -0
  42. package/dist/deltas.js.map +1 -0
  43. package/dist/react/index.d.ts +4 -77
  44. package/dist/react/index.d.ts.map +1 -1
  45. package/dist/react/index.js +4 -178
  46. package/dist/react/index.js.map +1 -1
  47. package/dist/react/optimisticallySendMessage.d.ts +36 -3
  48. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  49. package/dist/react/optimisticallySendMessage.js +37 -5
  50. package/dist/react/optimisticallySendMessage.js.map +1 -1
  51. package/dist/react/types.d.ts +4 -18
  52. package/dist/react/types.d.ts.map +1 -1
  53. package/dist/react/useStreamingUIMessages.d.ts +22 -0
  54. package/dist/react/useStreamingUIMessages.d.ts.map +1 -0
  55. package/dist/react/useStreamingUIMessages.js +204 -0
  56. package/dist/react/useStreamingUIMessages.js.map +1 -0
  57. package/dist/react/useThreadMessages.d.ts +103 -0
  58. package/dist/react/useThreadMessages.d.ts.map +1 -0
  59. package/dist/react/useThreadMessages.js +139 -0
  60. package/dist/react/useThreadMessages.js.map +1 -0
  61. package/dist/react/useUIMessages.d.ts +91 -0
  62. package/dist/react/useUIMessages.d.ts.map +1 -0
  63. package/dist/react/useUIMessages.js +115 -0
  64. package/dist/react/useUIMessages.js.map +1 -0
  65. package/dist/validators.d.ts +3 -1
  66. package/dist/validators.d.ts.map +1 -1
  67. package/dist/validators.js +1 -0
  68. package/dist/validators.js.map +1 -1
  69. package/package.json +4 -4
  70. package/src/{react/toUIMessages.ts → UIMessages.ts} +132 -15
  71. package/src/client/createTool.ts +1 -1
  72. package/src/client/index.ts +60 -41
  73. package/src/client/mockModel.ts +13 -8
  74. package/src/client/search.ts +2 -0
  75. package/src/client/start.ts +2 -1
  76. package/src/client/streaming.test.ts +186 -0
  77. package/src/client/streaming.ts +50 -9
  78. package/src/client/types.ts +19 -21
  79. package/src/component/_generated/api.d.ts +2 -0
  80. package/src/component/messages.ts +1 -1
  81. package/src/component/schema.ts +6 -0
  82. package/src/component/streams.ts +49 -66
  83. package/src/{react/deltas.test.ts → deltas.test.ts} +57 -46
  84. package/src/deltas.ts +589 -0
  85. package/src/{react/fromUIMessages.test.ts → fromUIMessages.test.ts} +108 -43
  86. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  87. package/src/react/index.ts +8 -250
  88. package/src/react/optimisticallySendMessage.ts +57 -8
  89. package/src/react/types.ts +6 -39
  90. package/src/react/useStreamingUIMessages.ts +294 -0
  91. package/src/react/useThreadMessages.ts +251 -0
  92. package/src/react/useUIMessages.ts +206 -0
  93. package/src/{react/toUIMessages.test.ts → toUIMessages.test.ts} +3 -3
  94. package/src/validators.ts +3 -0
  95. package/dist/client/textStreamParts.d.ts +0 -5
  96. package/dist/client/textStreamParts.d.ts.map +0 -1
  97. package/dist/client/textStreamParts.js +0 -44
  98. package/dist/client/textStreamParts.js.map +0 -1
  99. package/dist/react/deltas.d.ts +0 -27
  100. package/dist/react/deltas.d.ts.map +0 -1
  101. package/dist/react/deltas.js +0 -627
  102. package/dist/react/deltas.js.map +0 -1
  103. package/dist/react/fromUIMessages.d.ts +0 -13
  104. package/dist/react/fromUIMessages.d.ts.map +0 -1
  105. package/dist/react/fromUIMessages.js +0 -70
  106. package/dist/react/fromUIMessages.js.map +0 -1
  107. package/dist/react/toUIMessages.d.ts +0 -19
  108. package/dist/react/toUIMessages.d.ts.map +0 -1
  109. package/dist/react/toUIMessages.js.map +0 -1
  110. package/src/client/textStreamParts.ts +0 -62
  111. package/src/react/deltas.ts +0 -774
  112. package/src/react/fromUIMessages.ts +0 -85
@@ -1,774 +0,0 @@
1
- import type {
2
- JSONValue,
3
- ProviderMetadata,
4
- TextStreamPart,
5
- ToolSet,
6
- UIMessageChunk,
7
- } from "ai";
8
- import type { MessageDoc } from "../client/index.js";
9
- import {
10
- vSource,
11
- type Message,
12
- type MessageStatus,
13
- type StreamDelta,
14
- type StreamMessage,
15
- type vFilePart,
16
- type vReasoningPart,
17
- type vTextPart,
18
- type vToolCallPart,
19
- type vToolResultPart,
20
- } from "../validators.js";
21
- import type { Infer } from "convex/values";
22
- import { normalizeToolOutput, serializeWarnings } from "../mapping.js";
23
- import { parse } from "convex-helpers/validators";
24
- import { sorted } from "../shared.js";
25
-
26
- export function mergeDeltas(
27
- threadId: string,
28
- streamMessages: StreamMessage[],
29
- existingStreams: Array<{
30
- streamId: string;
31
- cursor: number;
32
- messages: MessageDoc[];
33
- }>,
34
- allDeltas: StreamDelta[],
35
- ): [
36
- MessageDoc[],
37
- Array<{ streamId: string; cursor: number; messages: MessageDoc[] }>,
38
- boolean,
39
- ] {
40
- const newStreams: Array<{
41
- streamId: string;
42
- cursor: number;
43
- messages: MessageDoc[];
44
- }> = [];
45
- // Seed the existing chunks
46
- let changed = false;
47
- for (const streamMessage of streamMessages) {
48
- const deltas = allDeltas.filter(
49
- (d) => d.streamId === streamMessage.streamId,
50
- );
51
- const existing = existingStreams.find(
52
- (s) => s.streamId === streamMessage.streamId,
53
- );
54
- const [newStream, messageChanged] = applyDeltasToStreamMessage(
55
- threadId,
56
- streamMessage,
57
- existing,
58
- deltas,
59
- );
60
- newStreams.push(newStream);
61
- if (messageChanged) changed = true;
62
- }
63
- for (const { streamId } of existingStreams) {
64
- if (!newStreams.find((s) => s.streamId === streamId)) {
65
- // There's a stream that's no longer active.
66
- changed = true;
67
- }
68
- }
69
- const messages = sorted(newStreams.map((s) => s.messages).flat());
70
- return [messages, newStreams, changed];
71
- }
72
-
73
- // exported for testing
74
- export function applyDeltasToStreamMessage(
75
- threadId: string,
76
- streamMessage: StreamMessage,
77
- existing:
78
- | { streamId: string; cursor: number; messages: MessageDoc[] }
79
- | undefined,
80
- deltas: StreamDelta[],
81
- ): [{ streamId: string; cursor: number; messages: MessageDoc[] }, boolean] {
82
- let changed = false;
83
- let cursor = existing?.cursor ?? 0;
84
- let parts: (UIMessageChunk<ToolSet> | TextStreamPart<ToolSet>)[] = [];
85
- for (const delta of deltas.sort((a, b) => a.start - b.start)) {
86
- if (delta.parts.length === 0) {
87
- console.warn(`Got delta with no parts: ${JSON.stringify(delta)}`);
88
- continue;
89
- }
90
- if (cursor !== delta.start) {
91
- if (cursor >= delta.end) {
92
- console.debug(
93
- `Got duplicate delta for stream ${delta.streamId} at ${delta.start}`,
94
- );
95
- continue;
96
- } else if (cursor < delta.start) {
97
- console.warn(
98
- `Got delta for stream ${delta.streamId} that has a gap ${cursor} -> ${delta.start}`,
99
- );
100
- continue;
101
- } else {
102
- throw new Error(
103
- `Got unexpected delta for stream ${delta.streamId}: delta: ${delta.start} -> ${delta.end} existing cursor: ${cursor}`,
104
- );
105
- }
106
- }
107
- changed = true;
108
- cursor = delta.end;
109
- parts.push(...delta.parts);
110
- }
111
- if (existing && existing.messages.length > 0 && !changed) {
112
- const lastMessage = existing.messages.at(-1)!;
113
- if (statusFromStreamStatus(streamMessage.status) !== lastMessage.status) {
114
- changed = true;
115
- }
116
- }
117
- if (!changed) {
118
- return [
119
- existing ?? { streamId: streamMessage.streamId, cursor, messages: [] },
120
- false,
121
- ];
122
- }
123
-
124
- const existingMessages = existing?.messages ?? [];
125
-
126
- let currentMessage: MessageDoc;
127
- if (existingMessages.length > 0) {
128
- // replace the last message with a new one
129
- const lastMessage = existingMessages.at(-1)!;
130
- currentMessage = {
131
- ...lastMessage,
132
- message: cloneMessageAndContent(lastMessage.message),
133
- status: statusFromStreamStatus(streamMessage.status),
134
- };
135
- } else {
136
- const newMessage = createStreamingMessage(
137
- threadId,
138
- streamMessage,
139
- parts[0]!,
140
- existingMessages.length,
141
- );
142
- parts = parts.slice(1);
143
- currentMessage = newMessage;
144
- }
145
- const newStream = {
146
- streamId: streamMessage.streamId,
147
- cursor,
148
- messages: [...existingMessages.slice(0, -1), currentMessage],
149
- };
150
- let lastContent = getLastContent(currentMessage);
151
- for (const part of parts) {
152
- let contentToAdd:
153
- | Infer<typeof vTextPart>
154
- | Infer<typeof vToolCallPart>
155
- | Infer<typeof vToolResultPart>
156
- | Infer<typeof vReasoningPart>
157
- | Infer<typeof vFilePart>
158
- | undefined;
159
- const isToolRole = part.type === "source" || part.type === "tool-result";
160
- if (isToolRole !== (currentMessage.message!.role === "tool")) {
161
- currentMessage = createStreamingMessage(
162
- threadId,
163
- streamMessage,
164
- part,
165
- newStream.messages.length,
166
- );
167
- lastContent = getLastContent(currentMessage);
168
- newStream.messages.push(currentMessage);
169
- continue;
170
- }
171
- switch (part.type) {
172
- case "text-delta": {
173
- const text = "text" in part ? part.text : part.delta;
174
- if (!text) {
175
- console.warn("Got text delta with no text", part);
176
- }
177
- currentMessage.text = (currentMessage.text ?? "") + text;
178
- if (lastContent?.type === "text") {
179
- lastContent.text = (lastContent.text ?? "") + text;
180
- lastContent.providerMetadata = mergeProviderMetadata(
181
- lastContent.providerMetadata,
182
- part.providerMetadata,
183
- );
184
- } else {
185
- contentToAdd = {
186
- type: "text",
187
- providerMetadata: part.providerMetadata,
188
- text,
189
- } satisfies Infer<typeof vTextPart>;
190
- }
191
- break;
192
- }
193
- case "tool-input-available": {
194
- if (
195
- lastContent?.type === "tool-call" &&
196
- lastContent.toolCallId === part.toolCallId
197
- ) {
198
- lastContent.args = part.input;
199
- lastContent.providerExecuted ??= part.providerExecuted;
200
- lastContent.providerMetadata = mergeProviderMetadata(
201
- lastContent.providerMetadata,
202
- part.providerMetadata,
203
- );
204
- } else {
205
- contentToAdd = toolCallContent(part);
206
- }
207
- break;
208
- }
209
- case "tool-input-start": {
210
- const toolCallId = "id" in part ? part.id : part.toolCallId;
211
- currentMessage.tool = true;
212
- contentToAdd = {
213
- type: "tool-call",
214
- toolCallId,
215
- toolName: part.toolName,
216
- args: "",
217
- providerMetadata:
218
- "providerMetadata" in part ? part.providerMetadata : undefined,
219
- providerExecuted:
220
- "providerExecuted" in part ? part.providerExecuted : undefined,
221
- } satisfies Infer<typeof vToolCallPart>;
222
- break;
223
- }
224
- case "tool-input-delta":
225
- {
226
- currentMessage.tool = true;
227
- if (lastContent?.type !== "tool-call") {
228
- throw new Error("Expected last content to be a tool call");
229
- }
230
- if (typeof lastContent.args !== "string") {
231
- lastContent.args = lastContent.args?.toString() ?? "";
232
- }
233
- const delta =
234
- "inputTextDelta" in part
235
- ? part.inputTextDelta
236
- : "delta" in part
237
- ? part.delta
238
- : "";
239
- lastContent.args = lastContent.args + delta;
240
- lastContent.providerMetadata =
241
- "providerMetadata" in part
242
- ? mergeProviderMetadata(
243
- lastContent.providerMetadata,
244
- part.providerMetadata,
245
- )
246
- : undefined;
247
- }
248
- break;
249
- case "tool-call": {
250
- currentMessage.tool = true;
251
- contentToAdd = toolCallContent(part);
252
- break;
253
- }
254
- case "tool-output-available":
255
- if (
256
- lastContent?.type === "tool-call" &&
257
- lastContent.toolCallId === part.toolCallId
258
- ) {
259
- contentToAdd = {
260
- type: "tool-result",
261
- toolCallId: part.toolCallId,
262
- toolName: lastContent.toolName,
263
- args: lastContent.args,
264
- result: part.output,
265
- providerExecuted: part.providerExecuted,
266
- } satisfies Infer<typeof vToolResultPart>;
267
- } else if (
268
- lastContent?.type === "tool-result" &&
269
- lastContent.toolCallId === part.toolCallId
270
- ) {
271
- lastContent.output = normalizeToolOutput(
272
- part.output as string | JSONValue | undefined,
273
- );
274
- lastContent.providerExecuted = part.providerExecuted;
275
- } else {
276
- console.warn(
277
- "Got tool output available part for unknown tool call",
278
- part,
279
- );
280
- }
281
- break;
282
- case "tool-result": {
283
- contentToAdd = toolResultContent(part);
284
- break;
285
- }
286
- case "reasoning-delta": {
287
- const text = "text" in part ? part.text : part.delta;
288
- currentMessage.reasoning = (currentMessage.reasoning ?? "") + text;
289
- if (lastContent?.type === "reasoning") {
290
- lastContent.text = (lastContent.text ?? "") + text;
291
- lastContent.providerMetadata = mergeProviderMetadata(
292
- lastContent.providerMetadata,
293
- part.providerMetadata,
294
- );
295
- } else {
296
- contentToAdd = {
297
- type: "reasoning",
298
- text,
299
- providerMetadata: part.providerMetadata,
300
- } satisfies Infer<typeof vReasoningPart>;
301
- }
302
- break;
303
- }
304
- case "source":
305
- if (!currentMessage.sources) {
306
- currentMessage.sources = [];
307
- }
308
- currentMessage.sources.push(parse(vSource, part));
309
- console.warn("Got source part with unknown source type", part);
310
- break;
311
- case "abort":
312
- currentMessage.status = "failed";
313
- currentMessage.error = "abort";
314
- break;
315
- case "error":
316
- currentMessage.status = "failed";
317
- currentMessage.error =
318
- "error" in part ? part.error?.toString() : part.errorText;
319
- break;
320
- case "message-metadata":
321
- currentMessage.providerMetadata ??= {};
322
- currentMessage.providerMetadata["metadata"] = {
323
- parts: [part.messageMetadata],
324
- };
325
- console.warn(
326
- "Putting message metadata part in providerMetadata. Use useUIMessages or useStreamingUIMessages instead.",
327
- part,
328
- );
329
- break;
330
- case "source-document":
331
- contentToAdd = {
332
- type: "file",
333
- data: part.sourceId,
334
- mimeType: part.mediaType,
335
- filename: part.title ?? part.filename,
336
- providerMetadata: part.providerMetadata,
337
- } satisfies Infer<typeof vFilePart>;
338
- break;
339
- case "source-url":
340
- contentToAdd = {
341
- type: "file",
342
- data: part.url,
343
- filename: part.title,
344
- mimeType: "text/plain", // What do we do here?
345
- providerMetadata: part.providerMetadata,
346
- } satisfies Infer<typeof vFilePart>;
347
- break;
348
- case "tool-input-error":
349
- if (
350
- lastContent?.type === "tool-call" &&
351
- lastContent.toolCallId === part.toolCallId
352
- ) {
353
- lastContent.args ||= part.input;
354
- lastContent.providerExecuted ??= part.providerExecuted;
355
- lastContent.providerMetadata = mergeProviderMetadata(
356
- lastContent.providerMetadata,
357
- part.providerMetadata,
358
- );
359
- } else {
360
- if (
361
- lastContent?.type === "tool-result" &&
362
- lastContent.toolCallId === part.toolCallId
363
- ) {
364
- lastContent.isError = true;
365
- lastContent.providerExecuted ??= part.providerExecuted;
366
- lastContent.providerMetadata = mergeProviderMetadata(
367
- lastContent.providerMetadata,
368
- part.providerMetadata,
369
- );
370
- } else {
371
- contentToAdd = toolCallContent(part);
372
- }
373
- }
374
- currentMessage.error = part.errorText;
375
- break;
376
- case "tool-output-error":
377
- if (
378
- lastContent?.type === "tool-result" &&
379
- lastContent.toolCallId === part.toolCallId
380
- ) {
381
- lastContent.isError = true;
382
- lastContent.output = {
383
- type: "error-text",
384
- value: part.errorText,
385
- };
386
- lastContent.providerExecuted = part.providerExecuted;
387
- } else if (
388
- lastContent?.type === "tool-call" &&
389
- lastContent.toolCallId === part.toolCallId
390
- ) {
391
- console.warn(
392
- "Got tool output error part for unknown tool result",
393
- part,
394
- );
395
- contentToAdd = {
396
- type: "tool-result",
397
- toolCallId: part.toolCallId,
398
- toolName: lastContent.toolName,
399
- args: lastContent.args,
400
- output: {
401
- type: "error-text",
402
- value: part.errorText,
403
- },
404
- providerExecuted: part.providerExecuted,
405
- isError: true,
406
- } satisfies Infer<typeof vToolResultPart>;
407
- } else {
408
- console.warn(
409
- "Got tool output error part for unknown tool call",
410
- part,
411
- );
412
- }
413
- break;
414
- case "tool-error":
415
- if (
416
- lastContent?.type === "tool-result" &&
417
- lastContent.toolCallId === part.toolCallId
418
- ) {
419
- lastContent.isError = true;
420
- lastContent.output = {
421
- type: "error-json",
422
- value: part.error,
423
- };
424
- lastContent.providerExecuted = part.providerExecuted;
425
- } else {
426
- if (
427
- lastContent?.type === "tool-call" &&
428
- lastContent.toolCallId === part.toolCallId
429
- ) {
430
- lastContent.args ||= part.input;
431
- lastContent.providerExecuted ??= part.providerExecuted;
432
- }
433
- currentMessage.error = part.error?.toString();
434
- contentToAdd = {
435
- type: "tool-result",
436
- toolCallId: part.toolCallId,
437
- toolName: part.toolName,
438
- result: part.error,
439
- providerExecuted: part.providerExecuted,
440
- isError: true,
441
- args: part.input,
442
- } satisfies Infer<typeof vToolResultPart>;
443
- }
444
- break;
445
- case "tool-input-end":
446
- case "text-end":
447
- case "reasoning-end":
448
- case "finish-step":
449
- case "finish":
450
- case "file":
451
- case "raw":
452
- case "start-step":
453
- case "text-start":
454
- case "reasoning-start":
455
- case "start":
456
- // ignore
457
- break;
458
- default: {
459
- if (!part.type.startsWith("data-")) {
460
- console.warn(`Received unexpected part: ${JSON.stringify(part)}`);
461
- } else {
462
- console.warn(
463
- "Dropping a data part. Use useUIMessages or useStreamingUIMessages instead for full UIMessage streaming support.",
464
- part,
465
- );
466
- }
467
- break;
468
- }
469
- }
470
- if (contentToAdd) {
471
- if (!currentMessage.message!.content) {
472
- currentMessage.message!.content = [];
473
- }
474
- if (!Array.isArray(currentMessage.message?.content)) {
475
- throw new Error("Expected message content to be an array");
476
- }
477
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
478
- currentMessage.message.content.push(contentToAdd as any);
479
- lastContent = contentToAdd;
480
- }
481
- }
482
- return [newStream, true];
483
- }
484
-
485
- function mergeProviderMetadata(
486
- existing: ProviderMetadata | undefined,
487
- part: ProviderMetadata | undefined,
488
- ): ProviderMetadata | undefined {
489
- if (!existing && !part) {
490
- return undefined;
491
- }
492
- if (!existing) {
493
- return part;
494
- }
495
- if (!part) {
496
- return existing;
497
- }
498
- const merged: ProviderMetadata = existing;
499
- for (const [provider, metadata] of Object.entries(part)) {
500
- merged[provider] = {
501
- ...merged[provider],
502
- ...metadata,
503
- };
504
- }
505
- return merged;
506
- }
507
-
508
- function toolCallContent(
509
- part:
510
- | Extract<TextStreamPart<ToolSet>, { type: "tool-call" }>
511
- | Extract<UIMessageChunk<ToolSet>, { type: "tool-input-error" }>
512
- | Extract<UIMessageChunk<ToolSet>, { type: "tool-input-available" }>,
513
- ): Infer<typeof vToolCallPart> {
514
- const args = "args" in part ? part.args : part.input;
515
- return {
516
- type: "tool-call",
517
- toolCallId: part.toolCallId,
518
- toolName: part.toolName,
519
- args,
520
- providerMetadata: part.providerMetadata,
521
- providerExecuted: part.providerExecuted,
522
- } satisfies Infer<typeof vToolCallPart>;
523
- }
524
-
525
- function toolResultContent(
526
- part: Extract<TextStreamPart<ToolSet>, { type: "tool-result" }>,
527
- ): Infer<typeof vToolResultPart> {
528
- return {
529
- type: "tool-result",
530
- toolCallId: part.toolCallId,
531
- toolName: part.toolName,
532
- result: part.output,
533
- args: part.input,
534
- providerExecuted: part.providerExecuted,
535
- } satisfies Infer<typeof vToolResultPart>;
536
- }
537
- function cloneMessageAndContent(
538
- message: Message | undefined,
539
- ): Message | undefined {
540
- return (
541
- message &&
542
- ({
543
- ...message,
544
- content: Array.isArray(message.content)
545
- ? [...message.content]
546
- : message.content,
547
- } as typeof message)
548
- );
549
- }
550
-
551
- function getLastContent(message: MessageDoc) {
552
- if (Array.isArray(message.message?.content)) {
553
- return message.message.content.at(-1);
554
- }
555
- return undefined;
556
- }
557
-
558
- function statusFromStreamStatus(
559
- status: StreamMessage["status"],
560
- ): MessageStatus {
561
- switch (status) {
562
- case "streaming":
563
- return "pending";
564
- case "finished":
565
- return "success";
566
- case "aborted":
567
- return "failed";
568
- default:
569
- return "pending";
570
- }
571
- }
572
-
573
- // TODO: share more code with applyDeltasToStreamMessage
574
- export function createStreamingMessage(
575
- threadId: string,
576
- message: StreamMessage,
577
- part: UIMessageChunk<ToolSet> | TextStreamPart<ToolSet>,
578
- index: number,
579
- ): MessageDoc {
580
- const { streamId, ...rest } = message;
581
- const metadata: MessageDoc = {
582
- ...rest,
583
- _id: `${streamId}-${index}`,
584
- _creationTime: Date.now(),
585
- status: statusFromStreamStatus(message.status),
586
- stepOrder: message.stepOrder + index,
587
- threadId,
588
- tool: false,
589
- };
590
- const providerMetadata =
591
- "providerMetadata" in part ? part.providerMetadata : undefined;
592
- metadata.providerMetadata = providerMetadata;
593
-
594
- switch (part.type) {
595
- case "text-delta": {
596
- const text = "text" in part ? part.text : part.delta;
597
- return {
598
- ...metadata,
599
- message: {
600
- role: "assistant",
601
- content: [{ type: "text", text, providerMetadata }],
602
- },
603
- text,
604
- };
605
- }
606
- case "tool-input-start": {
607
- return {
608
- ...metadata,
609
- tool: true,
610
- message: {
611
- role: "assistant",
612
- content: [
613
- {
614
- type: "tool-call",
615
- toolName: part.toolName,
616
- toolCallId: "id" in part ? part.id : part.toolCallId,
617
- args: "", // when it's a string, it's a partial call
618
- providerExecuted:
619
- "providerExecuted" in part ? part.providerExecuted : undefined,
620
- providerMetadata,
621
- },
622
- ],
623
- },
624
- };
625
- }
626
- case "tool-input-delta": {
627
- console.warn("Received tool call delta part first??");
628
- const delta = "delta" in part ? part.delta : part.inputTextDelta;
629
- const toolCallId = "id" in part ? part.id : part.toolCallId;
630
- const toolName = part.type.slice("tool-".length);
631
- return {
632
- ...metadata,
633
- tool: true,
634
- message: {
635
- role: "assistant",
636
- content: [
637
- {
638
- type: "tool-call",
639
- toolCallId,
640
- toolName,
641
- args: delta,
642
- providerMetadata,
643
- },
644
- ],
645
- },
646
- };
647
- }
648
- case "tool-call": {
649
- return {
650
- ...metadata,
651
- tool: true,
652
- message: { role: "assistant", content: [toolCallContent(part)] },
653
- };
654
- }
655
- case "tool-result":
656
- return {
657
- ...metadata,
658
- tool: true,
659
- message: { role: "tool", content: [toolResultContent(part)] },
660
- };
661
- case "reasoning-delta": {
662
- const text = "text" in part ? part.text : part.delta;
663
- return {
664
- ...metadata,
665
- message: {
666
- role: "assistant",
667
- content: [
668
- {
669
- type: "reasoning",
670
- text,
671
- providerMetadata,
672
- },
673
- ],
674
- },
675
- reasoning: text,
676
- };
677
- }
678
- case "source":
679
- console.warn("Received source part first??");
680
- return {
681
- ...metadata,
682
- tool: true,
683
- message: { role: "tool", content: [] },
684
- sources: [part],
685
- };
686
- case "raw":
687
- case "start":
688
- return {
689
- ...metadata,
690
- tool: false,
691
- message: { role: "assistant", content: [] },
692
- };
693
- case "start-step":
694
- return {
695
- ...metadata,
696
- message: { role: "assistant", content: [] },
697
- ...("warnings" in part && part.warnings?.length > 0
698
- ? { warnings: serializeWarnings(part.warnings) }
699
- : {}),
700
- };
701
- case "reasoning-start":
702
- return {
703
- ...metadata,
704
- message: { role: "assistant", content: [] },
705
- reasoning: "",
706
- };
707
- case "abort":
708
- return {
709
- ...metadata,
710
- message: { role: "assistant", content: [] },
711
- status: "failed",
712
- error: "abort",
713
- };
714
- case "tool-error":
715
- return {
716
- ...metadata,
717
- message: {
718
- role: "assistant",
719
- content: [
720
- {
721
- type: "tool-call",
722
- toolCallId: part.toolCallId,
723
- args: part.input,
724
- toolName: part.toolName,
725
- providerExecuted: part.providerExecuted,
726
- providerMetadata,
727
- },
728
- {
729
- type: "tool-result",
730
- result: part.error,
731
- toolCallId: part.toolCallId,
732
- isError: true,
733
- args: part.input,
734
- toolName: part.toolName,
735
- providerExecuted: part.providerExecuted,
736
- },
737
- ],
738
- },
739
- error: part.error?.toString(),
740
- };
741
- case "text-start":
742
- return {
743
- ...metadata,
744
- message: { role: "assistant", content: [] },
745
- providerMetadata,
746
- id: part.id,
747
- _id: part.id,
748
- };
749
- case "error": {
750
- const errorMessage: MessageDoc = {
751
- ...metadata,
752
- message: {
753
- role: "assistant",
754
- content: [],
755
- },
756
- };
757
- if ("error" in part) {
758
- errorMessage.error = part.error?.toString();
759
- } else if (part.errorText) {
760
- errorMessage.error = part.errorText;
761
- } else {
762
- console.warn("Got an error delta with no error", part);
763
- }
764
- return errorMessage;
765
- }
766
- // case "raw":
767
- // return {
768
- // ...metadata,
769
- // message: { role: "assistant", content: [part.rawValue] },
770
- // };
771
- default:
772
- throw new Error(`Unexpected part type: ${JSON.stringify(part)}`);
773
- }
774
- }