@convex-dev/agent 0.0.1-alpha.1 → 0.0.1-alpha.2

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 (54) hide show
  1. package/README.md +297 -6
  2. package/dist/commonjs/client/index.d.ts +480 -56
  3. package/dist/commonjs/client/index.d.ts.map +1 -1
  4. package/dist/commonjs/client/index.js +154 -78
  5. package/dist/commonjs/client/index.js.map +1 -1
  6. package/dist/commonjs/client/types.d.ts +3 -0
  7. package/dist/commonjs/client/types.d.ts.map +1 -1
  8. package/dist/commonjs/component/messages.d.ts +10 -9
  9. package/dist/commonjs/component/messages.d.ts.map +1 -1
  10. package/dist/commonjs/component/messages.js +84 -41
  11. package/dist/commonjs/component/messages.js.map +1 -1
  12. package/dist/commonjs/component/schema.d.ts +10 -6
  13. package/dist/commonjs/component/schema.d.ts.map +1 -1
  14. package/dist/commonjs/component/schema.js +9 -2
  15. package/dist/commonjs/component/schema.js.map +1 -1
  16. package/dist/commonjs/mapping.d.ts +6 -1
  17. package/dist/commonjs/mapping.d.ts.map +1 -1
  18. package/dist/commonjs/mapping.js +25 -0
  19. package/dist/commonjs/mapping.js.map +1 -1
  20. package/dist/commonjs/validators.d.ts +1375 -0
  21. package/dist/commonjs/validators.d.ts.map +1 -1
  22. package/dist/commonjs/validators.js +27 -0
  23. package/dist/commonjs/validators.js.map +1 -1
  24. package/dist/esm/client/index.d.ts +480 -56
  25. package/dist/esm/client/index.d.ts.map +1 -1
  26. package/dist/esm/client/index.js +154 -78
  27. package/dist/esm/client/index.js.map +1 -1
  28. package/dist/esm/client/types.d.ts +3 -0
  29. package/dist/esm/client/types.d.ts.map +1 -1
  30. package/dist/esm/component/messages.d.ts +10 -9
  31. package/dist/esm/component/messages.d.ts.map +1 -1
  32. package/dist/esm/component/messages.js +84 -41
  33. package/dist/esm/component/messages.js.map +1 -1
  34. package/dist/esm/component/schema.d.ts +10 -6
  35. package/dist/esm/component/schema.d.ts.map +1 -1
  36. package/dist/esm/component/schema.js +9 -2
  37. package/dist/esm/component/schema.js.map +1 -1
  38. package/dist/esm/mapping.d.ts +6 -1
  39. package/dist/esm/mapping.d.ts.map +1 -1
  40. package/dist/esm/mapping.js +25 -0
  41. package/dist/esm/mapping.js.map +1 -1
  42. package/dist/esm/validators.d.ts +1375 -0
  43. package/dist/esm/validators.d.ts.map +1 -1
  44. package/dist/esm/validators.js +27 -0
  45. package/dist/esm/validators.js.map +1 -1
  46. package/package.json +2 -2
  47. package/src/client/index.ts +290 -188
  48. package/src/client/types.ts +4 -0
  49. package/src/component/_generated/api.d.ts +7 -6
  50. package/src/component/messages.ts +106 -58
  51. package/src/component/schema.ts +9 -2
  52. package/src/mapping.ts +46 -11
  53. package/src/validators.test.ts +9 -0
  54. package/src/validators.ts +32 -0
@@ -11,26 +11,24 @@ import type {
11
11
  ToolChoice,
12
12
  ToolExecutionOptions,
13
13
  ToolSet,
14
- Message as UIMessage,
15
14
  } from "ai";
16
- import {
17
- convertToCoreMessages,
18
- coreMessageSchema,
19
- generateObject,
20
- generateText,
21
- streamObject,
22
- streamText,
23
- } from "ai";
24
- import type { ZodType } from "zod";
15
+ import { generateObject, generateText, streamObject, streamText } from "ai";
25
16
  import { api } from "../component/_generated/api";
26
- import { Message, MessageStatus, SearchOptions } from "../validators";
17
+ import {
18
+ SearchOptions,
19
+ vChatArgs,
20
+ vContextOptions,
21
+ vObjectArgs,
22
+ vStorageOptions,
23
+ } from "../validators";
27
24
  import { RunActionCtx, RunMutationCtx, RunQueryCtx, UseApi } from "./types";
28
25
  // TODO: is this the only dependency that needs helpers in client?
29
26
  import { assert } from "convex-helpers";
30
- import { convexToZod } from "convex-helpers/server/zod";
31
- import { GenericActionCtx, GenericDataModel } from "convex/server";
32
- import { Infer, Validator } from "convex/values";
27
+ import { ConvexToZod, convexToZod } from "convex-helpers/server/zod";
28
+ import { internalActionGeneric } from "convex/server";
29
+ import { Infer, v, Validator } from "convex/values";
33
30
  import {
31
+ promptOrMessagesToCoreMessages,
34
32
  serializeMessageWithId,
35
33
  serializeNewMessagesInStep,
36
34
  serializeStep,
@@ -38,15 +36,42 @@ import {
38
36
  import { DEFAULT_MESSAGE_RANGE, extractText } from "../shared";
39
37
 
40
38
  export type ContextOptions = {
41
- parentMessageId?: string;
42
- includeToolMessages?: boolean;
39
+ /**
40
+ * Whether to include tool messages in the context.
41
+ */
42
+ includeToolCalls?: boolean;
43
+ /**
44
+ * How many recent messages to include. These are added after the search
45
+ * messages, and do not count against the search limit.
46
+ */
43
47
  recentMessages?: number;
48
+ /**
49
+ * Options for searching messages.
50
+ */
44
51
  searchOptions?: {
52
+ /**
53
+ * The maximum number of messages to fetch.
54
+ */
45
55
  limit: number;
56
+ /**
57
+ * Whether to use text search to find messages.
58
+ */
46
59
  textSearch?: boolean;
60
+ /**
61
+ * Whether to use vector search to find messages.
62
+ */
47
63
  vectorSearch?: boolean;
48
- messageRange: { before: number; after: number };
64
+ /**
65
+ * Note, this is after the limit is applied.
66
+ * By default this will quadruple the number of messages fetched.
67
+ * (two before, and one after each message found in the search)
68
+ */
69
+ messageRange?: { before: number; after: number };
49
70
  };
71
+ /**
72
+ * Whether to search across other chats for relevant messages.
73
+ * By default, only the current chat is searched.
74
+ */
50
75
  searchOtherChats?: boolean;
51
76
  };
52
77
 
@@ -70,8 +95,12 @@ export class Agent<AgentTools extends ToolSet> {
70
95
  name?: string;
71
96
  chat: LanguageModelV1;
72
97
  textEmbedding?: EmbeddingModelV1<string>;
73
- defaultSystemPrompt?: string;
98
+ instructions?: string;
74
99
  tools?: AgentTools;
100
+ contextOptions?: ContextOptions;
101
+ // TODO: storageOptions?: StorageOptions;
102
+ maxSteps?: number;
103
+ // TODO: maxRetries?: number;
75
104
  }
76
105
  ) {}
77
106
 
@@ -85,7 +114,7 @@ export class Agent<AgentTools extends ToolSet> {
85
114
  * @param args The chat metadata.
86
115
  * @returns The chatId of the new chat and the chat object.
87
116
  */
88
- async startChat(
117
+ async createChat(
89
118
  ctx: RunActionCtx,
90
119
  args: {
91
120
  /**
@@ -121,7 +150,7 @@ export class Agent<AgentTools extends ToolSet> {
121
150
  * @param args The chat metadata.
122
151
  * @returns The chatId of the new chat.
123
152
  */
124
- async startChat(
153
+ async createChat(
125
154
  ctx: RunMutationCtx,
126
155
  args: {
127
156
  userId?: string;
@@ -132,7 +161,7 @@ export class Agent<AgentTools extends ToolSet> {
132
161
  ): Promise<{
133
162
  chatId: string;
134
163
  }>;
135
- async startChat(
164
+ async createChat(
136
165
  ctx: RunActionCtx | RunMutationCtx,
137
166
  args: {
138
167
  userId: string;
@@ -145,7 +174,7 @@ export class Agent<AgentTools extends ToolSet> {
145
174
  chat?: Chat<AgentTools>;
146
175
  }> {
147
176
  const chatDoc = await ctx.runMutation(this.component.messages.createChat, {
148
- defaultSystemPrompt: this.options.defaultSystemPrompt,
177
+ defaultSystemPrompt: this.options.instructions,
149
178
  userId: args.userId,
150
179
  title: args.title,
151
180
  summary: args.summary,
@@ -171,6 +200,10 @@ export class Agent<AgentTools extends ToolSet> {
171
200
  userId,
172
201
  }: {
173
202
  chatId: string;
203
+ /**
204
+ * If supplied, the userId can be used to search across other chats for
205
+ * relevant messages from the same user as context for the LLM calls.
206
+ */
174
207
  userId?: string;
175
208
  }
176
209
  ): Promise<{
@@ -193,12 +226,14 @@ export class Agent<AgentTools extends ToolSet> {
193
226
  userId?: string;
194
227
  chatId?: string;
195
228
  messages: CoreMessage[];
229
+ parentMessageId?: string;
196
230
  } & ContextOptions
197
231
  ): Promise<CoreMessage[]> {
198
232
  assert(args.userId || args.chatId, "Specify userId or chatId");
199
233
  // Fetch the latest messages from the chat
200
234
  const contextMessages: CoreMessage[] = [];
201
- if (args.searchOptions?.textSearch || args.searchOptions?.vectorSearch) {
235
+ const opts = this.mergedContextOptions(args);
236
+ if (opts.searchOptions?.textSearch || opts.searchOptions?.vectorSearch) {
202
237
  if (!("runAction" in ctx)) {
203
238
  throw new Error("searchUserMessages only works in an action");
204
239
  }
@@ -208,7 +243,7 @@ export class Agent<AgentTools extends ToolSet> {
208
243
  userId: args.searchOtherChats ? args.userId : undefined,
209
244
  chatId: args.chatId,
210
245
  parentMessageId: args.parentMessageId,
211
- ...(await this.searchOptionsWithDefaults(args, args.messages)),
246
+ ...(await this.searchOptionsWithDefaults(opts, args.messages)),
212
247
  }
213
248
  );
214
249
  // TODO: track what messages we used for context
@@ -219,7 +254,7 @@ export class Agent<AgentTools extends ToolSet> {
219
254
  this.component.messages.getChatMessages,
220
255
  {
221
256
  chatId: args.chatId,
222
- isTool: args.includeToolMessages ?? false,
257
+ isTool: args.includeToolCalls ?? false,
223
258
  limit: args.recentMessages,
224
259
  parentMessageId: args.parentMessageId,
225
260
  order: "desc",
@@ -234,10 +269,12 @@ export class Agent<AgentTools extends ToolSet> {
234
269
  async saveMessages(
235
270
  ctx: RunMutationCtx,
236
271
  args: {
237
- chatId: string;
272
+ chatId?: string;
273
+ userId?: string;
238
274
  messages: CoreMessageMaybeWithId[];
239
275
  pending?: boolean;
240
276
  parentMessageId?: string;
277
+ failPendingSteps?: boolean;
241
278
  }
242
279
  ): Promise<{
243
280
  lastMessageId: string;
@@ -245,10 +282,11 @@ export class Agent<AgentTools extends ToolSet> {
245
282
  }> {
246
283
  const result = await ctx.runMutation(this.component.messages.addMessages, {
247
284
  chatId: args.chatId,
285
+ userId: args.userId,
248
286
  agentName: this.options.name,
249
287
  model: this.options.chat.modelId,
250
288
  messages: args.messages.map(serializeMessageWithId),
251
- failPendingSteps: true,
289
+ failPendingSteps: args.failPendingSteps ?? true,
252
290
  pending: args.pending ?? false,
253
291
  parentMessageId: args.parentMessageId,
254
292
  });
@@ -318,7 +356,7 @@ export class Agent<AgentTools extends ToolSet> {
318
356
  chatId,
319
357
  }: {
320
358
  userId?: string;
321
- chatId: string;
359
+ chatId?: string;
322
360
  },
323
361
  args: TextArgs<
324
362
  AgentTools,
@@ -338,23 +376,26 @@ export class Agent<AgentTools extends ToolSet> {
338
376
  });
339
377
  const { lastMessageId: messageId } = await this.saveMessages(ctx, {
340
378
  chatId,
379
+ userId,
341
380
  messages: args.saveAllInputMessages ? messages : messages.slice(-1),
342
381
  pending: true,
343
382
  parentMessageId: args.parentMessageId,
344
383
  });
345
- const defaults = this.options.tools;
346
- const tools = wrapTools(ctx, chatId, userId, defaults, args.tools) as TOOLS;
384
+ const toolCtx = { ...ctx, userId, chatId, messageId };
385
+ const tools = wrapTools(toolCtx, this.options.tools, args.tools) as TOOLS;
347
386
  try {
348
387
  const result = await generateText({
349
388
  model: this.options.chat,
350
389
  messages: [...contextMessages, ...messages],
351
- system: this.options.defaultSystemPrompt,
352
- tools,
390
+ system: this.options.instructions,
391
+ maxSteps: this.options.maxSteps,
353
392
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
354
393
  toolChoice: args.toolChoice as any,
355
394
  ...rest,
395
+ tools,
356
396
  onStepFinish: async (step) => {
357
397
  if (chatId && messageId && args.saveOutputMessages) {
398
+ console.log("onStepFinish", step);
358
399
  await this.saveStep(ctx, {
359
400
  chatId,
360
401
  messageId,
@@ -367,6 +408,7 @@ export class Agent<AgentTools extends ToolSet> {
367
408
  return { ...result, messageId };
368
409
  } catch (error) {
369
410
  if (chatId && messageId) {
411
+ console.error("RollbackMessage", messageId);
370
412
  await ctx.runMutation(this.component.messages.rollbackMessage, {
371
413
  messageId,
372
414
  error: (error as Error).message,
@@ -382,12 +424,12 @@ export class Agent<AgentTools extends ToolSet> {
382
424
  PARTIAL_OUTPUT = never,
383
425
  >(
384
426
  ctx: RunActionCtx,
385
- { userId, chatId }: { userId?: string; chatId: string },
386
- args: Partial<
427
+ { userId, chatId }: { userId?: string; chatId?: string },
428
+ args: TextArgs<
429
+ AgentTools,
430
+ TOOLS,
387
431
  Parameters<typeof streamText<TOOLS, OUTPUT, PARTIAL_OUTPUT>>[0]
388
- > &
389
- ContextOptions &
390
- StorageOptions
432
+ >
391
433
  ): Promise<
392
434
  StreamTextResult<TOOLS, PARTIAL_OUTPUT> & GenerationOutputMetadata
393
435
  > {
@@ -405,16 +447,17 @@ export class Agent<AgentTools extends ToolSet> {
405
447
  pending: true,
406
448
  parentMessageId: args.parentMessageId,
407
449
  });
408
- const defaults = this.options.tools;
409
- const tools = wrapTools(ctx, chatId, userId, defaults, args.tools) as TOOLS;
450
+ const toolCtx = { ...ctx, userId, chatId, messageId };
451
+ const tools = wrapTools(toolCtx, this.options.tools, args.tools) as TOOLS;
410
452
  const result = streamText({
411
453
  model: this.options.chat,
412
454
  messages: [...contextMessages, ...messages],
413
- system: this.options.defaultSystemPrompt,
414
- tools,
455
+ system: this.options.instructions,
456
+ maxSteps: this.options.maxSteps,
415
457
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
416
458
  toolChoice: args.toolChoice as any,
417
459
  ...rest,
460
+ tools,
418
461
  onChunk: async (chunk) => {
419
462
  console.log("onChunk", chunk);
420
463
  return args.onChunk?.(chunk);
@@ -453,10 +496,10 @@ export class Agent<AgentTools extends ToolSet> {
453
496
  // TODO: add the crazy number of overloads to get types through
454
497
  async generateObject<T>(
455
498
  ctx: RunActionCtx,
456
- { userId, chatId }: { userId?: string; chatId: string },
499
+ { userId, chatId }: { userId?: string; chatId?: string },
457
500
  args: Omit<Parameters<typeof generateObject>[0], "model"> & {
458
501
  model?: LanguageModelV1;
459
- } & ContextOptions &
502
+ } & { parentMessageId?: string } & ContextOptions &
460
503
  StorageOptions
461
504
  ): Promise<GenerateObjectResult<T> & GenerationOutputMetadata> {
462
505
  const { prompt, messages: raw, ...rest } = args;
@@ -482,10 +525,10 @@ export class Agent<AgentTools extends ToolSet> {
482
525
 
483
526
  async streamObject<T>(
484
527
  ctx: RunMutationCtx,
485
- { userId, chatId }: { userId?: string; chatId: string },
528
+ { userId, chatId }: { userId?: string; chatId?: string },
486
529
  args: Omit<Parameters<typeof streamObject<T>>[0], "model"> & {
487
530
  model?: LanguageModelV1;
488
- } & ContextOptions &
531
+ } & { parentMessageId?: string } & ContextOptions &
489
532
  StorageOptions
490
533
  ): Promise<
491
534
  StreamObjectResult<DeepPartial<T>, T, never> & GenerationOutputMetadata
@@ -518,27 +561,41 @@ export class Agent<AgentTools extends ToolSet> {
518
561
  return { ...result, messageId };
519
562
  }
520
563
 
564
+ mergedContextOptions(opts: ContextOptions): ContextOptions {
565
+ const searchOptions = {
566
+ ...this.options.contextOptions?.searchOptions,
567
+ ...opts.searchOptions,
568
+ };
569
+ return {
570
+ ...this.options.contextOptions,
571
+ ...opts,
572
+ searchOptions: searchOptions.limit
573
+ ? (searchOptions as SearchOptions)
574
+ : undefined,
575
+ };
576
+ }
577
+
521
578
  async searchOptionsWithDefaults(
522
- searchArgs: ContextOptions,
579
+ contextOptions: ContextOptions,
523
580
  messages: CoreMessage[]
524
581
  ): Promise<SearchOptions> {
525
582
  assert(
526
- searchArgs.searchOptions?.textSearch ||
527
- searchArgs.searchOptions?.vectorSearch,
583
+ contextOptions.searchOptions?.textSearch ||
584
+ contextOptions.searchOptions?.vectorSearch,
528
585
  "searchOptions is required"
529
586
  );
530
587
  assert(messages.length > 0, "Core messages cannot be empty");
531
588
  const text = extractText(messages.at(-1)!);
532
589
  const search: SearchOptions = {
533
- limit: searchArgs.searchOptions?.limit ?? 10,
590
+ limit: contextOptions.searchOptions?.limit ?? 10,
534
591
  messageRange: {
535
592
  ...DEFAULT_MESSAGE_RANGE,
536
- ...searchArgs.searchOptions?.messageRange,
593
+ ...contextOptions.searchOptions?.messageRange,
537
594
  },
538
595
  text: extractText(messages.at(-1)!),
539
596
  };
540
597
  if (
541
- searchArgs.searchOptions?.vectorSearch &&
598
+ contextOptions.searchOptions?.vectorSearch &&
542
599
  text &&
543
600
  this.options.textEmbedding
544
601
  ) {
@@ -552,71 +609,191 @@ export class Agent<AgentTools extends ToolSet> {
552
609
  return search;
553
610
  }
554
611
 
555
- async getChatMessages(
556
- ctx: RunQueryCtx,
557
- args: {
558
- chatId: string;
559
- limit?: number;
560
- statuses?: MessageStatus[];
561
- cursor?: string;
562
- includeToolMessages?: boolean;
563
- order?: "asc" | "desc";
564
- }
565
- ): Promise<{
566
- messages: (Message & { id: string })[];
567
- continueCursor?: string;
568
- isDone: boolean;
569
- }> {
570
- const messages = await ctx.runQuery(
571
- this.component.messages.getChatMessages,
572
- {
573
- chatId: args.chatId,
574
- limit: args.limit,
575
- statuses: args.statuses,
576
- cursor: args.cursor,
577
- isTool: args.includeToolMessages,
578
- order: args.order,
579
- }
580
- );
581
- return {
582
- messages: messages.messages
583
- .map((m) => m && { ...m.message, id: m._id })
584
- .filter((m): m is Message & { id: string } => m !== undefined),
585
- continueCursor: messages.continueCursor,
586
- isDone: messages.isDone,
587
- };
612
+ /**
613
+ *
614
+ */
615
+ asAction(spec: { contextOptions?: ContextOptions; maxSteps?: number }) {
616
+ return internalActionGeneric({
617
+ args: {
618
+ userId: v.optional(v.string()),
619
+ chatId: v.optional(v.string()),
620
+ contextOptions: v.optional(vContextOptions),
621
+ storageOptions: v.optional(vStorageOptions),
622
+ maxRetries: v.optional(v.number()),
623
+
624
+ createChat: v.optional(
625
+ v.object({
626
+ userId: v.string(),
627
+ parentChatIds: v.optional(v.array(v.string())),
628
+ title: v.optional(v.string()),
629
+ summary: v.optional(v.string()),
630
+ })
631
+ ),
632
+ continueChat: v.optional(
633
+ v.object({
634
+ chatId: v.string(),
635
+ userId: v.optional(v.string()),
636
+ })
637
+ ),
638
+ generateText: v.optional(vChatArgs),
639
+ streamText: v.optional(vChatArgs),
640
+ generateObject: v.optional(vObjectArgs),
641
+ streamObject: v.optional(
642
+ v.object({ ...vObjectArgs.fields, schema: v.any() })
643
+ ),
644
+ },
645
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
646
+ handler: async (ctx, args): Promise<any> => {
647
+ const contextOptions =
648
+ spec.contextOptions && this.mergedContextOptions(spec.contextOptions);
649
+ const maxSteps = spec.maxSteps ?? this.options.maxSteps;
650
+ const maxRetries = args.maxRetries;
651
+ const commonArgs = {
652
+ userId: args.userId,
653
+ chatId: args.chatId,
654
+ ...contextOptions,
655
+ ...args.storageOptions,
656
+ };
657
+ if (args.createChat) {
658
+ return this.createChat(ctx, {
659
+ userId: args.createChat.userId,
660
+ parentChatIds: args.createChat.parentChatIds,
661
+ title: args.createChat.title,
662
+ summary: args.createChat.summary,
663
+ });
664
+ } else if (args.continueChat) {
665
+ return this.continueChat(ctx, {
666
+ chatId: args.continueChat.chatId,
667
+ userId: args.continueChat.userId,
668
+ });
669
+ } else if (args.generateText) {
670
+ return this.generateText(ctx, commonArgs, {
671
+ ...args.generateText,
672
+ maxSteps: args.generateText.maxSteps ?? maxSteps,
673
+ maxRetries,
674
+ });
675
+ } else if (args.streamText) {
676
+ return this.streamText(ctx, commonArgs, {
677
+ ...args.streamText,
678
+ maxSteps: args.streamText.maxSteps ?? maxSteps,
679
+ maxRetries,
680
+ });
681
+ } else if (args.generateObject) {
682
+ return this.generateObject(ctx, commonArgs, {
683
+ ...args.generateObject,
684
+ output: args.generateObject.output ?? "string",
685
+ maxRetries,
686
+ });
687
+ } else if (args.streamObject) {
688
+ return this.streamObject(ctx, commonArgs, {
689
+ ...args.streamObject,
690
+ output: args.streamObject.output ?? "string",
691
+ maxRetries,
692
+ });
693
+ }
694
+ },
695
+ });
588
696
  }
589
- }
590
697
 
591
- export function promptOrMessagesToCoreMessages(args: {
592
- system?: string;
593
- prompt?: string;
594
- messages?: CoreMessage[] | Omit<UIMessage, "id">[];
595
- }): CoreMessage[] {
596
- const messages: CoreMessage[] = [];
597
- if (args.system) {
598
- messages.push({ role: "system", content: args.system });
698
+ /**
699
+ * Create a tool that can call this agent.
700
+ * @param spec The specification for the arguments to this agent.
701
+ * They will be encoded as JSON and passed to the agent.
702
+ * @returns The agent as a tool that can be passed to other agents.
703
+ */
704
+ asTool(spec: {
705
+ description: string;
706
+ args: Validator<unknown, "required", string>;
707
+ contextOptions?: ContextOptions;
708
+ maxSteps?: number;
709
+ }) {
710
+ return createTool({
711
+ ...spec,
712
+ handler: async (ctx, args) => {
713
+ const maxSteps = spec.maxSteps ?? this.options.maxSteps;
714
+ const contextOptions =
715
+ spec.contextOptions && this.mergedContextOptions(spec.contextOptions);
716
+ const value = await this.generateText(
717
+ ctx,
718
+ { userId: ctx.userId, chatId: ctx.chatId },
719
+ {
720
+ prompt: JSON.stringify(args),
721
+ parentMessageId: ctx.messageId,
722
+ maxSteps,
723
+ ...contextOptions,
724
+ }
725
+ );
726
+ return value.text;
727
+ },
728
+ });
599
729
  }
600
- if (!args.messages) {
601
- assert(args.prompt, "messages or prompt is required");
602
- messages.push({ role: "user", content: args.prompt });
603
- } else if (
604
- args.messages.some(
605
- (m) =>
606
- typeof m === "object" &&
607
- m !== null &&
608
- (m.role === "data" || // UI-only role
609
- "toolInvocations" in m || // UI-specific field
610
- "parts" in m || // UI-specific field
611
- "experimental_attachments" in m)
612
- )
613
- ) {
614
- messages.push(...convertToCoreMessages(args.messages as UIMessage[]));
615
- } else {
616
- messages.push(...coreMessageSchema.array().parse(args.messages));
730
+ }
731
+
732
+ export type ToolCtx = RunActionCtx & {
733
+ userId?: string;
734
+ chatId?: string;
735
+ messageId?: string;
736
+ };
737
+
738
+ /**
739
+ * This is a wrapper around the ai.tool function that adds support for
740
+ * userId and chatId to the tool, if they're called within a chat from an agent.
741
+ * @param tool The AI tool. See https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling
742
+ * @returns The same tool, but with userId and chatId args support added.
743
+ */
744
+ export function createTool<
745
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
746
+ V extends Validator<any, any, any>,
747
+ RESULT,
748
+ >(convexTool: {
749
+ args: V;
750
+ description?: string;
751
+ handler: (
752
+ ctx: ToolCtx,
753
+ args: Infer<V>,
754
+ options: ToolExecutionOptions
755
+ ) => PromiseLike<RESULT>;
756
+ ctx?: ToolCtx;
757
+ }): Tool<ConvexToZod<V>, RESULT> {
758
+ const tool = {
759
+ __acceptsCtx: true,
760
+ ctx: convexTool.ctx,
761
+ description: convexTool.description,
762
+ parameters: convexToZod(convexTool.args),
763
+ async execute(args: Infer<V>, options: ToolExecutionOptions) {
764
+ if (!this.ctx) {
765
+ throw new Error(
766
+ "To use a Convex tool, you must either provide the ctx" +
767
+ " at definition time (dynamically in an action), or use the Agent to" +
768
+ " call it (which injects the ctx, userId and chatId)"
769
+ );
770
+ }
771
+ return convexTool.handler(this.ctx, args, options);
772
+ },
773
+ };
774
+ return tool;
775
+ }
776
+
777
+ function wrapTools(
778
+ ctx: ToolCtx,
779
+ ...toolSets: (ToolSet | undefined)[]
780
+ ): ToolSet {
781
+ const output = {} as ToolSet;
782
+ for (const toolSet of toolSets) {
783
+ if (!toolSet) {
784
+ continue;
785
+ }
786
+ for (const [name, tool] of Object.entries(toolSet)) {
787
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
788
+ if (!(tool as any).__acceptsCtx) {
789
+ output[name] = tool;
790
+ } else {
791
+ const out = { ...tool, ctx };
792
+ output[name] = out;
793
+ }
794
+ }
617
795
  }
618
- assert(messages.length > 0, "Messages must contain at least one message");
619
- return messages;
796
+ return output;
620
797
  }
621
798
 
622
799
  type TextArgs<
@@ -629,6 +806,7 @@ type TextArgs<
629
806
  },
630
807
  > = Omit<T, "toolChoice" | "tools" | "model"> & {
631
808
  model?: LanguageModelV1;
809
+ parentMessageId?: string;
632
810
  } & {
633
811
  tools?: TOOLS;
634
812
  toolChoice?: ToolChoice<{ [key in keyof TOOLS | keyof AgentTools]: unknown }>;
@@ -675,79 +853,3 @@ interface Chat<AgentTools extends ToolSet> {
675
853
  StreamObjectResult<DeepPartial<T>, T, never> & GenerationOutputMetadata
676
854
  >;
677
855
  }
678
-
679
- // type ToolParameters = ZodTypeAny | Schema<unknown>; // TODO: support convex validator
680
- // type inferParameters<PARAMETERS extends ToolParameters> =
681
- // PARAMETERS extends Schema<unknown>
682
- // ? PARAMETERS["_type"]
683
- // : PARAMETERS extends z.ZodTypeAny
684
- // ? z.infer<PARAMETERS>
685
- // : never;
686
- /**
687
- * This is a wrapper around the ai.tool function that adds support for
688
- * userId and chatId to the tool, if they're called within a chat from an agent.
689
- * @param tool The AI tool. See https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling
690
- * @returns The same tool, but with userId and chatId args support added.
691
- */
692
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
693
- export function tool<V extends Validator<any, any, any>, RESULT>(convexTool: {
694
- args: V;
695
- description?: string;
696
- handler: (
697
- ctx: GenericActionCtx<GenericDataModel> & {
698
- userId?: string;
699
- chatId?: string;
700
- },
701
- args: Infer<V>,
702
- options: ToolExecutionOptions
703
- ) => PromiseLike<RESULT>;
704
- ctx?: GenericActionCtx<GenericDataModel> & {
705
- userId?: string;
706
- chatId?: string;
707
- };
708
- }): Tool<ZodType<Infer<V>>, RESULT> {
709
- const tool = {
710
- __acceptUserIdAndChatId: true,
711
- description: convexTool.description,
712
- parameters: convexToZod(convexTool.args),
713
- execute: async (args: Infer<V>, options: ToolExecutionOptions) => {
714
- if (!convexTool.ctx) {
715
- throw new Error(
716
- "To use a Convex tool, you must either provide the ctx" +
717
- " at definition time (dynamically in an action), or use the Agent to" +
718
- " call it (which injects the ctx, userId and chatId)"
719
- );
720
- }
721
- return convexTool.handler(convexTool.ctx, args, options);
722
- },
723
- };
724
- return tool;
725
- }
726
-
727
- export function wrapTools(
728
- actionCtx: RunActionCtx,
729
- chatId: string,
730
- userId?: string,
731
- ...toolSets: (ToolSet | undefined)[]
732
- ): ToolSet {
733
- const ctx = { ...actionCtx, chatId, userId };
734
- const output = {} as ToolSet;
735
- for (const toolSet of toolSets) {
736
- if (!toolSet) {
737
- continue;
738
- }
739
- for (const [name, tool] of Object.entries(toolSet)) {
740
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
741
- if (!(tool as any).__acceptUserIdAndChatId) {
742
- output[name] = tool;
743
- } else {
744
- const out = { ...tool, ctx };
745
- output[name] = out;
746
- }
747
- }
748
- }
749
- return output;
750
- }
751
- // export function convexValidatorSchema<T>(validator: Validator<unknown>) {
752
- // return ai.jsonSchema(convexToJsonSchema(validator));
753
- // }