@convex-dev/agent 0.2.0-alpha.2 → 0.2.1

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 (100) hide show
  1. package/dist/client/createTool.d.ts +4 -6
  2. package/dist/client/createTool.d.ts.map +1 -1
  3. package/dist/client/createTool.js +0 -1
  4. package/dist/client/createTool.js.map +1 -1
  5. package/dist/client/definePlaygroundAPI.d.ts +28 -28
  6. package/dist/client/files.d.ts +9 -2
  7. package/dist/client/files.d.ts.map +1 -1
  8. package/dist/client/files.js +61 -6
  9. package/dist/client/files.js.map +1 -1
  10. package/dist/client/index.d.ts +232 -242
  11. package/dist/client/index.d.ts.map +1 -1
  12. package/dist/client/index.js +375 -383
  13. package/dist/client/index.js.map +1 -1
  14. package/dist/client/messages.d.ts +91 -373
  15. package/dist/client/messages.d.ts.map +1 -1
  16. package/dist/client/messages.js +7 -1
  17. package/dist/client/messages.js.map +1 -1
  18. package/dist/client/search.d.ts +15 -15
  19. package/dist/client/search.d.ts.map +1 -1
  20. package/dist/client/search.js +3 -3
  21. package/dist/client/search.js.map +1 -1
  22. package/dist/client/streaming.d.ts +30 -22
  23. package/dist/client/streaming.d.ts.map +1 -1
  24. package/dist/client/streaming.js +36 -40
  25. package/dist/client/streaming.js.map +1 -1
  26. package/dist/client/types.d.ts +148 -61
  27. package/dist/client/types.d.ts.map +1 -1
  28. package/dist/component/_generated/api.d.ts +47 -357
  29. package/dist/component/messages.d.ts +157 -150
  30. package/dist/component/messages.d.ts.map +1 -1
  31. package/dist/component/messages.js +124 -70
  32. package/dist/component/messages.js.map +1 -1
  33. package/dist/component/schema.d.ts +1147 -2687
  34. package/dist/component/schema.d.ts.map +1 -1
  35. package/dist/component/schema.js +5 -4
  36. package/dist/component/schema.js.map +1 -1
  37. package/dist/component/streams.d.ts +29 -323
  38. package/dist/component/streams.d.ts.map +1 -1
  39. package/dist/component/streams.js +104 -60
  40. package/dist/component/streams.js.map +1 -1
  41. package/dist/component/threads.d.ts +4 -4
  42. package/dist/component/users.d.ts +3 -3
  43. package/dist/mapping.d.ts +4 -1
  44. package/dist/mapping.d.ts.map +1 -1
  45. package/dist/mapping.js +17 -1
  46. package/dist/mapping.js.map +1 -1
  47. package/dist/parts.d.ts +3 -0
  48. package/dist/parts.d.ts.map +1 -0
  49. package/dist/parts.js +34 -0
  50. package/dist/parts.js.map +1 -0
  51. package/dist/react/SmoothText.d.ts +5 -0
  52. package/dist/react/SmoothText.d.ts.map +1 -0
  53. package/dist/react/SmoothText.js +6 -0
  54. package/dist/react/SmoothText.js.map +1 -0
  55. package/dist/react/deltas.d.ts +3 -2
  56. package/dist/react/deltas.d.ts.map +1 -1
  57. package/dist/react/deltas.js +19 -79
  58. package/dist/react/deltas.js.map +1 -1
  59. package/dist/react/index.d.ts +1 -0
  60. package/dist/react/index.d.ts.map +1 -1
  61. package/dist/react/index.js +31 -11
  62. package/dist/react/index.js.map +1 -1
  63. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  64. package/dist/react/optimisticallySendMessage.js +2 -8
  65. package/dist/react/optimisticallySendMessage.js.map +1 -1
  66. package/dist/react/useSmoothText.d.ts +13 -12
  67. package/dist/react/useSmoothText.d.ts.map +1 -1
  68. package/dist/react/useSmoothText.js +1 -1
  69. package/dist/react/useSmoothText.js.map +1 -1
  70. package/dist/shared.d.ts +3 -3
  71. package/dist/shared.d.ts.map +1 -1
  72. package/dist/shared.js.map +1 -1
  73. package/dist/validators.d.ts +109 -1590
  74. package/dist/validators.d.ts.map +1 -1
  75. package/dist/validators.js +4 -81
  76. package/dist/validators.js.map +1 -1
  77. package/package.json +3 -3
  78. package/src/client/createTool.ts +5 -10
  79. package/src/client/files.ts +89 -11
  80. package/src/client/index.test.ts +25 -35
  81. package/src/client/index.ts +686 -652
  82. package/src/client/messages.ts +16 -2
  83. package/src/client/search.ts +10 -7
  84. package/src/client/streaming.ts +62 -57
  85. package/src/client/types.ts +227 -97
  86. package/src/component/_generated/api.d.ts +47 -357
  87. package/src/component/messages.ts +157 -96
  88. package/src/component/schema.ts +4 -4
  89. package/src/component/streams.ts +180 -69
  90. package/src/mapping.ts +23 -2
  91. package/src/parts.ts +39 -0
  92. package/src/react/SmoothText.tsx +9 -0
  93. package/src/react/deltas.test.ts +38 -42
  94. package/src/react/deltas.ts +24 -86
  95. package/src/react/index.ts +35 -13
  96. package/src/react/optimisticallySendMessage.ts +2 -8
  97. package/src/react/useSmoothText.ts +14 -15
  98. package/src/shared.ts +3 -3
  99. package/src/validators.test.ts +0 -17
  100. package/src/validators.ts +8 -101
@@ -1,6 +1,7 @@
1
1
  import { omit, pick } from "convex-helpers";
2
2
  import { v } from "convex/values";
3
3
  import {
4
+ type MessageWithMetadataInternal,
4
5
  type StreamDelta,
5
6
  vStreamDelta,
6
7
  vStreamMessage,
@@ -18,6 +19,9 @@ import schema from "./schema.js";
18
19
  import { stream } from "convex-helpers/server/stream";
19
20
  import { mergedStream } from "convex-helpers/server/stream";
20
21
  import { paginator } from "convex-helpers/server/pagination";
22
+ import type { WithoutSystemFields } from "convex/server";
23
+ import { mergeDeltas } from "../react/deltas.js";
24
+ import { serializeOrThrow } from "../mapping.js";
21
25
 
22
26
  const SECOND = 1000;
23
27
  const MINUTE = 60 * SECOND;
@@ -33,13 +37,16 @@ export const addDelta = mutation({
33
37
  args: deltaValidator,
34
38
  returns: v.boolean(),
35
39
  handler: async (ctx, args) => {
36
- await ctx.db.insert("streamDeltas", args);
37
- await heartbeatStream(ctx, { streamId: args.streamId });
38
40
  const stream = await ctx.db.get(args.streamId);
39
- if (stream?.state.kind !== "streaming") {
40
- console.warn(`Stream is not streaming: ${args.streamId}`);
41
+ if (!stream) {
42
+ console.warn("Stream not found", args.streamId);
43
+ return false;
44
+ }
45
+ if (stream.state.kind !== "streaming") {
41
46
  return false;
42
47
  }
48
+ await ctx.db.insert("streamDeltas", args);
49
+ await heartbeatStream(ctx, { streamId: args.streamId });
43
50
  return true;
44
51
  },
45
52
  });
@@ -48,10 +55,7 @@ export const listDeltas = query({
48
55
  args: {
49
56
  threadId: v.id("threads"),
50
57
  cursors: v.array(
51
- v.object({
52
- streamId: v.id("streamingMessages"),
53
- cursor: v.number(),
54
- }),
58
+ v.object({ streamId: v.id("streamingMessages"), cursor: v.number() }),
55
59
  ),
56
60
  },
57
61
  returns: v.array(vStreamDelta),
@@ -85,10 +89,7 @@ export const create = mutation({
85
89
  args: omit(schema.tables.streamingMessages.validator.fields, ["state"]),
86
90
  returns: v.id("streamingMessages"),
87
91
  handler: async (ctx, args) => {
88
- const state = {
89
- kind: "streaming" as const,
90
- lastHeartbeat: Date.now(),
91
- };
92
+ const state = { kind: "streaming" as const, lastHeartbeat: Date.now() };
92
93
  const streamId = await ctx.db.insert("streamingMessages", {
93
94
  ...args,
94
95
  state,
@@ -152,11 +153,7 @@ export const list = query({
152
153
  });
153
154
 
154
155
  export const abortByOrder = mutation({
155
- args: {
156
- threadId: v.id("threads"),
157
- order: v.number(),
158
- reason: v.string(),
159
- },
156
+ args: { threadId: v.id("threads"), order: v.number(), reason: v.string() },
160
157
  returns: v.boolean(),
161
158
  handler: async (ctx, args) => {
162
159
  const streams = await ctx.db
@@ -169,10 +166,7 @@ export const abortByOrder = mutation({
169
166
  )
170
167
  .take(100);
171
168
  for (const stream of streams) {
172
- await abortById(ctx, {
173
- streamId: stream._id,
174
- reason: args.reason,
175
- });
169
+ await abortById(ctx, { streamId: stream._id, reason: args.reason });
176
170
  }
177
171
  return streams.length > 0;
178
172
  },
@@ -182,6 +176,7 @@ export const abort = mutation({
182
176
  args: {
183
177
  streamId: v.id("streamingMessages"),
184
178
  reason: v.string(),
179
+ finalDelta: v.optional(deltaValidator),
185
180
  },
186
181
  returns: v.boolean(),
187
182
  handler: abortById,
@@ -189,16 +184,20 @@ export const abort = mutation({
189
184
 
190
185
  async function abortById(
191
186
  ctx: MutationCtx,
192
- args: { streamId: Id<"streamingMessages">; reason: string },
187
+ args: {
188
+ streamId: Id<"streamingMessages">;
189
+ reason: string;
190
+ finalDelta?: WithoutSystemFields<Doc<"streamDeltas">>;
191
+ },
193
192
  ) {
194
193
  const stream = await ctx.db.get(args.streamId);
195
194
  if (!stream) {
196
195
  throw new Error(`Stream not found: ${args.streamId}`);
197
196
  }
197
+ if (args.finalDelta) {
198
+ await ctx.db.insert("streamDeltas", args.finalDelta);
199
+ }
198
200
  if (stream.state.kind !== "streaming") {
199
- console.warn(
200
- `Stream trying to abort but not currently streaming (${stream.state.kind}): ${args.streamId}`,
201
- );
202
201
  return false;
203
202
  }
204
203
  await cleanupTimeoutFn(ctx, stream);
@@ -220,49 +219,65 @@ async function cleanupTimeoutFn(
220
219
  }
221
220
  }
222
221
 
222
+ // No longer used from the DeltaStreamer
223
223
  export const finish = mutation({
224
224
  args: {
225
225
  streamId: v.id("streamingMessages"),
226
226
  finalDelta: v.optional(deltaValidator),
227
227
  },
228
228
  returns: v.null(),
229
- handler: async (ctx, args) => {
230
- if (args.finalDelta) {
231
- await ctx.db.insert("streamDeltas", args.finalDelta);
232
- }
233
- const stream = await ctx.db.get(args.streamId);
234
- if (!stream) {
235
- throw new Error(`Stream not found: ${args.streamId}`);
236
- }
237
- if (stream.state.kind !== "streaming") {
238
- console.warn(
239
- `Stream trying to finish but not currently streaming: ${args.streamId}`,
240
- );
241
- return;
242
- }
243
- await cleanupTimeoutFn(ctx, stream);
244
- const cleanupFnId = await ctx.scheduler.runAfter(
245
- DELETE_STREAM_DELAY,
246
- api.streams.deleteStreamAsync,
247
- { streamId: args.streamId },
248
- );
249
- await ctx.db.patch(args.streamId, {
250
- state: { kind: "finished", endedAt: Date.now(), cleanupFnId },
251
- });
229
+ handler: finishHandler,
230
+ });
231
+
232
+ export async function finishHandler(
233
+ ctx: MutationCtx,
234
+ args: {
235
+ streamId: Id<"streamingMessages">;
236
+ finalDelta?: WithoutSystemFields<Doc<"streamDeltas">>;
252
237
  },
238
+ ) {
239
+ if (args.finalDelta) {
240
+ await ctx.db.insert("streamDeltas", args.finalDelta);
241
+ }
242
+ const stream = await ctx.db.get(args.streamId);
243
+ if (!stream) {
244
+ throw new Error(`Stream not found: ${args.streamId}`);
245
+ }
246
+ if (stream.state.kind !== "streaming") {
247
+ console.warn(
248
+ `Stream trying to finish ${args.streamId} but is ${stream.state.kind}`,
249
+ );
250
+ return;
251
+ }
252
+ await cleanupTimeoutFn(ctx, stream);
253
+ const cleanupFnId = await ctx.scheduler.runAfter(
254
+ DELETE_STREAM_DELAY,
255
+ api.streams.deleteStreamAsync,
256
+ { streamId: args.streamId },
257
+ );
258
+ await ctx.db.patch(args.streamId, {
259
+ state: { kind: "finished", endedAt: Date.now(), cleanupFnId },
260
+ });
261
+ }
262
+
263
+ // TODO: use this heartbeat while streaming, every 30 seconds or so,
264
+ // then reduce the timeout to 60 seconds.
265
+ export const heartbeat = mutation({
266
+ args: { streamId: v.id("streamingMessages") },
267
+ returns: v.null(),
268
+ handler: heartbeatStream,
253
269
  });
254
270
 
255
271
  async function heartbeatStream(
256
272
  ctx: MutationCtx,
257
273
  args: { streamId: Id<"streamingMessages"> },
258
- ) {
274
+ ): Promise<void> {
259
275
  const stream = await ctx.db.get(args.streamId);
260
276
  if (!stream) {
261
277
  console.warn("Stream not found", args.streamId);
262
278
  return;
263
279
  }
264
280
  if (stream.state.kind !== "streaming") {
265
- console.warn("Stream is not streaming", args.streamId);
266
281
  return;
267
282
  }
268
283
  if (Date.now() - stream.state.lastHeartbeat < TIMEOUT_INTERVAL / 4) {
@@ -286,11 +301,7 @@ async function heartbeatStream(
286
301
  { streamId: args.streamId },
287
302
  );
288
303
  await ctx.db.patch(args.streamId, {
289
- state: {
290
- kind: "streaming",
291
- lastHeartbeat: Date.now(),
292
- timeoutFnId,
293
- },
304
+ state: { kind: "streaming", lastHeartbeat: Date.now(), timeoutFnId },
294
305
  });
295
306
  }
296
307
 
@@ -304,10 +315,7 @@ export const timeoutStream = internalMutation({
304
315
  return;
305
316
  }
306
317
  await ctx.db.patch(args.streamId, {
307
- state: {
308
- kind: "aborted",
309
- reason: "timeout",
310
- },
318
+ state: { kind: "aborted", reason: "timeout" },
311
319
  });
312
320
  },
313
321
  });
@@ -362,11 +370,7 @@ export async function deleteStreamsPageForThreadId(
362
370
  "stepOrder",
363
371
  ]).first();
364
372
  if (!streamMessage) {
365
- return {
366
- isDone: true,
367
- streamOrder: undefined,
368
- deltaCursor: undefined,
369
- };
373
+ return { isDone: true, streamOrder: undefined, deltaCursor: undefined };
370
374
  }
371
375
  const result = await deletePageForStreamId(ctx, {
372
376
  streamId: streamMessage._id,
@@ -375,11 +379,7 @@ export async function deleteStreamsPageForThreadId(
375
379
  if (result.isDone) {
376
380
  deltaCursor = undefined;
377
381
  }
378
- return {
379
- isDone: false,
380
- streamOrder: streamMessage.order,
381
- deltaCursor,
382
- };
382
+ return { isDone: false, streamOrder: streamMessage.order, deltaCursor };
383
383
  }
384
384
 
385
385
  export const deleteStreamsPageForThreadIdMutation = internalMutation({
@@ -474,3 +474,114 @@ export const deleteAllStreamsForThreadIdSync = action({
474
474
  }
475
475
  },
476
476
  });
477
+
478
+ export async function getStreamingMessages(
479
+ ctx: MutationCtx,
480
+ threadId: Id<"threads">,
481
+ order: number,
482
+ stepOrder: number,
483
+ ): Promise<Doc<"streamingMessages">[]> {
484
+ return mergedStream(
485
+ (["aborted", "streaming", "finished"] as const).map((state) =>
486
+ stream(ctx.db, schema)
487
+ .query("streamingMessages")
488
+ .withIndex("threadId_state_order_stepOrder", (q) =>
489
+ q
490
+ .eq("threadId", threadId)
491
+ .eq("state.kind", state)
492
+ .eq("order", order)
493
+ .lte("stepOrder", stepOrder),
494
+ )
495
+ .order("desc"),
496
+ ),
497
+ ["stepOrder"],
498
+ ).take(10);
499
+ }
500
+
501
+ export async function getStreamingMessagesWithMetadata(
502
+ ctx: MutationCtx,
503
+ {
504
+ threadId,
505
+ order,
506
+ stepOrder,
507
+ }: { threadId: Id<"threads">; order: number; stepOrder: number },
508
+ metadata: { status: "success" | "failed"; error?: string },
509
+ ): Promise<MessageWithMetadataInternal[]> {
510
+ // See if there are any streaming messages for this order
511
+ const streamingMessages = await getStreamingMessages(
512
+ ctx,
513
+ threadId,
514
+ order,
515
+ stepOrder,
516
+ );
517
+ const messages = (
518
+ await Promise.all(
519
+ streamingMessages.map((m) =>
520
+ getMessagesWithMetadataForStreamingMessage(
521
+ ctx,
522
+ threadId,
523
+ stepOrder,
524
+ m,
525
+ metadata,
526
+ ),
527
+ ),
528
+ )
529
+ ).flat();
530
+ return messages;
531
+ }
532
+
533
+ export async function getMessagesWithMetadataForStreamingMessage(
534
+ ctx: MutationCtx,
535
+ threadId: Id<"threads">,
536
+ stepOrder: number,
537
+ streamingMessage: Doc<"streamingMessages">,
538
+ metadata: { status: "success" | "failed"; error?: string },
539
+ ): Promise<MessageWithMetadataInternal[]> {
540
+ const deltas = await ctx.db
541
+ .query("streamDeltas")
542
+ .withIndex("streamId_start_end", (q) =>
543
+ q.eq("streamId", streamingMessage._id),
544
+ )
545
+ .take(1000);
546
+ const [messageDocs] = mergeDeltas(
547
+ threadId,
548
+ [
549
+ {
550
+ ...streamingMessage,
551
+ status: "streaming",
552
+ streamId: streamingMessage._id,
553
+ },
554
+ ],
555
+ [],
556
+ deltas,
557
+ );
558
+ // We don't save messages that have already been saved
559
+ const numToSkip = stepOrder - streamingMessage.stepOrder;
560
+ const messages = await Promise.all(
561
+ messageDocs
562
+ .slice(numToSkip)
563
+ .filter((m) => m.message !== undefined)
564
+ .map(async (msg) => {
565
+ const message = await serializeOrThrow(msg.message!);
566
+ return {
567
+ message,
568
+ ...pick(msg, [
569
+ "fileIds",
570
+ "status",
571
+ "finishReason",
572
+ "model",
573
+ "provider",
574
+ "providerMetadata",
575
+ "sources",
576
+ "reasoning",
577
+ "reasoningDetails",
578
+ "usage",
579
+ "warnings",
580
+ "error",
581
+ ]),
582
+ ...metadata,
583
+ } as MessageWithMetadataInternal;
584
+ }),
585
+ );
586
+ return messages;
587
+ }
package/src/mapping.ts CHANGED
@@ -76,6 +76,25 @@ export async function serializeMessage(
76
76
  };
77
77
  }
78
78
 
79
+ export async function serializeOrThrow(
80
+ message: ModelMessage | Message,
81
+ ): Promise<SerializedMessage> {
82
+ const { content } = await serializeContent(
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ {} as any,
85
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
+ {} as any,
87
+ message.content,
88
+ );
89
+ return {
90
+ role: message.role,
91
+ content,
92
+ ...(message.providerOptions
93
+ ? { providerOptions: message.providerOptions }
94
+ : {}),
95
+ } as SerializedMessage;
96
+ }
97
+
79
98
  export function deserializeMessage(message: SerializedMessage): ModelMessage {
80
99
  return {
81
100
  ...message,
@@ -128,7 +147,7 @@ export async function serializeNewMessagesInStep<TOOLS extends ToolSet>(
128
147
  component: AgentComponent,
129
148
  step: StepResult<TOOLS>,
130
149
  metadata: { model: string; provider: string },
131
- ): Promise<MessageWithMetadata[]> {
150
+ ): Promise<{ messages: MessageWithMetadata[] }> {
132
151
  // If there are tool results, there's another message with the tool results
133
152
  // ref: https://github.com/vercel/ai/blob/main/packages/ai/core/generate-text/to-response-messages.ts
134
153
  const assistantFields = {
@@ -158,7 +177,8 @@ export async function serializeNewMessagesInStep<TOOLS extends ToolSet>(
158
177
  };
159
178
  }),
160
179
  );
161
- return messages;
180
+ // TODO: capture step.files separately?
181
+ return { messages };
162
182
  }
163
183
 
164
184
  export async function serializeObjectResult(
@@ -288,6 +308,7 @@ export async function serializeContent(
288
308
  providerOptions: part.providerOptions,
289
309
  } satisfies Infer<typeof vReasoningPart>;
290
310
  }
311
+ // Not in current generation output, but could be in historical messages
291
312
  case "redacted-reasoning": {
292
313
  return {
293
314
  type: part.type,
package/src/parts.ts ADDED
@@ -0,0 +1,39 @@
1
+ import type { TextStreamPart, ToolSet } from "ai";
2
+
3
+ export function serializeTextStreamingPartsV5(
4
+ parts: TextStreamPart<ToolSet>[],
5
+ ): TextStreamPart<ToolSet>[] {
6
+ const compressed: TextStreamPart<ToolSet>[] = [];
7
+ for (const part of parts) {
8
+ const last = compressed.at(-1);
9
+ if (part.type === "text-delta" && last?.type === "text-delta") {
10
+ last.text += part.text;
11
+ } else if (
12
+ part.type === "reasoning-delta" &&
13
+ last?.type === "reasoning-delta"
14
+ ) {
15
+ last.text += part.text;
16
+ } else {
17
+ if (
18
+ part.type === "start-step" ||
19
+ part.type === "finish-step" ||
20
+ part.type === "start" ||
21
+ part.type === "finish"
22
+ ) {
23
+ continue;
24
+ }
25
+ if (part.type === "file") {
26
+ compressed.push({
27
+ type: "file",
28
+ file: {
29
+ mediaType: part.file.mediaType,
30
+ base64: part.file.base64,
31
+ uint8Array: new Uint8Array([]),
32
+ },
33
+ });
34
+ }
35
+ compressed.push(part);
36
+ }
37
+ }
38
+ return compressed;
39
+ }
@@ -0,0 +1,9 @@
1
+ import { useSmoothText, type SmoothTextOptions } from "./useSmoothText.js";
2
+
3
+ export function SmoothText({
4
+ text,
5
+ ...options
6
+ }: { text: string } & SmoothTextOptions) {
7
+ const [visibleText] = useSmoothText(text, options);
8
+ return visibleText;
9
+ }
@@ -1,36 +1,24 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import { mergeDeltas, applyDeltasToStreamMessage } from "./deltas.js";
3
- import type {
4
- StreamMessage,
5
- StreamDelta,
6
- TextStreamPart,
7
- } from "../validators.js";
3
+ import type { StreamMessage, StreamDelta } from "../validators.js";
8
4
  import { omit } from "convex-helpers";
5
+ import type { TextStreamPart, ToolSet } from "ai";
9
6
 
10
7
  function makeStreamMessage(
11
8
  streamId: string,
12
9
  order: number,
13
10
  stepOrder: number,
14
11
  ): StreamMessage {
15
- return {
16
- streamId,
17
- order,
18
- stepOrder,
19
- } as StreamMessage;
12
+ return { streamId, order, stepOrder } as StreamMessage;
20
13
  }
21
14
 
22
15
  function makeDelta(
23
16
  streamId: string,
24
17
  start: number,
25
18
  end: number,
26
- parts: TextStreamPart[],
19
+ parts: TextStreamPart<ToolSet>[],
27
20
  ): StreamDelta {
28
- return {
29
- streamId,
30
- start,
31
- end,
32
- parts,
33
- };
21
+ return { streamId, start, end, parts };
34
22
  }
35
23
 
36
24
  describe("mergeDeltas", () => {
@@ -38,7 +26,9 @@ describe("mergeDeltas", () => {
38
26
  const streamId = "s1";
39
27
  const streamMessages = [makeStreamMessage(streamId, 1, 0)];
40
28
  const deltas = [
41
- makeDelta(streamId, 0, 5, [{ type: "text-delta", textDelta: "Hello" }]),
29
+ makeDelta(streamId, 0, 5, [
30
+ { type: "text-delta", id: "1", text: "Hello" },
31
+ ]),
42
32
  ];
43
33
  const [messages, newStreams, changed] = mergeDeltas(
44
34
  "thread1",
@@ -57,9 +47,11 @@ describe("mergeDeltas", () => {
57
47
  const streamId = "s1";
58
48
  const streamMessages = [makeStreamMessage(streamId, 1, 0)];
59
49
  const deltas = [
60
- makeDelta(streamId, 0, 5, [{ type: "text-delta", textDelta: "Hello" }]),
50
+ makeDelta(streamId, 0, 5, [
51
+ { type: "text-delta", id: "1", text: "Hello" },
52
+ ]),
61
53
  makeDelta(streamId, 5, 11, [
62
- { type: "text-delta", textDelta: " World!" },
54
+ { type: "text-delta", id: "2", text: " World!" },
63
55
  ]),
64
56
  ];
65
57
  const [messages, newStreams, changed] = mergeDeltas(
@@ -83,7 +75,7 @@ describe("mergeDeltas", () => {
83
75
  type: "tool-call",
84
76
  toolCallId: "call1",
85
77
  toolName: "myTool",
86
- args: "",
78
+ input: "",
87
79
  },
88
80
  ]),
89
81
  makeDelta(streamId, 1, 2, [
@@ -91,7 +83,8 @@ describe("mergeDeltas", () => {
91
83
  type: "tool-result",
92
84
  toolCallId: "call1",
93
85
  toolName: "myTool",
94
- result: "42",
86
+ input: undefined,
87
+ output: "42",
95
88
  },
96
89
  ]),
97
90
  ];
@@ -106,12 +99,7 @@ describe("mergeDeltas", () => {
106
99
  expect(messages[0].tool).toBe(true);
107
100
  const content = messages[0].message?.content;
108
101
  expect(content).toEqual([
109
- {
110
- type: "tool-call",
111
- toolCallId: "call1",
112
- toolName: "myTool",
113
- args: "",
114
- },
102
+ { type: "tool-call", toolCallId: "call1", toolName: "myTool", args: "" },
115
103
  ]);
116
104
  expect(messages[1].message?.role).toBe("tool");
117
105
  expect(messages[1].tool).toBe(true);
@@ -145,8 +133,8 @@ describe("mergeDeltas", () => {
145
133
  const s1 = makeStreamMessage("s1", 1, 0);
146
134
  const s2 = makeStreamMessage("s2", 2, 0);
147
135
  const deltas = [
148
- makeDelta("s2", 0, 3, [{ type: "text-delta", textDelta: "B" }]),
149
- makeDelta("s1", 0, 3, [{ type: "text-delta", textDelta: "A" }]),
136
+ makeDelta("s2", 0, 3, [{ type: "text-delta", id: "1", text: "B" }]),
137
+ makeDelta("s1", 0, 3, [{ type: "text-delta", id: "2", text: "A" }]),
150
138
  ];
151
139
  const [messages, _, changed] = mergeDeltas("thread1", [s2, s1], [], deltas);
152
140
  expect(messages).toHaveLength(2);
@@ -162,11 +150,13 @@ describe("mergeDeltas", () => {
162
150
  const streamId = "s4";
163
151
  const streamMessages = [makeStreamMessage(streamId, 4, 0)];
164
152
  const deltas = [
165
- makeDelta(streamId, 0, 5, [{ type: "text-delta", textDelta: "Hello" }]),
153
+ makeDelta(streamId, 0, 5, [
154
+ { type: "text-delta", id: "1", text: "Hello" },
155
+ ]),
166
156
  makeDelta(streamId, 5, 11, [
167
- { type: "text-delta", textDelta: " World!" },
157
+ { type: "text-delta", id: "2", text: " World!" },
168
158
  ]),
169
- makeDelta(streamId, 11, 12, [{ type: "text-delta", textDelta: "!" }]),
159
+ makeDelta(streamId, 11, 12, [{ type: "text-delta", id: "3", text: "!" }]),
170
160
  ];
171
161
  const [messages] = mergeDeltas("thread1", streamMessages, [], deltas);
172
162
  expect(messages).toHaveLength(1);
@@ -185,10 +175,10 @@ describe("mergeDeltas", () => {
185
175
  const streamMessages = [makeStreamMessage(streamId, 6, 0)];
186
176
  const deltas = [
187
177
  makeDelta(streamId, 0, 1, [
188
- { type: "reasoning", textDelta: "I'm thinking..." },
178
+ { type: "reasoning-delta", id: "1", text: "I'm thinking..." },
189
179
  ]),
190
180
  makeDelta(streamId, 1, 2, [
191
- { type: "reasoning", textDelta: " Still thinking..." },
181
+ { type: "reasoning-delta", id: "2", text: " Still thinking..." },
192
182
  ]),
193
183
  ];
194
184
  const [messages] = mergeDeltas("thread1", streamMessages, [], deltas);
@@ -206,9 +196,11 @@ describe("mergeDeltas", () => {
206
196
  const streamId = "s7";
207
197
  const streamMessage = makeStreamMessage(streamId, 7, 0);
208
198
  const deltas = [
209
- makeDelta(streamId, 0, 5, [{ type: "text-delta", textDelta: "Hello" }]),
199
+ makeDelta(streamId, 0, 5, [
200
+ { type: "text-delta", id: "1", text: "Hello" },
201
+ ]),
210
202
  makeDelta(streamId, 5, 11, [
211
- { type: "text-delta", textDelta: " World!" },
203
+ { type: "text-delta", id: "2", text: " World!" },
212
204
  ]),
213
205
  ];
214
206
  // First call: apply both deltas
@@ -232,7 +224,7 @@ describe("mergeDeltas", () => {
232
224
  // Third call: add a new delta
233
225
  const moreDeltas = [
234
226
  ...deltas,
235
- makeDelta(streamId, 11, 12, [{ type: "text-delta", textDelta: "!" }]),
227
+ makeDelta(streamId, 11, 12, [{ type: "text-delta", id: "3", text: "!" }]),
236
228
  ];
237
229
  [result, changed] = applyDeltasToStreamMessage(
238
230
  "thread1",
@@ -259,9 +251,11 @@ describe("mergeDeltas", () => {
259
251
  const streamId = "s8";
260
252
  const streamMessages = [makeStreamMessage(streamId, 8, 0)];
261
253
  const deltas = [
262
- makeDelta(streamId, 0, 5, [{ type: "text-delta", textDelta: "Hello" }]),
254
+ makeDelta(streamId, 0, 5, [
255
+ { type: "text-delta", id: "1", text: "Hello" },
256
+ ]),
263
257
  makeDelta(streamId, 5, 11, [
264
- { type: "text-delta", textDelta: " World!" },
258
+ { type: "text-delta", id: "2", text: " World!" },
265
259
  ]),
266
260
  ];
267
261
  // Deep freeze inputs to catch mutation
@@ -306,9 +300,11 @@ describe("mergeDeltas", () => {
306
300
  // Inputs should remain unchanged
307
301
  expect(streamMessages).toEqual([makeStreamMessage(streamId, 8, 0)]);
308
302
  expect(deltas).toEqual([
309
- makeDelta(streamId, 0, 5, [{ type: "text-delta", textDelta: "Hello" }]),
303
+ makeDelta(streamId, 0, 5, [
304
+ { type: "text-delta", id: "1", text: "Hello" },
305
+ ]),
310
306
  makeDelta(streamId, 5, 11, [
311
- { type: "text-delta", textDelta: " World!" },
307
+ { type: "text-delta", id: "2", text: " World!" },
312
308
  ]),
313
309
  ]);
314
310
  });