@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.
- package/LICENSE +201 -0
- package/README.md +55 -0
- package/dist/commonjs/client/index.d.ts +198 -0
- package/dist/commonjs/client/index.d.ts.map +1 -0
- package/dist/commonjs/client/index.js +365 -0
- package/dist/commonjs/client/index.js.map +1 -0
- package/dist/commonjs/client/types.d.ts +21 -0
- package/dist/commonjs/client/types.d.ts.map +1 -0
- package/dist/commonjs/client/types.js +2 -0
- package/dist/commonjs/client/types.js.map +1 -0
- package/dist/commonjs/component/_generated/api.d.ts +12 -0
- package/dist/commonjs/component/_generated/api.d.ts.map +1 -0
- package/dist/commonjs/component/_generated/api.js +22 -0
- package/dist/commonjs/component/_generated/api.js.map +1 -0
- package/dist/commonjs/component/_generated/server.d.ts +64 -0
- package/dist/commonjs/component/_generated/server.d.ts.map +1 -0
- package/dist/commonjs/component/_generated/server.js +74 -0
- package/dist/commonjs/component/_generated/server.js.map +1 -0
- package/dist/commonjs/component/convex.config.d.ts +3 -0
- package/dist/commonjs/component/convex.config.d.ts.map +1 -0
- package/dist/commonjs/component/convex.config.js +3 -0
- package/dist/commonjs/component/convex.config.js.map +1 -0
- package/dist/commonjs/component/lib.d.ts +2 -0
- package/dist/commonjs/component/lib.d.ts.map +1 -0
- package/dist/commonjs/component/lib.js +2 -0
- package/dist/commonjs/component/lib.js.map +1 -0
- package/dist/commonjs/component/messages.d.ts +1913 -0
- package/dist/commonjs/component/messages.d.ts.map +1 -0
- package/dist/commonjs/component/messages.js +787 -0
- package/dist/commonjs/component/messages.js.map +1 -0
- package/dist/commonjs/component/schema.d.ts +5496 -0
- package/dist/commonjs/component/schema.d.ts.map +1 -0
- package/dist/commonjs/component/schema.js +97 -0
- package/dist/commonjs/component/schema.js.map +1 -0
- package/dist/commonjs/component/vector/tables.d.ts +40 -0
- package/dist/commonjs/component/vector/tables.d.ts.map +1 -0
- package/dist/commonjs/component/vector/tables.js +46 -0
- package/dist/commonjs/component/vector/tables.js.map +1 -0
- package/dist/commonjs/mapping.d.ts +26 -0
- package/dist/commonjs/mapping.d.ts.map +1 -0
- package/dist/commonjs/mapping.js +101 -0
- package/dist/commonjs/mapping.js.map +1 -0
- package/dist/commonjs/package.json +3 -0
- package/dist/commonjs/react/index.d.ts +2 -0
- package/dist/commonjs/react/index.d.ts.map +1 -0
- package/dist/commonjs/react/index.js +8 -0
- package/dist/commonjs/react/index.js.map +1 -0
- package/dist/commonjs/shared.d.ts +9 -0
- package/dist/commonjs/shared.d.ts.map +1 -0
- package/dist/commonjs/shared.js +29 -0
- package/dist/commonjs/shared.js.map +1 -0
- package/dist/commonjs/validators.d.ts +6177 -0
- package/dist/commonjs/validators.d.ts.map +1 -0
- package/dist/commonjs/validators.js +171 -0
- package/dist/commonjs/validators.js.map +1 -0
- package/dist/esm/client/index.d.ts +198 -0
- package/dist/esm/client/index.d.ts.map +1 -0
- package/dist/esm/client/index.js +365 -0
- package/dist/esm/client/index.js.map +1 -0
- package/dist/esm/client/types.d.ts +21 -0
- package/dist/esm/client/types.d.ts.map +1 -0
- package/dist/esm/client/types.js +2 -0
- package/dist/esm/client/types.js.map +1 -0
- package/dist/esm/component/_generated/api.d.ts +12 -0
- package/dist/esm/component/_generated/api.d.ts.map +1 -0
- package/dist/esm/component/_generated/api.js +22 -0
- package/dist/esm/component/_generated/api.js.map +1 -0
- package/dist/esm/component/_generated/server.d.ts +64 -0
- package/dist/esm/component/_generated/server.d.ts.map +1 -0
- package/dist/esm/component/_generated/server.js +74 -0
- package/dist/esm/component/_generated/server.js.map +1 -0
- package/dist/esm/component/convex.config.d.ts +3 -0
- package/dist/esm/component/convex.config.d.ts.map +1 -0
- package/dist/esm/component/convex.config.js +3 -0
- package/dist/esm/component/convex.config.js.map +1 -0
- package/dist/esm/component/lib.d.ts +2 -0
- package/dist/esm/component/lib.d.ts.map +1 -0
- package/dist/esm/component/lib.js +2 -0
- package/dist/esm/component/lib.js.map +1 -0
- package/dist/esm/component/messages.d.ts +1913 -0
- package/dist/esm/component/messages.d.ts.map +1 -0
- package/dist/esm/component/messages.js +787 -0
- package/dist/esm/component/messages.js.map +1 -0
- package/dist/esm/component/schema.d.ts +5496 -0
- package/dist/esm/component/schema.d.ts.map +1 -0
- package/dist/esm/component/schema.js +97 -0
- package/dist/esm/component/schema.js.map +1 -0
- package/dist/esm/component/vector/tables.d.ts +40 -0
- package/dist/esm/component/vector/tables.d.ts.map +1 -0
- package/dist/esm/component/vector/tables.js +46 -0
- package/dist/esm/component/vector/tables.js.map +1 -0
- package/dist/esm/mapping.d.ts +26 -0
- package/dist/esm/mapping.d.ts.map +1 -0
- package/dist/esm/mapping.js +101 -0
- package/dist/esm/mapping.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/react/index.d.ts +2 -0
- package/dist/esm/react/index.d.ts.map +1 -0
- package/dist/esm/react/index.js +8 -0
- package/dist/esm/react/index.js.map +1 -0
- package/dist/esm/shared.d.ts +9 -0
- package/dist/esm/shared.d.ts.map +1 -0
- package/dist/esm/shared.js +29 -0
- package/dist/esm/shared.js.map +1 -0
- package/dist/esm/validators.d.ts +6177 -0
- package/dist/esm/validators.d.ts.map +1 -0
- package/dist/esm/validators.js +171 -0
- package/dist/esm/validators.js.map +1 -0
- package/package.json +91 -0
- package/react/package.json +5 -0
- package/src/client/index.ts +659 -0
- package/src/client/types.ts +54 -0
- package/src/component/_generated/api.d.ts +1497 -0
- package/src/component/_generated/api.js +23 -0
- package/src/component/_generated/dataModel.d.ts +60 -0
- package/src/component/_generated/server.d.ts +149 -0
- package/src/component/_generated/server.js +90 -0
- package/src/component/convex.config.ts +3 -0
- package/src/component/lib.test.ts +13 -0
- package/src/component/lib.ts +2 -0
- package/src/component/messages.ts +959 -0
- package/src/component/schema.ts +101 -0
- package/src/component/setup.test.ts +5 -0
- package/src/component/vector/tables.ts +92 -0
- package/src/mapping.ts +160 -0
- package/src/react/index.ts +8 -0
- package/src/shared.ts +35 -0
- package/src/validators.test.ts +101 -0
- 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
|
+
}>;
|