@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.
Files changed (122) hide show
  1. package/dist/client/createTool.d.ts +30 -11
  2. package/dist/client/createTool.d.ts.map +1 -1
  3. package/dist/client/createTool.js +24 -10
  4. package/dist/client/createTool.js.map +1 -1
  5. package/dist/client/defaultComponent.d.ts +11 -0
  6. package/dist/client/defaultComponent.d.ts.map +1 -0
  7. package/dist/client/defaultComponent.js +7 -0
  8. package/dist/client/defaultComponent.js.map +1 -0
  9. package/dist/client/definePlaygroundAPI.d.ts +68 -23
  10. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  11. package/dist/client/definePlaygroundAPI.js +7 -20
  12. package/dist/client/definePlaygroundAPI.js.map +1 -1
  13. package/dist/client/files.d.ts +14 -4
  14. package/dist/client/files.d.ts.map +1 -1
  15. package/dist/client/files.js +66 -11
  16. package/dist/client/files.js.map +1 -1
  17. package/dist/client/index.d.ts +323 -780
  18. package/dist/client/index.d.ts.map +1 -1
  19. package/dist/client/index.js +445 -543
  20. package/dist/client/index.js.map +1 -1
  21. package/dist/client/messages.d.ts +256 -0
  22. package/dist/client/messages.d.ts.map +1 -0
  23. package/dist/client/messages.js +97 -0
  24. package/dist/client/messages.js.map +1 -0
  25. package/dist/client/search.d.ts +35 -12
  26. package/dist/client/search.d.ts.map +1 -1
  27. package/dist/client/search.js +21 -4
  28. package/dist/client/search.js.map +1 -1
  29. package/dist/client/streaming.d.ts +21 -21
  30. package/dist/client/streaming.d.ts.map +1 -1
  31. package/dist/client/streaming.js +35 -40
  32. package/dist/client/streaming.js.map +1 -1
  33. package/dist/client/threads.d.ts +46 -0
  34. package/dist/client/threads.d.ts.map +1 -0
  35. package/dist/client/threads.js +49 -0
  36. package/dist/client/threads.js.map +1 -0
  37. package/dist/client/types.d.ts +159 -75
  38. package/dist/client/types.d.ts.map +1 -1
  39. package/dist/component/_generated/api.d.ts +261 -209
  40. package/dist/component/messages.d.ts +314 -105
  41. package/dist/component/messages.d.ts.map +1 -1
  42. package/dist/component/messages.js +141 -83
  43. package/dist/component/messages.js.map +1 -1
  44. package/dist/component/schema.d.ts +631 -870
  45. package/dist/component/schema.d.ts.map +1 -1
  46. package/dist/component/schema.js +11 -16
  47. package/dist/component/schema.js.map +1 -1
  48. package/dist/component/streams.d.ts +29 -149
  49. package/dist/component/streams.d.ts.map +1 -1
  50. package/dist/component/streams.js +104 -60
  51. package/dist/component/streams.js.map +1 -1
  52. package/dist/component/threads.d.ts +4 -4
  53. package/dist/component/users.d.ts +3 -3
  54. package/dist/mapping.d.ts +15 -16
  55. package/dist/mapping.d.ts.map +1 -1
  56. package/dist/mapping.js +208 -62
  57. package/dist/mapping.js.map +1 -1
  58. package/dist/parts.d.ts +3 -0
  59. package/dist/parts.d.ts.map +1 -0
  60. package/dist/parts.js +34 -0
  61. package/dist/parts.js.map +1 -0
  62. package/dist/react/SmoothText.d.ts +5 -0
  63. package/dist/react/SmoothText.d.ts.map +1 -0
  64. package/dist/react/SmoothText.js +6 -0
  65. package/dist/react/SmoothText.js.map +1 -0
  66. package/dist/react/deltas.d.ts +3 -5
  67. package/dist/react/deltas.d.ts.map +1 -1
  68. package/dist/react/deltas.js +89 -53
  69. package/dist/react/deltas.js.map +1 -1
  70. package/dist/react/index.d.ts +1 -0
  71. package/dist/react/index.d.ts.map +1 -1
  72. package/dist/react/index.js +31 -11
  73. package/dist/react/index.js.map +1 -1
  74. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  75. package/dist/react/optimisticallySendMessage.js +2 -8
  76. package/dist/react/optimisticallySendMessage.js.map +1 -1
  77. package/dist/react/toUIMessages.d.ts +5 -4
  78. package/dist/react/toUIMessages.d.ts.map +1 -1
  79. package/dist/react/toUIMessages.js +103 -40
  80. package/dist/react/toUIMessages.js.map +1 -1
  81. package/dist/react/useSmoothText.d.ts +13 -12
  82. package/dist/react/useSmoothText.d.ts.map +1 -1
  83. package/dist/react/useSmoothText.js +1 -1
  84. package/dist/react/useSmoothText.js.map +1 -1
  85. package/dist/validators.d.ts +751 -1464
  86. package/dist/validators.d.ts.map +1 -1
  87. package/dist/validators.js +39 -80
  88. package/dist/validators.js.map +1 -1
  89. package/package.json +34 -30
  90. package/src/client/createTool.ts +66 -40
  91. package/src/client/defaultComponent.ts +17 -0
  92. package/src/client/definePlaygroundAPI.ts +17 -31
  93. package/src/client/files.ts +94 -17
  94. package/src/client/index.test.ts +28 -38
  95. package/src/client/index.ts +785 -977
  96. package/src/client/messages.ts +205 -0
  97. package/src/client/search.ts +37 -10
  98. package/src/client/streaming.ts +53 -56
  99. package/src/client/threads.ts +78 -0
  100. package/src/client/types.ts +239 -131
  101. package/src/component/_generated/api.d.ts +261 -209
  102. package/src/component/messages.test.ts +182 -40
  103. package/src/component/messages.ts +165 -108
  104. package/src/component/schema.ts +11 -17
  105. package/src/component/streams.ts +180 -69
  106. package/src/mapping.ts +253 -96
  107. package/src/parts.ts +39 -0
  108. package/src/react/SmoothText.tsx +9 -0
  109. package/src/react/deltas.test.ts +38 -42
  110. package/src/react/deltas.ts +116 -65
  111. package/src/react/index.ts +35 -13
  112. package/src/react/optimisticallySendMessage.ts +2 -8
  113. package/src/react/toUIMessages.test.ts +154 -36
  114. package/src/react/toUIMessages.ts +136 -57
  115. package/src/react/useSmoothText.ts +14 -15
  116. package/src/validators.test.ts +1 -117
  117. package/src/validators.ts +57 -107
  118. package/dist/client/listMessages.d.ts +0 -22
  119. package/dist/client/listMessages.d.ts.map +0 -1
  120. package/dist/client/listMessages.js +0 -25
  121. package/dist/client/listMessages.js.map +0 -1
  122. package/src/client/listMessages.ts +0 -38
@@ -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
+ }