@convex-dev/agent 0.0.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +55 -0
  3. package/dist/commonjs/client/index.d.ts +198 -0
  4. package/dist/commonjs/client/index.d.ts.map +1 -0
  5. package/dist/commonjs/client/index.js +365 -0
  6. package/dist/commonjs/client/index.js.map +1 -0
  7. package/dist/commonjs/client/types.d.ts +21 -0
  8. package/dist/commonjs/client/types.d.ts.map +1 -0
  9. package/dist/commonjs/client/types.js +2 -0
  10. package/dist/commonjs/client/types.js.map +1 -0
  11. package/dist/commonjs/component/_generated/api.d.ts +12 -0
  12. package/dist/commonjs/component/_generated/api.d.ts.map +1 -0
  13. package/dist/commonjs/component/_generated/api.js +22 -0
  14. package/dist/commonjs/component/_generated/api.js.map +1 -0
  15. package/dist/commonjs/component/_generated/server.d.ts +64 -0
  16. package/dist/commonjs/component/_generated/server.d.ts.map +1 -0
  17. package/dist/commonjs/component/_generated/server.js +74 -0
  18. package/dist/commonjs/component/_generated/server.js.map +1 -0
  19. package/dist/commonjs/component/convex.config.d.ts +3 -0
  20. package/dist/commonjs/component/convex.config.d.ts.map +1 -0
  21. package/dist/commonjs/component/convex.config.js +3 -0
  22. package/dist/commonjs/component/convex.config.js.map +1 -0
  23. package/dist/commonjs/component/lib.d.ts +2 -0
  24. package/dist/commonjs/component/lib.d.ts.map +1 -0
  25. package/dist/commonjs/component/lib.js +2 -0
  26. package/dist/commonjs/component/lib.js.map +1 -0
  27. package/dist/commonjs/component/messages.d.ts +1913 -0
  28. package/dist/commonjs/component/messages.d.ts.map +1 -0
  29. package/dist/commonjs/component/messages.js +787 -0
  30. package/dist/commonjs/component/messages.js.map +1 -0
  31. package/dist/commonjs/component/schema.d.ts +5496 -0
  32. package/dist/commonjs/component/schema.d.ts.map +1 -0
  33. package/dist/commonjs/component/schema.js +97 -0
  34. package/dist/commonjs/component/schema.js.map +1 -0
  35. package/dist/commonjs/component/vector/tables.d.ts +40 -0
  36. package/dist/commonjs/component/vector/tables.d.ts.map +1 -0
  37. package/dist/commonjs/component/vector/tables.js +46 -0
  38. package/dist/commonjs/component/vector/tables.js.map +1 -0
  39. package/dist/commonjs/mapping.d.ts +26 -0
  40. package/dist/commonjs/mapping.d.ts.map +1 -0
  41. package/dist/commonjs/mapping.js +101 -0
  42. package/dist/commonjs/mapping.js.map +1 -0
  43. package/dist/commonjs/package.json +3 -0
  44. package/dist/commonjs/react/index.d.ts +2 -0
  45. package/dist/commonjs/react/index.d.ts.map +1 -0
  46. package/dist/commonjs/react/index.js +8 -0
  47. package/dist/commonjs/react/index.js.map +1 -0
  48. package/dist/commonjs/shared.d.ts +9 -0
  49. package/dist/commonjs/shared.d.ts.map +1 -0
  50. package/dist/commonjs/shared.js +29 -0
  51. package/dist/commonjs/shared.js.map +1 -0
  52. package/dist/commonjs/validators.d.ts +6177 -0
  53. package/dist/commonjs/validators.d.ts.map +1 -0
  54. package/dist/commonjs/validators.js +171 -0
  55. package/dist/commonjs/validators.js.map +1 -0
  56. package/dist/esm/client/index.d.ts +198 -0
  57. package/dist/esm/client/index.d.ts.map +1 -0
  58. package/dist/esm/client/index.js +365 -0
  59. package/dist/esm/client/index.js.map +1 -0
  60. package/dist/esm/client/types.d.ts +21 -0
  61. package/dist/esm/client/types.d.ts.map +1 -0
  62. package/dist/esm/client/types.js +2 -0
  63. package/dist/esm/client/types.js.map +1 -0
  64. package/dist/esm/component/_generated/api.d.ts +12 -0
  65. package/dist/esm/component/_generated/api.d.ts.map +1 -0
  66. package/dist/esm/component/_generated/api.js +22 -0
  67. package/dist/esm/component/_generated/api.js.map +1 -0
  68. package/dist/esm/component/_generated/server.d.ts +64 -0
  69. package/dist/esm/component/_generated/server.d.ts.map +1 -0
  70. package/dist/esm/component/_generated/server.js +74 -0
  71. package/dist/esm/component/_generated/server.js.map +1 -0
  72. package/dist/esm/component/convex.config.d.ts +3 -0
  73. package/dist/esm/component/convex.config.d.ts.map +1 -0
  74. package/dist/esm/component/convex.config.js +3 -0
  75. package/dist/esm/component/convex.config.js.map +1 -0
  76. package/dist/esm/component/lib.d.ts +2 -0
  77. package/dist/esm/component/lib.d.ts.map +1 -0
  78. package/dist/esm/component/lib.js +2 -0
  79. package/dist/esm/component/lib.js.map +1 -0
  80. package/dist/esm/component/messages.d.ts +1913 -0
  81. package/dist/esm/component/messages.d.ts.map +1 -0
  82. package/dist/esm/component/messages.js +787 -0
  83. package/dist/esm/component/messages.js.map +1 -0
  84. package/dist/esm/component/schema.d.ts +5496 -0
  85. package/dist/esm/component/schema.d.ts.map +1 -0
  86. package/dist/esm/component/schema.js +97 -0
  87. package/dist/esm/component/schema.js.map +1 -0
  88. package/dist/esm/component/vector/tables.d.ts +40 -0
  89. package/dist/esm/component/vector/tables.d.ts.map +1 -0
  90. package/dist/esm/component/vector/tables.js +46 -0
  91. package/dist/esm/component/vector/tables.js.map +1 -0
  92. package/dist/esm/mapping.d.ts +26 -0
  93. package/dist/esm/mapping.d.ts.map +1 -0
  94. package/dist/esm/mapping.js +101 -0
  95. package/dist/esm/mapping.js.map +1 -0
  96. package/dist/esm/package.json +3 -0
  97. package/dist/esm/react/index.d.ts +2 -0
  98. package/dist/esm/react/index.d.ts.map +1 -0
  99. package/dist/esm/react/index.js +8 -0
  100. package/dist/esm/react/index.js.map +1 -0
  101. package/dist/esm/shared.d.ts +9 -0
  102. package/dist/esm/shared.d.ts.map +1 -0
  103. package/dist/esm/shared.js +29 -0
  104. package/dist/esm/shared.js.map +1 -0
  105. package/dist/esm/validators.d.ts +6177 -0
  106. package/dist/esm/validators.d.ts.map +1 -0
  107. package/dist/esm/validators.js +171 -0
  108. package/dist/esm/validators.js.map +1 -0
  109. package/package.json +91 -0
  110. package/react/package.json +5 -0
  111. package/src/client/index.ts +659 -0
  112. package/src/client/types.ts +54 -0
  113. package/src/component/_generated/api.d.ts +1497 -0
  114. package/src/component/_generated/api.js +23 -0
  115. package/src/component/_generated/dataModel.d.ts +60 -0
  116. package/src/component/_generated/server.d.ts +149 -0
  117. package/src/component/_generated/server.js +90 -0
  118. package/src/component/convex.config.ts +3 -0
  119. package/src/component/lib.test.ts +13 -0
  120. package/src/component/lib.ts +2 -0
  121. package/src/component/messages.ts +959 -0
  122. package/src/component/schema.ts +101 -0
  123. package/src/component/setup.test.ts +5 -0
  124. package/src/component/vector/tables.ts +92 -0
  125. package/src/mapping.ts +160 -0
  126. package/src/react/index.ts +8 -0
  127. package/src/shared.ts +35 -0
  128. package/src/validators.test.ts +101 -0
  129. package/src/validators.ts +258 -0
@@ -0,0 +1,659 @@
1
+ import { api } from "../component/_generated/api";
2
+ import { RunActionCtx, RunMutationCtx, RunQueryCtx, UseApi } from "./types";
3
+ import type { EmbeddingModelV1, LanguageModelV1 } from "@ai-sdk/provider";
4
+ import { Message, MessageStatus, SearchOptions, Step } from "../validators";
5
+ import type {
6
+ StreamTextResult,
7
+ Tool,
8
+ ToolSet,
9
+ StepResult,
10
+ Message as UIMessage,
11
+ CoreMessage,
12
+ GenerateObjectResult,
13
+ StreamObjectResult,
14
+ DeepPartial,
15
+ GenerateTextResult,
16
+ } from "ai";
17
+ import {
18
+ generateText,
19
+ generateObject,
20
+ streamText,
21
+ streamObject,
22
+ convertToCoreMessages,
23
+ coreMessageSchema,
24
+ } from "ai";
25
+ // TODO: is this the only dependency that needs helpers in client?
26
+ import { assert } from "convex-helpers";
27
+ import { DEFAULT_MESSAGE_RANGE, extractText } from "../shared";
28
+ import {
29
+ serializeMessage,
30
+ serializeMessageWithId,
31
+ serializeNewMessagesInStep,
32
+ serializeStep,
33
+ } from "../mapping";
34
+
35
+ export type ContextOptions = {
36
+ includeToolMessages?: boolean;
37
+ recentMessages?: number;
38
+ searchOptions?: {
39
+ limit: number;
40
+ textSearch?: boolean;
41
+ vectorSearch?: boolean;
42
+ messageRange: { before: number; after: number };
43
+ };
44
+ searchOtherChats?: boolean;
45
+ };
46
+
47
+ type CoreMessageMaybeWithId = CoreMessage & { id?: string | undefined };
48
+
49
+ export class Agent {
50
+ constructor(
51
+ public component: UseApi<typeof api>,
52
+ public options: {
53
+ name?: string;
54
+ chat: LanguageModelV1;
55
+ textEmbedding?: EmbeddingModelV1<string>;
56
+ defaultSystemPrompt?: string;
57
+ tools?: Record<string, Tool>;
58
+ }
59
+ ) {}
60
+
61
+ /**
62
+ * Start a new chat with the agent. This will have a fresh history, though if
63
+ * you pass in a userId you can have it search across other chats for relevant
64
+ * messages as context for the LLM calls.
65
+ * @param ctx The context of the Convex function. From an action, you can chat
66
+ * with the agent. From a mutation, you can start a chat and save the chatId
67
+ * to pass to continueChat later.
68
+ * @param args The chat metadata.
69
+ * @returns The chatId of the new chat and the chat object.
70
+ */
71
+ async startChat(
72
+ ctx: RunActionCtx,
73
+ args: {
74
+ /**
75
+ * The userId to associate with the chat. If not provided, the chat will be
76
+ * anonymous.
77
+ */
78
+ userId?: string;
79
+ /**
80
+ * The parent chatIds to merge with.
81
+ * If the chat is a continuation of one or many previous chats,
82
+ * you can pass in the chatIds of the parent chats to merge the histories.
83
+ */
84
+ parentChatIds?: string[];
85
+ /**
86
+ * The title of the chat. Not currently used.
87
+ */
88
+ title?: string;
89
+ /**
90
+ * The summary of the chat. Not currently used.
91
+ */
92
+ summary?: string;
93
+ }
94
+ ): Promise<{
95
+ chatId: string;
96
+ chat: Chat;
97
+ }>;
98
+ /**
99
+ * Start a new chat with the agent. This will have a fresh history, though if
100
+ * you pass in a userId you can have it search across other chats for relevant
101
+ * messages as context for the LLM calls.
102
+ * @param ctx The context of the Convex function. From a mutation, you can
103
+ * start a chat and save the chatId to pass to continueChat later.
104
+ * @param args The chat metadata.
105
+ * @returns The chatId of the new chat.
106
+ */
107
+ async startChat(
108
+ ctx: RunMutationCtx,
109
+ args: {
110
+ userId?: string;
111
+ parentChatIds?: string[];
112
+ title?: string;
113
+ summary?: string;
114
+ }
115
+ ): Promise<{
116
+ chatId: string;
117
+ }>;
118
+ async startChat(
119
+ ctx: RunActionCtx | RunMutationCtx,
120
+ args: {
121
+ userId: string;
122
+ parentChatIds?: string[];
123
+ title?: string;
124
+ summary?: string;
125
+ }
126
+ ): Promise<{
127
+ chatId: string;
128
+ chat?: Chat;
129
+ }> {
130
+ const chatDoc = await ctx.runMutation(this.component.messages.createChat, {
131
+ defaultSystemPrompt: this.options.defaultSystemPrompt,
132
+ userId: args.userId,
133
+ title: args.title,
134
+ summary: args.summary,
135
+ parentChatIds: args.parentChatIds,
136
+ });
137
+ if (!("runAction" in ctx)) {
138
+ return { chatId: chatDoc._id };
139
+ }
140
+ const { chat } = await this.continueChat(ctx, {
141
+ chatId: chatDoc._id,
142
+ userId: args.userId,
143
+ });
144
+ return {
145
+ chatId: chatDoc._id,
146
+ chat,
147
+ };
148
+ }
149
+
150
+ async continueChat(
151
+ ctx: RunActionCtx,
152
+ {
153
+ chatId,
154
+ userId,
155
+ }: {
156
+ chatId: string;
157
+ userId?: string;
158
+ }
159
+ ): Promise<{
160
+ chat: Chat;
161
+ }> {
162
+ // return this.component.continueChat(ctx, args);
163
+ return {
164
+ chat: {
165
+ generateText: this.generateText.bind(this, ctx, { userId, chatId }),
166
+ streamText: this.streamText.bind(this, ctx, { userId, chatId }),
167
+ generateObject: this.generateObject.bind(this, ctx, { userId, chatId }),
168
+ streamObject: this.streamObject.bind(this, ctx, { userId, chatId }),
169
+ } as Chat,
170
+ };
171
+ }
172
+
173
+ async fetchContextMessages(
174
+ ctx: RunQueryCtx | RunActionCtx,
175
+ args: {
176
+ userId?: string;
177
+ chatId?: string;
178
+ messages: CoreMessage[];
179
+ } & ContextOptions
180
+ ): Promise<CoreMessage[]> {
181
+ assert(args.userId || args.chatId, "Specify userId or chatId");
182
+ // Fetch the latest messages from the chat
183
+ const contextMessages: CoreMessage[] = [];
184
+ if (args.searchOptions?.textSearch || args.searchOptions?.vectorSearch) {
185
+ if (!("runAction" in ctx)) {
186
+ throw new Error("searchUserMessages only works in an action");
187
+ }
188
+ const searchMessages = await ctx.runAction(
189
+ this.component.messages.searchMessages,
190
+ {
191
+ userId: args.searchOtherChats ? args.userId : undefined,
192
+ chatId: args.chatId,
193
+ ...(await this.searchWithDefaults(args, args.messages)),
194
+ }
195
+ );
196
+ contextMessages.push(...searchMessages.map((m) => m.message!));
197
+ }
198
+ if (args.chatId) {
199
+ const { messages } = await ctx.runQuery(
200
+ this.component.messages.getChatMessages,
201
+ {
202
+ chatId: args.chatId,
203
+ isTool: args.includeToolMessages ?? false,
204
+ limit: args.recentMessages,
205
+ order: "desc",
206
+ statuses: ["success"],
207
+ }
208
+ );
209
+ contextMessages.push(...messages.map((m) => m.message!));
210
+ }
211
+ return contextMessages;
212
+ }
213
+
214
+ async saveMessages(
215
+ ctx: RunMutationCtx,
216
+ args: {
217
+ chatId: string;
218
+ messages: CoreMessageMaybeWithId[];
219
+ steps?: StepResult<ToolSet>[];
220
+ pending?: boolean;
221
+ }
222
+ ): Promise<{
223
+ lastMessageId: string;
224
+ }>;
225
+ async saveMessages(
226
+ ctx: RunMutationCtx,
227
+ args: {
228
+ chatId: string;
229
+ messages: CoreMessageMaybeWithId[];
230
+ steps?: StepResult<ToolSet>[];
231
+ pending?: boolean;
232
+ }
233
+ ): Promise<{
234
+ lastMessageId?: string;
235
+ }> {
236
+ const result = await ctx.runMutation(this.component.messages.addMessages, {
237
+ chatId: args.chatId,
238
+ agentName: this.options.name,
239
+ model: this.options.chat.modelId,
240
+ messages: args.messages.map(serializeMessageWithId),
241
+ failPendingSteps: true,
242
+ pending: args.pending ?? false,
243
+ });
244
+ return {
245
+ lastMessageId: result.messages.at(-1)?._id,
246
+ };
247
+ }
248
+
249
+ async replaceMessages(
250
+ ctx: RunMutationCtx,
251
+ args: { chatId: string; messages: Message[] }
252
+ ): Promise<void> {
253
+ throw new Error("Not implemented");
254
+ }
255
+
256
+ async saveStep<TOOLS extends ToolSet>(
257
+ ctx: RunMutationCtx,
258
+ args: { chatId: string; messageId: string; step: StepResult<TOOLS> }
259
+ ): Promise<void> {
260
+ const step = serializeStep(args.step as StepResult<ToolSet>);
261
+ const messages = serializeNewMessagesInStep(args.step);
262
+ await ctx.runMutation(this.component.messages.addSteps, {
263
+ chatId: args.chatId,
264
+ messageId: args.messageId,
265
+ steps: [{ step, messages: messages }],
266
+ failPendingSteps: false,
267
+ });
268
+ }
269
+
270
+ async completeMessage<TOOLS extends ToolSet>(
271
+ ctx: RunMutationCtx,
272
+ args: {
273
+ chatId: string;
274
+ messageId: string;
275
+ result:
276
+ | { kind: "error"; error: string }
277
+ | {
278
+ kind: "success";
279
+ value: { steps: StepResult<TOOLS>[] };
280
+ };
281
+ }
282
+ ): Promise<void> {
283
+ const result = args.result;
284
+ if (result.kind === "success") {
285
+ await ctx.runMutation(this.component.messages.commitMessage, {
286
+ messageId: args.messageId,
287
+ });
288
+ } else {
289
+ await ctx.runMutation(this.component.messages.addSteps, {
290
+ chatId: args.chatId,
291
+ messageId: args.messageId,
292
+ steps: [],
293
+ failPendingSteps: true,
294
+ });
295
+ }
296
+ }
297
+
298
+ /**
299
+ * This behaves like {@link generateText} except that it add context based on
300
+ * the userId and chatId. It saves the input and resulting messages to the
301
+ * chat, if specified.
302
+ * however. To do that, use {@link continueChat} or {@link saveMessages}.
303
+ * @param ctx The context of the agent.
304
+ * @param args The arguments to the generateText function.
305
+ * @returns The result of the generateText function.
306
+ */
307
+ async generateText<
308
+ TOOLS extends ToolSet,
309
+ OUTPUT = never,
310
+ OUTPUT_PARTIAL = never,
311
+ >(
312
+ ctx: RunActionCtx,
313
+ {
314
+ userId,
315
+ chatId,
316
+ }: {
317
+ userId?: string;
318
+ chatId?: string;
319
+ },
320
+ args: Partial<
321
+ Parameters<typeof generateText<TOOLS, OUTPUT, OUTPUT_PARTIAL>>[0]
322
+ > &
323
+ ContextOptions
324
+ ): Promise<GenerateTextResult<TOOLS, OUTPUT>> {
325
+ const { prompt, messages: raw, ...rest } = args;
326
+ const messages = promptOrMessagesToCoreMessages({ prompt, messages: raw });
327
+ const contextMessages = await this.fetchContextMessages(ctx, {
328
+ ...args,
329
+ userId,
330
+ chatId,
331
+ messages,
332
+ });
333
+ let messageId: string | undefined;
334
+ if (chatId) {
335
+ const { lastMessageId } = await this.saveMessages(ctx, {
336
+ chatId,
337
+ // TODO: only save the last message unless explicitly told to save all
338
+ messages,
339
+ pending: true,
340
+ });
341
+ messageId = lastMessageId;
342
+ }
343
+ try {
344
+ const result = await generateText({
345
+ model: this.options.chat,
346
+ messages: [...contextMessages, ...messages],
347
+ system: this.options.defaultSystemPrompt,
348
+ ...rest,
349
+ onStepFinish: async (step) => {
350
+ if (chatId && messageId) {
351
+ await this.saveStep(ctx, {
352
+ chatId,
353
+ messageId,
354
+ step,
355
+ });
356
+ }
357
+ return args.onStepFinish?.(step);
358
+ },
359
+ });
360
+ return result;
361
+ } catch (error) {
362
+ if (chatId && messageId) {
363
+ await ctx.runMutation(this.component.messages.rollbackMessage, {
364
+ messageId,
365
+ error: (error as Error).message,
366
+ });
367
+ }
368
+ throw error;
369
+ }
370
+ }
371
+
372
+ async streamText<
373
+ TOOLS extends ToolSet,
374
+ OUTPUT = never,
375
+ PARTIAL_OUTPUT = never,
376
+ >(
377
+ ctx: RunMutationCtx,
378
+ { userId, chatId }: { userId?: string; chatId?: string },
379
+ args: Partial<
380
+ Parameters<typeof streamText<TOOLS, OUTPUT, PARTIAL_OUTPUT>>[0]
381
+ > &
382
+ ContextOptions
383
+ ): Promise<StreamTextResult<TOOLS, PARTIAL_OUTPUT>> {
384
+ const { prompt, messages: raw, ...rest } = args;
385
+ const messages = promptOrMessagesToCoreMessages({ prompt, messages: raw });
386
+ const contextMessages = await this.fetchContextMessages(ctx, {
387
+ ...args,
388
+ userId,
389
+ chatId,
390
+ messages,
391
+ });
392
+ let messageId: string | undefined;
393
+ if (chatId) {
394
+ const { lastMessageId } = await this.saveMessages(ctx, {
395
+ chatId,
396
+ // TODO: only save the last message unless explicitly told to save all
397
+ messages,
398
+ pending: true,
399
+ });
400
+ messageId = lastMessageId;
401
+ }
402
+ return streamText({
403
+ model: this.options.chat,
404
+ messages: [...contextMessages, ...messages],
405
+ system: this.options.defaultSystemPrompt,
406
+ ...rest,
407
+ onChunk: async (chunk) => {
408
+ console.log("onChunk", chunk);
409
+ return args.onChunk?.(chunk);
410
+ },
411
+ onError: async (error) => {
412
+ console.error("onError", error);
413
+ if (chatId && messageId) {
414
+ await ctx.runMutation(this.component.messages.rollbackMessage, {
415
+ messageId,
416
+ error: (error.error as Error).message,
417
+ });
418
+ }
419
+ return args.onError?.(error);
420
+ },
421
+ onFinish: async (result) => {
422
+ result.response.messages.forEach((message) => {
423
+ console.log("onFinish", message);
424
+ });
425
+ return args.onFinish?.(result);
426
+ },
427
+ onStepFinish: async (step) => {
428
+ console.log("onStepFinish", step);
429
+ if (chatId && messageId) {
430
+ await this.saveStep(ctx, {
431
+ chatId,
432
+ messageId,
433
+ step,
434
+ });
435
+ }
436
+ return args.onStepFinish?.(step);
437
+ },
438
+ });
439
+ }
440
+
441
+ // TODO: not sure why it needs to extend string
442
+ async generateObject<OBJECT extends string>(
443
+ ctx: RunActionCtx,
444
+ { userId, chatId }: { userId?: string; chatId?: string },
445
+ args: Omit<Parameters<typeof generateObject<OBJECT>>[0], "model"> & {
446
+ model?: LanguageModelV1;
447
+ } & ContextOptions
448
+ ): Promise<GenerateObjectResult<OBJECT>> {
449
+ const { prompt, messages: raw, ...rest } = args;
450
+ const messages = promptOrMessagesToCoreMessages({ prompt, messages: raw });
451
+ const contextMessages = await this.fetchContextMessages(ctx, {
452
+ ...args,
453
+ userId,
454
+ chatId,
455
+ messages,
456
+ });
457
+ return generateObject({
458
+ model: this.options.chat,
459
+ messages: [...contextMessages, ...messages],
460
+ ...rest,
461
+ }) as Promise<GenerateObjectResult<OBJECT>>;
462
+ }
463
+
464
+ async streamObject<T>(
465
+ ctx: RunMutationCtx,
466
+ { userId, chatId }: { userId?: string; chatId?: string },
467
+ args: Omit<Parameters<typeof streamObject<T>>[0], "model"> & {
468
+ model?: LanguageModelV1;
469
+ } & ContextOptions
470
+ ) {
471
+ const { prompt, messages: raw, ...rest } = args;
472
+ const messages = promptOrMessagesToCoreMessages({ prompt, messages: raw });
473
+ const contextMessages = await this.fetchContextMessages(ctx, {
474
+ ...args,
475
+ userId,
476
+ chatId,
477
+ messages,
478
+ });
479
+ return streamObject<T>({
480
+ model: this.options.chat,
481
+ messages: [...contextMessages, ...messages],
482
+ ...rest,
483
+ onError: async (error) => {
484
+ console.error("onError", error);
485
+ return args.onError?.(error);
486
+ },
487
+ onFinish: async (result) => {
488
+ console.log("onFinish", result);
489
+ },
490
+ }) as StreamObjectResult<DeepPartial<T>, T, never>;
491
+ }
492
+
493
+ async searchWithDefaults(
494
+ searchArgs: ContextOptions,
495
+ messages: CoreMessage[]
496
+ ): Promise<SearchOptions> {
497
+ assert(
498
+ searchArgs.searchOptions?.textSearch ||
499
+ searchArgs.searchOptions?.vectorSearch,
500
+ "searchOptions is required"
501
+ );
502
+ assert(messages.length > 0, "Core messages cannot be empty");
503
+ const text = extractText(messages.at(-1)!);
504
+ const search: SearchOptions = {
505
+ limit: searchArgs.searchOptions?.limit ?? 10,
506
+ messageRange: {
507
+ ...DEFAULT_MESSAGE_RANGE,
508
+ ...searchArgs.searchOptions?.messageRange,
509
+ },
510
+ text: extractText(messages.at(-1)!),
511
+ };
512
+ if (
513
+ searchArgs.searchOptions?.vectorSearch &&
514
+ text &&
515
+ this.options.textEmbedding
516
+ ) {
517
+ search.vector = (
518
+ await this.options.textEmbedding.doEmbed({
519
+ values: [text],
520
+ })
521
+ ).embeddings[0];
522
+ search.vectorModel = this.options.textEmbedding.modelId;
523
+ }
524
+ return search;
525
+ }
526
+
527
+ async getChatMessages(
528
+ ctx: RunQueryCtx,
529
+ args: {
530
+ chatId: string;
531
+ limit?: number;
532
+ statuses?: MessageStatus[];
533
+ cursor?: string;
534
+ includeToolMessages?: boolean;
535
+ order?: "asc" | "desc";
536
+ }
537
+ ): Promise<{
538
+ messages: (Message & { id: string })[];
539
+ continueCursor?: string;
540
+ isDone: boolean;
541
+ }> {
542
+ const messages = await ctx.runQuery(
543
+ this.component.messages.getChatMessages,
544
+ {
545
+ chatId: args.chatId,
546
+ limit: args.limit,
547
+ statuses: args.statuses,
548
+ cursor: args.cursor,
549
+ isTool: args.includeToolMessages,
550
+ order: args.order,
551
+ }
552
+ );
553
+ return {
554
+ messages: messages.messages
555
+ .map((m) => m && { ...m.message, id: m._id })
556
+ .filter((m): m is Message & { id: string } => m !== undefined),
557
+ continueCursor: messages.continueCursor,
558
+ isDone: messages.isDone,
559
+ };
560
+ }
561
+
562
+ async getSteps(
563
+ ctx: RunQueryCtx,
564
+ args: { messageId: string }
565
+ ): Promise<{
566
+ steps: Step[];
567
+ }> {
568
+ return { steps: [] };
569
+ }
570
+ }
571
+
572
+ interface Chat {
573
+ generateText<TOOLS extends ToolSet, OUTPUT = never, OUTPUT_PARTIAL = never>(
574
+ args: Partial<
575
+ Parameters<typeof generateText<TOOLS, OUTPUT, OUTPUT_PARTIAL>>[0]
576
+ >
577
+ ): Promise<GenerateTextResult<TOOLS, OUTPUT>>;
578
+ streamText<TOOLS extends ToolSet, OUTPUT = never, PARTIAL_OUTPUT = never>(
579
+ args: Partial<
580
+ Parameters<typeof streamText<TOOLS, OUTPUT, PARTIAL_OUTPUT>>[0]
581
+ >
582
+ ): Promise<StreamTextResult<TOOLS, PARTIAL_OUTPUT>>;
583
+ generateObject<OBJECT extends string>(
584
+ args: Omit<Parameters<typeof generateObject<OBJECT>>[0], "model"> & {
585
+ model?: LanguageModelV1;
586
+ }
587
+ ): Promise<GenerateObjectResult<OBJECT>>;
588
+ streamObject<T>(
589
+ args: Omit<Parameters<typeof streamObject<T>>[0], "model"> & {
590
+ model?: LanguageModelV1;
591
+ }
592
+ ): Promise<StreamObjectResult<DeepPartial<T>, T, never>>;
593
+ }
594
+
595
+ // type ToolParameters = ZodTypeAny | Schema<unknown>; // TODO: support convex validator
596
+ // type inferParameters<PARAMETERS extends ToolParameters> =
597
+ // PARAMETERS extends Schema<unknown>
598
+ // ? PARAMETERS["_type"]
599
+ // : PARAMETERS extends z.ZodTypeAny
600
+ // ? z.infer<PARAMETERS>
601
+ // : never;
602
+ // /**
603
+ // * This is a wrapper around the ai.tool function that adds support for
604
+ // * userId and chatId to the tool, if they're called within a chat from an agent.
605
+ // * @param tool The AI tool. See https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling
606
+ // * @returns The same tool, but with userId and chatId args support added.
607
+ // */
608
+ // export function tool<PARAMETERS extends ToolParameters, RESULT>(
609
+ // tool: Tool<PARAMETERS, RESULT> & {
610
+ // execute: (
611
+ // args: inferParameters<PARAMETERS> & { userId?: string; chatId?: string },
612
+ // options: ToolExecutionOptions
613
+ // ) => PromiseLike<RESULT>;
614
+ // }
615
+ // ): Tool<PARAMETERS, RESULT> & {
616
+ // execute: (
617
+ // args: inferParameters<PARAMETERS>,
618
+ // options: ToolExecutionOptions
619
+ // ) => PromiseLike<RESULT>;
620
+ // } {
621
+ // // eslint-disable-next-line @typescript-eslint/no-explicit-any
622
+ // (tool as any).__acceptUserIdAndChatId = true;
623
+ // return tool;
624
+ // }
625
+
626
+ export function promptOrMessagesToCoreMessages(args: {
627
+ system?: string;
628
+ prompt?: string;
629
+ messages?: CoreMessage[] | Omit<UIMessage, "id">[];
630
+ }): CoreMessage[] {
631
+ const messages: CoreMessage[] = [];
632
+ if (args.system) {
633
+ messages.push({ role: "system", content: args.system });
634
+ }
635
+ if (!args.messages) {
636
+ assert(args.prompt, "messages or prompt is required");
637
+ messages.push({ role: "user", content: args.prompt });
638
+ } else if (
639
+ args.messages.some(
640
+ (m) =>
641
+ typeof m === "object" &&
642
+ m !== null &&
643
+ (m.role === "data" || // UI-only role
644
+ "toolInvocations" in m || // UI-specific field
645
+ "parts" in m || // UI-specific field
646
+ "experimental_attachments" in m)
647
+ )
648
+ ) {
649
+ messages.push(...convertToCoreMessages(args.messages as UIMessage[]));
650
+ } else {
651
+ messages.push(...coreMessageSchema.array().parse(args.messages));
652
+ }
653
+ assert(messages.length > 0, "Messages must contain at least one message");
654
+ return messages;
655
+ }
656
+
657
+ // export function convexValidatorSchema<T>(validator: Validator<unknown>) {
658
+ // return ai.jsonSchema(convexToJsonSchema(validator));
659
+ // }
@@ -0,0 +1,54 @@
1
+ import {
2
+ GenericQueryCtx,
3
+ GenericDataModel,
4
+ GenericMutationCtx,
5
+ Expand,
6
+ FunctionReference,
7
+ GenericActionCtx,
8
+ } from "convex/server";
9
+ import { GenericId } from "convex/values";
10
+
11
+ /* Type utils follow */
12
+ export type RunQueryCtx = {
13
+ runQuery: GenericQueryCtx<GenericDataModel>["runQuery"];
14
+ };
15
+ export type RunMutationCtx = {
16
+ runQuery: GenericMutationCtx<GenericDataModel>["runQuery"];
17
+ runMutation: GenericMutationCtx<GenericDataModel>["runMutation"];
18
+ };
19
+ export type RunActionCtx = {
20
+ runQuery: GenericActionCtx<GenericDataModel>["runQuery"];
21
+ runMutation: GenericActionCtx<GenericDataModel>["runMutation"];
22
+ runAction: GenericActionCtx<GenericDataModel>["runAction"];
23
+ };
24
+
25
+ export type OpaqueIds<T> =
26
+ T extends GenericId<infer _T>
27
+ ? string
28
+ : T extends (infer U)[]
29
+ ? OpaqueIds<U>[]
30
+ : T extends ArrayBuffer
31
+ ? ArrayBuffer
32
+ : T extends object
33
+ ? {
34
+ [K in keyof T]: OpaqueIds<T[K]>;
35
+ }
36
+ : T;
37
+
38
+ export type UseApi<API> = Expand<{
39
+ [mod in keyof API]: API[mod] extends FunctionReference<
40
+ infer FType,
41
+ "public",
42
+ infer FArgs,
43
+ infer FReturnType,
44
+ infer FComponentPath
45
+ >
46
+ ? FunctionReference<
47
+ FType,
48
+ "internal",
49
+ OpaqueIds<FArgs>,
50
+ OpaqueIds<FReturnType>,
51
+ FComponentPath
52
+ >
53
+ : UseApi<API[mod]>;
54
+ }>;