@convex-dev/agent 0.2.0-alpha.2 → 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 (96) hide show
  1. package/dist/client/createTool.d.ts +3 -5
  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 +8 -1
  7. package/dist/client/files.d.ts.map +1 -1
  8. package/dist/client/files.js +60 -5
  9. package/dist/client/files.js.map +1 -1
  10. package/dist/client/index.d.ts +223 -239
  11. package/dist/client/index.d.ts.map +1 -1
  12. package/dist/client/index.js +368 -382
  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 +21 -22
  23. package/dist/client/streaming.d.ts.map +1 -1
  24. package/dist/client/streaming.js +35 -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/validators.d.ts +109 -1590
  71. package/dist/validators.d.ts.map +1 -1
  72. package/dist/validators.js +4 -81
  73. package/dist/validators.js.map +1 -1
  74. package/package.json +3 -3
  75. package/src/client/createTool.ts +4 -9
  76. package/src/client/files.ts +88 -10
  77. package/src/client/index.test.ts +9 -21
  78. package/src/client/index.ts +664 -650
  79. package/src/client/messages.ts +16 -2
  80. package/src/client/search.ts +10 -7
  81. package/src/client/streaming.ts +53 -57
  82. package/src/client/types.ts +227 -97
  83. package/src/component/_generated/api.d.ts +47 -357
  84. package/src/component/messages.ts +157 -96
  85. package/src/component/schema.ts +4 -4
  86. package/src/component/streams.ts +180 -69
  87. package/src/mapping.ts +23 -2
  88. package/src/parts.ts +39 -0
  89. package/src/react/SmoothText.tsx +9 -0
  90. package/src/react/deltas.test.ts +38 -42
  91. package/src/react/deltas.ts +24 -86
  92. package/src/react/index.ts +35 -13
  93. package/src/react/optimisticallySendMessage.ts +2 -8
  94. package/src/react/useSmoothText.ts +14 -15
  95. package/src/validators.test.ts +0 -17
  96. package/src/validators.ts +8 -101
@@ -1,6 +1,9 @@
1
- import { assert, omit } from "convex-helpers";
1
+ import { assert, omit, pick } from "convex-helpers";
2
2
  import { mergedStream, stream } from "convex-helpers/server/stream";
3
- import { paginationOptsValidator } from "convex/server";
3
+ import {
4
+ paginationOptsValidator,
5
+ type WithoutSystemFields,
6
+ } from "convex/server";
4
7
  import type { ObjectType } from "convex/values";
5
8
  import {
6
9
  DEFAULT_MESSAGE_RANGE,
@@ -26,11 +29,6 @@ import {
26
29
  } from "./_generated/server.js";
27
30
  import type { MessageDoc } from "./schema.js";
28
31
  import { schema, v, vMessageDoc } from "./schema.js";
29
- import {
30
- getThread as _getThread,
31
- listThreadsByUserId as _listThreadsByUserId,
32
- updateThread as _updateThread,
33
- } from "./threads.js";
34
32
  import { insertVector, searchVectors } from "./vector/index.js";
35
33
  import {
36
34
  type VectorDimension,
@@ -39,6 +37,8 @@ import {
39
37
  vVectorId,
40
38
  } from "./vector/tables.js";
41
39
  import { changeRefcount } from "./files.js";
40
+ import { getStreamingMessagesWithMetadata } from "./streams.js";
41
+ import { partial } from "convex-helpers/validators";
42
42
 
43
43
  function publicMessage(message: Doc<"messages">): MessageDoc {
44
44
  return omit(message, ["parentMessageId", "stepId", "files"]);
@@ -127,6 +127,8 @@ const addMessagesArgs = {
127
127
  messages: v.array(vMessageWithMetadataInternal),
128
128
  embeddings: v.optional(vMessageEmbeddingsWithDimension),
129
129
  failPendingSteps: v.optional(v.boolean()),
130
+ // A pending message to update. If the pending message failed, abort.
131
+ pendingMessageId: v.optional(v.id("messages")),
130
132
  };
131
133
  export const addMessages = mutation({
132
134
  args: addMessagesArgs,
@@ -144,9 +146,15 @@ async function addMessagesHandler(
144
146
  assert(thread, `Thread ${args.threadId} not found`);
145
147
  userId = thread.userId;
146
148
  }
147
- const { embeddings, failPendingSteps, messages, promptMessageId, ...rest } =
148
- args;
149
- const parentMessage = promptMessageId && (await ctx.db.get(promptMessageId));
149
+ const {
150
+ embeddings,
151
+ failPendingSteps,
152
+ messages,
153
+ promptMessageId,
154
+ pendingMessageId,
155
+ ...rest
156
+ } = args;
157
+ const promptMessage = promptMessageId && (await ctx.db.get(promptMessageId));
150
158
  if (failPendingSteps) {
151
159
  assert(args.threadId, "threadId is required to fail pending steps");
152
160
  const pendingMessages = await ctx.db
@@ -154,26 +162,37 @@ async function addMessagesHandler(
154
162
  .withIndex("threadId_status_tool_order_stepOrder", (q) =>
155
163
  q.eq("threadId", threadId).eq("status", "pending"),
156
164
  )
157
- .collect();
165
+ .order("desc")
166
+ .take(100);
158
167
  await Promise.all(
159
168
  pendingMessages
160
- .filter((m) => !parentMessage || m.order === parentMessage.order)
161
- .map((m) =>
162
- ctx.db.patch(m._id, { status: "failed", error: "Restarting" }),
163
- ),
169
+ .filter((m) => !promptMessage || m.order === promptMessage.order)
170
+ .filter((m) => !pendingMessageId || m._id !== pendingMessageId)
171
+ .map(async (m) => {
172
+ if (m.embeddingId) {
173
+ await ctx.db.delete(m.embeddingId);
174
+ }
175
+ await ctx.db.patch(m._id, {
176
+ status: "failed",
177
+ error: "Restarting",
178
+ embeddingId: undefined,
179
+ });
180
+ }),
164
181
  );
165
182
  }
166
183
  let order, stepOrder;
167
184
  let fail = false;
185
+ let error: string | undefined;
168
186
  if (promptMessageId) {
169
- assert(parentMessage, `Parent message ${promptMessageId} not found`);
170
- if (parentMessage.status === "failed") {
187
+ assert(promptMessage, `Parent message ${promptMessageId} not found`);
188
+ if (promptMessage.status === "failed") {
171
189
  fail = true;
190
+ error = promptMessage.error ?? error ?? "The prompt message failed";
172
191
  }
173
- order = parentMessage.order;
192
+ order = promptMessage.order;
174
193
  // Defend against there being existing messages with this parent.
175
194
  const maxMessage = await getMaxMessage(ctx, threadId, order);
176
- stepOrder = maxMessage?.stepOrder ?? parentMessage.stepOrder;
195
+ stepOrder = maxMessage?.stepOrder ?? promptMessage.stepOrder;
177
196
  } else {
178
197
  const maxMessage = await getMaxMessage(ctx, threadId);
179
198
  order = maxMessage?.order ?? -1;
@@ -189,7 +208,12 @@ async function addMessagesHandler(
189
208
  for (let i = 0; i < messages.length; i++) {
190
209
  const message = messages[i];
191
210
  let embeddingId: VectorTableId | undefined;
192
- if (embeddings && embeddings.vectors[i]) {
211
+ if (
212
+ embeddings &&
213
+ embeddings.vectors[i] &&
214
+ !fail &&
215
+ message.status !== "failed"
216
+ ) {
193
217
  embeddingId = await insertVector(ctx, embeddings.dimension, {
194
218
  vector: embeddings.vectors[i]!,
195
219
  model: embeddings.model,
@@ -198,8 +222,50 @@ async function addMessagesHandler(
198
222
  threadId,
199
223
  });
200
224
  }
225
+ const messageDoc = {
226
+ ...rest,
227
+ ...message,
228
+ embeddingId,
229
+ parentMessageId: promptMessageId,
230
+ userId,
231
+ tool: isTool(message.message),
232
+ text: extractText(message.message),
233
+ status: fail ? "failed" : (message.status ?? "success"),
234
+ error: fail ? error : message.error,
235
+ } satisfies Omit<
236
+ WithoutSystemFields<Doc<"messages">>,
237
+ "order" | "stepOrder"
238
+ >;
239
+ // If there is a pending message, we replace that one with the first message
240
+ // and subsequent ones will follow the regular order/subOrder advancement.
241
+ if (i === 0 && pendingMessageId) {
242
+ const pendingMessage = await ctx.db.get(pendingMessageId);
243
+ assert(pendingMessage, `Pending msg ${pendingMessageId} not found`);
244
+ if (pendingMessage.status === "failed") {
245
+ fail = true;
246
+ error =
247
+ `Trying to update a message that failed: ${pendingMessageId}, ` +
248
+ `error: ${pendingMessage.error ?? error}`;
249
+ messageDoc.status = "failed";
250
+ messageDoc.error = error;
251
+ }
252
+ if (message.fileIds) {
253
+ await changeRefcount(
254
+ ctx,
255
+ pendingMessage.fileIds ?? [],
256
+ message.fileIds,
257
+ );
258
+ }
259
+ await ctx.db.replace(pendingMessage._id, {
260
+ ...messageDoc,
261
+ order: pendingMessage.order,
262
+ stepOrder: pendingMessage.stepOrder,
263
+ });
264
+ toReturn.push(pendingMessage);
265
+ continue;
266
+ }
201
267
  if (message.message.role === "user") {
202
- if (parentMessage && parentMessage.order === order) {
268
+ if (promptMessage && promptMessage.order === order) {
203
269
  // see if there's a later message than the parent message order
204
270
  const maxMessage = await getMaxMessage(ctx, threadId);
205
271
  order = (maxMessage?.order ?? order) + 1;
@@ -208,21 +274,14 @@ async function addMessagesHandler(
208
274
  }
209
275
  stepOrder = 0;
210
276
  } else {
277
+ if (order < 0) {
278
+ order = 0;
279
+ }
211
280
  stepOrder++;
212
281
  }
213
282
  const messageId = await ctx.db.insert("messages", {
214
- ...rest,
215
- ...message,
216
- embeddingId,
217
- parentMessageId: promptMessageId,
218
- userId,
283
+ ...messageDoc,
219
284
  order,
220
- tool: isTool(message.message),
221
- text: extractText(message.message),
222
- status: fail ? "failed" : (message.status ?? "success"),
223
- error: fail
224
- ? (parentMessage?.error ?? "Parent message failed")
225
- : undefined,
226
285
  stepOrder,
227
286
  });
228
287
  if (message.fileIds) {
@@ -271,51 +330,77 @@ function orderedMessagesStream(
271
330
  );
272
331
  }
273
332
 
274
- export const rollbackMessage = mutation({
333
+ export const finalizeMessage = mutation({
275
334
  args: {
276
335
  messageId: v.id("messages"),
277
- error: v.optional(v.string()),
336
+ result: v.union(
337
+ v.object({ status: v.literal("success") }),
338
+ v.object({ status: v.literal("failed"), error: v.string() }),
339
+ ),
278
340
  },
279
341
  returns: v.null(),
280
- handler: async (ctx, { messageId, error }) => {
342
+ handler: async (ctx, { messageId, result }) => {
281
343
  const message = await ctx.db.get(messageId);
282
344
  assert(message, `Message ${messageId} not found`);
283
- const messages = await orderedMessagesStream(
284
- ctx,
285
- message.threadId,
286
- "asc",
287
- message.order,
288
- ).collect();
289
- for (const m of messages) {
290
- if (m.status === "pending") {
291
- await ctx.db.patch(m._id, { status: "failed", error });
345
+ if (message.status !== "pending") {
346
+ console.log(
347
+ "Trying to finalize a message that's already",
348
+ message.status,
349
+ );
350
+ return;
351
+ }
352
+ // See if we can add any in-progress data
353
+ if (message.message === undefined) {
354
+ const messages = await getStreamingMessagesWithMetadata(
355
+ ctx,
356
+ message,
357
+ result,
358
+ );
359
+ if (messages.length > 0) {
360
+ await addMessagesHandler(ctx, {
361
+ messages,
362
+ threadId: message.threadId,
363
+ agentName: message.agentName,
364
+ failPendingSteps: false,
365
+ pendingMessageId: messageId,
366
+ userId: message.userId,
367
+ embeddings: undefined,
368
+ });
369
+ return;
292
370
  }
293
371
  }
294
-
295
- await ctx.db.patch(messageId, {
296
- status: "failed",
297
- error: error,
298
- });
299
- },
300
- });
301
-
302
- export const commitMessage = mutation({
303
- args: {
304
- messageId: v.id("messages"),
372
+ if (result.status === "failed") {
373
+ if (message.embeddingId) {
374
+ await ctx.db.delete(message.embeddingId);
375
+ }
376
+ await ctx.db.patch(messageId, {
377
+ status: "failed",
378
+ error: result.error,
379
+ embeddingId: undefined,
380
+ });
381
+ } else {
382
+ await ctx.db.patch(messageId, { status: "success" });
383
+ }
305
384
  },
306
- returns: v.null(),
307
- handler: commitMessageHandler,
308
385
  });
309
386
 
310
387
  export const updateMessage = mutation({
311
388
  args: {
312
389
  messageId: v.id("messages"),
313
- patch: v.object({
314
- message: v.optional(vMessageDoc.fields.message),
315
- fileIds: v.optional(v.array(v.id("files"))),
316
- status: v.optional(vMessageStatus),
317
- error: v.optional(v.string()),
318
- }),
390
+ patch: v.object(
391
+ partial(
392
+ pick(schema.tables.messages.validator.fields, [
393
+ "message",
394
+ "fileIds",
395
+ "status",
396
+ "error",
397
+ "model",
398
+ "provider",
399
+ "providerOptions",
400
+ "finishReason",
401
+ ]),
402
+ ),
403
+ ),
319
404
  },
320
405
  returns: vMessageDoc,
321
406
  handler: async (ctx, args) => {
@@ -326,9 +411,7 @@ export const updateMessage = mutation({
326
411
  await changeRefcount(ctx, message.fileIds ?? [], args.patch.fileIds);
327
412
  }
328
413
 
329
- const patch: Partial<Doc<"messages">> = {
330
- ...args.patch,
331
- };
414
+ const patch: Partial<Doc<"messages">> = { ...args.patch };
332
415
 
333
416
  if (args.patch.message !== undefined) {
334
417
  patch.message = args.patch.message;
@@ -336,38 +419,18 @@ export const updateMessage = mutation({
336
419
  patch.text = extractText(args.patch.message);
337
420
  }
338
421
 
422
+ if (args.patch.status === "failed") {
423
+ if (message.embeddingId) {
424
+ await ctx.db.delete(message.embeddingId);
425
+ }
426
+ patch.embeddingId = undefined;
427
+ }
428
+
339
429
  await ctx.db.patch(args.messageId, patch);
340
430
  return publicMessage((await ctx.db.get(args.messageId))!);
341
431
  },
342
432
  });
343
433
 
344
- async function commitMessageHandler(
345
- ctx: MutationCtx,
346
- { messageId }: { messageId: Id<"messages"> },
347
- ) {
348
- const message = await ctx.db.get(messageId);
349
- assert(message, `Message ${messageId} not found`);
350
-
351
- const order = message.order!;
352
- const messages = await mergedStream(
353
- [true, false].map((tool) =>
354
- stream(ctx.db, schema)
355
- .query("messages")
356
- .withIndex("threadId_status_tool_order_stepOrder", (q) =>
357
- q
358
- .eq("threadId", message.threadId)
359
- .eq("status", "pending")
360
- .eq("tool", tool)
361
- .eq("order", order),
362
- ),
363
- ),
364
- ["order", "stepOrder"],
365
- ).collect();
366
- for (const message of messages) {
367
- await ctx.db.patch(message._id, { status: "success" });
368
- }
369
- }
370
-
371
434
  export const listMessagesByThreadId = query({
372
435
  args: {
373
436
  threadId: v.id("threads"),
@@ -427,9 +490,7 @@ export const listMessagesByThreadId = query({
427
490
  });
428
491
 
429
492
  export const getMessagesByIds = query({
430
- args: {
431
- messageIds: v.array(v.id("messages")),
432
- },
493
+ args: { messageIds: v.array(v.id("messages")) },
433
494
  handler: async (ctx, args) => {
434
495
  return (await Promise.all(args.messageIds.map((id) => ctx.db.get(id)))).map(
435
496
  (m) => (m ? publicMessage(m) : null),
@@ -11,7 +11,6 @@ import {
11
11
  vProviderOptions,
12
12
  vProviderMetadata,
13
13
  vReasoningDetails,
14
- vTextStreamPart,
15
14
  } from "../validators.js";
16
15
  import { typedV } from "convex-helpers/validators";
17
16
  import vectorTables, { vVectorId } from "./vector/tables.js";
@@ -56,10 +55,11 @@ export const schema = defineSchema({
56
55
  usage: v.optional(vUsage),
57
56
  providerMetadata: v.optional(vProviderMetadata), // Received from model
58
57
  sources: v.optional(v.array(vSource)),
59
- reasoning: v.optional(v.string()),
60
- reasoningDetails: v.optional(vReasoningDetails),
61
58
  warnings: v.optional(v.array(vLanguageModelCallWarning)),
62
59
  finishReason: v.optional(vFinishReason),
60
+ // Likely deprecated soon
61
+ reasoning: v.optional(v.string()),
62
+ reasoningDetails: v.optional(vReasoningDetails),
63
63
  // DEPRECATED
64
64
  id: v.optional(v.string()), // external id, e.g. from Vercel AI SDK
65
65
  parentMessageId: v.optional(v.id("messages")),
@@ -129,7 +129,7 @@ export const schema = defineSchema({
129
129
  // the indexes work like: 0 <first> 1 <second> 2 <third> 3 ...
130
130
  start: v.number(), // inclusive
131
131
  end: v.number(), // exclusive
132
- parts: v.array(vTextStreamPart),
132
+ parts: v.array(v.any()),
133
133
  }).index("streamId_start_end", ["streamId", "start", "end"]),
134
134
 
135
135
  memories: defineTable({