@convex-dev/agent 0.1.16 → 0.1.17

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.
@@ -0,0 +1,337 @@
1
+ import {
2
+ paginationOptsValidator,
3
+ queryGeneric,
4
+ mutationGeneric,
5
+ actionGeneric,
6
+ type GenericDataModel,
7
+ type GenericQueryCtx,
8
+ type ApiFromModules,
9
+ type GenericActionCtx,
10
+ } from "convex/server";
11
+ import {
12
+ vMessageDoc,
13
+ vThreadDoc,
14
+ vPaginationResult,
15
+ vMessage,
16
+ vContextOptions,
17
+ vStorageOptions,
18
+ type AgentComponent,
19
+ type Agent,
20
+ type ContextOptions,
21
+ } from "@convex-dev/agent";
22
+ import type { ToolSet } from "ai";
23
+ import { v } from "convex/values";
24
+
25
+ export type PlaygroundAPI = ApiFromModules<{
26
+ playground: ReturnType<typeof definePlaygroundAPI>;
27
+ }>["playground"];
28
+
29
+ export type AgentsFn<DataModel extends GenericDataModel> = (
30
+ ctx: GenericActionCtx<DataModel> | GenericQueryCtx<DataModel>,
31
+ args: { userId: string | undefined; threadId: string | undefined },
32
+ ) => Agent<ToolSet>[] | Promise<Agent<ToolSet>[]>;
33
+
34
+ // Playground API definition
35
+ export function definePlaygroundAPI<DataModel extends GenericDataModel>(
36
+ component: AgentComponent,
37
+ {
38
+ agents: agentsOrFn,
39
+ userNameLookup,
40
+ }: {
41
+ agents: Agent<ToolSet>[] | AgentsFn<DataModel>;
42
+ userNameLookup?: (
43
+ ctx: GenericQueryCtx<DataModel>,
44
+ userId: string,
45
+ ) => string | Promise<string>;
46
+ },
47
+ ) {
48
+ function validateAgents(agents: Agent<ToolSet>[]) {
49
+ for (const agent of agents) {
50
+ if (!agent.options.name) {
51
+ console.warn(
52
+ `Agent has no name (instructions: ${agent.options.instructions})`,
53
+ );
54
+ }
55
+ }
56
+ }
57
+
58
+ async function validateApiKey(ctx: RunQueryCtx, apiKey: string) {
59
+ await ctx.runQuery(component.apiKeys.validate, { apiKey });
60
+ }
61
+
62
+ const isApiKeyValid = queryGeneric({
63
+ args: {
64
+ apiKey: v.string(),
65
+ },
66
+ handler: async (ctx, args) => {
67
+ try {
68
+ await validateApiKey(ctx, args.apiKey);
69
+ return true;
70
+ } catch {
71
+ return false;
72
+ }
73
+ },
74
+ returns: v.boolean(),
75
+ });
76
+
77
+ async function getAgents(
78
+ ctx: GenericActionCtx<DataModel> | GenericQueryCtx<DataModel>,
79
+ args: { userId: string | undefined; threadId: string | undefined },
80
+ ) {
81
+ const agents = Array.isArray(agentsOrFn)
82
+ ? agentsOrFn
83
+ : await agentsOrFn(ctx, args);
84
+ validateAgents(agents);
85
+ return agents.map((agent, i) => ({
86
+ name: agent.options.name ?? `Agent ${i} (missing 'name')`,
87
+ agent,
88
+ }));
89
+ }
90
+
91
+ // List all agents
92
+ const listAgents = queryGeneric({
93
+ args: {
94
+ apiKey: v.string(),
95
+ userId: v.optional(v.string()),
96
+ threadId: v.optional(v.string()),
97
+ },
98
+ handler: async (ctx, args) => {
99
+ const agents = await getAgents(ctx, {
100
+ userId: args.userId,
101
+ threadId: args.threadId,
102
+ });
103
+ await validateApiKey(ctx, args.apiKey);
104
+ return agents.map(({ name, agent }) => ({
105
+ name,
106
+ instructions: agent.options.instructions,
107
+ contextOptions: agent.options.contextOptions,
108
+ storageOptions: agent.options.storageOptions,
109
+ maxSteps: agent.options.maxSteps,
110
+ maxRetries: agent.options.maxRetries,
111
+ tools: agent.options.tools ? Object.keys(agent.options.tools) : [],
112
+ }));
113
+ },
114
+ });
115
+
116
+ const listUsers = queryGeneric({
117
+ args: {
118
+ apiKey: v.string(),
119
+ paginationOpts: paginationOptsValidator,
120
+ },
121
+ handler: async (ctx, args) => {
122
+ await validateApiKey(ctx, args.apiKey);
123
+ const users = await ctx.runQuery(component.users.listUsersWithThreads, {
124
+ paginationOpts: args.paginationOpts,
125
+ });
126
+ return {
127
+ ...users,
128
+ page: await Promise.all(
129
+ users.page.map(async (userId) => ({
130
+ _id: userId,
131
+ name: userNameLookup ? await userNameLookup(ctx, userId) : userId,
132
+ })),
133
+ ),
134
+ };
135
+ },
136
+ returns: vPaginationResult(
137
+ v.object({
138
+ _id: v.string(),
139
+ name: v.string(),
140
+ }),
141
+ ),
142
+ });
143
+
144
+ // List threads for a user (query)
145
+ const listThreads = queryGeneric({
146
+ args: {
147
+ apiKey: v.string(),
148
+ userId: v.optional(v.string()),
149
+ paginationOpts: paginationOptsValidator,
150
+ },
151
+ handler: async (ctx, args) => {
152
+ await validateApiKey(ctx, args.apiKey);
153
+ const results = await ctx.runQuery(
154
+ component.threads.listThreadsByUserId,
155
+ {
156
+ userId: args.userId,
157
+ paginationOpts: args.paginationOpts,
158
+ order: "desc",
159
+ },
160
+ );
161
+ return {
162
+ ...results,
163
+ page: await Promise.all(
164
+ results.page.map(async (thread) => {
165
+ const {
166
+ page: [last],
167
+ } = await ctx.runQuery(component.messages.listMessagesByThreadId, {
168
+ threadId: thread._id,
169
+ order: "desc",
170
+ paginationOpts: {
171
+ numItems: 1,
172
+ cursor: null,
173
+ },
174
+ });
175
+ return {
176
+ ...thread,
177
+ lastAgentName: last?.agentName,
178
+ latestMessage: last?.text,
179
+ lastMessageAt: last?._creationTime,
180
+ };
181
+ }),
182
+ ),
183
+ };
184
+ },
185
+ returns: vPaginationResult(
186
+ v.object({
187
+ ...vThreadDoc.fields,
188
+ lastAgentName: v.optional(v.string()),
189
+ latestMessage: v.optional(v.string()),
190
+ lastMessageAt: v.optional(v.number()),
191
+ }),
192
+ ),
193
+ });
194
+
195
+ // List messages for a thread (query)
196
+ const listMessages = queryGeneric({
197
+ args: {
198
+ apiKey: v.string(),
199
+ threadId: v.string(),
200
+ paginationOpts: paginationOptsValidator,
201
+ },
202
+ handler: async (ctx, args) => {
203
+ await validateApiKey(ctx, args.apiKey);
204
+ return ctx.runQuery(component.messages.listMessagesByThreadId, {
205
+ threadId: args.threadId,
206
+ paginationOpts: args.paginationOpts,
207
+ order: "desc",
208
+ statuses: ["success", "failed", "pending"],
209
+ });
210
+ },
211
+ returns: vPaginationResult(vMessageDoc),
212
+ });
213
+
214
+ // Create a thread (mutation)
215
+ const createThread = mutationGeneric({
216
+ args: {
217
+ apiKey: v.string(),
218
+ userId: v.string(),
219
+ title: v.optional(v.string()),
220
+ summary: v.optional(v.string()),
221
+ /** @deprecated Unused. */
222
+ agentName: v.optional(v.string()),
223
+ },
224
+ handler: async (ctx, args) => {
225
+ // if (args.agentName) {
226
+ // console.warn(
227
+ // "Upgrade to the latest version of @convex-dev/agent-playground"
228
+ // );
229
+ // }
230
+ await validateApiKey(ctx, args.apiKey);
231
+ const { _id } = await ctx.runMutation(component.threads.createThread, {
232
+ userId: args.userId,
233
+ title: args.title,
234
+ summary: args.summary,
235
+ });
236
+ return { threadId: _id };
237
+ },
238
+ returns: v.object({ threadId: v.string() }),
239
+ });
240
+
241
+ // Send a message (action)
242
+ const generateText = actionGeneric({
243
+ args: {
244
+ apiKey: v.string(),
245
+ agentName: v.string(),
246
+ userId: v.string(),
247
+ threadId: v.string(),
248
+ // Options for generateText
249
+ contextOptions: v.optional(vContextOptions),
250
+ storageOptions: v.optional(vStorageOptions),
251
+ // Args passed through to generateText
252
+ prompt: v.optional(v.string()),
253
+ messages: v.optional(v.array(vMessage)),
254
+ system: v.optional(v.string()),
255
+ },
256
+ handler: async (ctx: GenericActionCtx<DataModel>, args) => {
257
+ const {
258
+ apiKey,
259
+ agentName,
260
+ userId,
261
+ threadId,
262
+ contextOptions,
263
+ storageOptions,
264
+ system,
265
+ ...rest
266
+ } = args;
267
+ await validateApiKey(ctx, apiKey);
268
+ const agents = await getAgents(ctx, {
269
+ userId: args.userId,
270
+ threadId: args.threadId,
271
+ });
272
+ const namedAgent = agents.find(({ name }) => name === agentName);
273
+ if (!namedAgent) throw new Error(`Unknown agent: ${agentName}`);
274
+ const { agent } = namedAgent;
275
+ const { thread } = await agent.continueThread(ctx, { threadId, userId });
276
+ const { messageId, text } = await thread.generateText(
277
+ { ...rest, ...(system ? { system } : {}) },
278
+ {
279
+ contextOptions,
280
+ storageOptions,
281
+ },
282
+ );
283
+ return { messageId, text };
284
+ },
285
+ });
286
+
287
+ // Fetch prompt context (action)
288
+ const fetchPromptContext = actionGeneric({
289
+ args: {
290
+ apiKey: v.string(),
291
+ agentName: v.string(),
292
+ userId: v.optional(v.string()),
293
+ threadId: v.optional(v.string()),
294
+ messages: v.array(vMessage),
295
+ contextOptions: vContextOptions,
296
+ beforeMessageId: v.optional(v.string()),
297
+ },
298
+ handler: async (ctx, args) => {
299
+ await validateApiKey(ctx, args.apiKey);
300
+ const agents = await getAgents(ctx, {
301
+ userId: args.userId,
302
+ threadId: args.threadId,
303
+ });
304
+ const namedAgent = agents.find(({ name }) => name === args.agentName);
305
+ if (!namedAgent) throw new Error(`Unknown agent: ${args.agentName}`);
306
+ const { agent } = namedAgent;
307
+ const contextOptions = args.contextOptions;
308
+ if (args.beforeMessageId) {
309
+ contextOptions.recentMessages =
310
+ (contextOptions.recentMessages ?? 10) + 1;
311
+ }
312
+ const messages = await agent.fetchContextMessages(ctx, {
313
+ userId: args.userId,
314
+ threadId: args.threadId,
315
+ messages: args.messages,
316
+ contextOptions: args.contextOptions,
317
+ upToAndIncludingMessageId: args.beforeMessageId,
318
+ });
319
+ return messages.filter(
320
+ (m) => !args.beforeMessageId || m._id !== args.beforeMessageId,
321
+ );
322
+ },
323
+ });
324
+
325
+ return {
326
+ isApiKeyValid,
327
+ listUsers,
328
+ listThreads,
329
+ listMessages,
330
+ listAgents,
331
+ createThread,
332
+ generateText,
333
+ fetchPromptContext,
334
+ };
335
+ }
336
+
337
+ type RunQueryCtx = { runQuery: GenericQueryCtx<GenericDataModel>["runQuery"] };
@@ -51,7 +51,7 @@ export async function storeFile(
51
51
  sha256 ||
52
52
  Array.from(
53
53
  new Uint8Array(
54
- await crypto.subtle.digest("SHA-256", await blob.arrayBuffer()),
54
+ await crypto.subtle.digest("SHA-256", await blob.slice().arrayBuffer()),
55
55
  ),
56
56
  )
57
57
  .map((b) => b.toString(16).padStart(2, "0"))
@@ -114,6 +114,11 @@ export {
114
114
  listMessages,
115
115
  syncStreams,
116
116
  };
117
+ export {
118
+ definePlaygroundAPI,
119
+ type PlaygroundAPI,
120
+ type AgentsFn,
121
+ } from "./definePlaygroundAPI.js";
117
122
  export type {
118
123
  AgentComponent,
119
124
  ContextOptions,
@@ -625,7 +630,7 @@ export class Agent<AgentTools extends ToolSet = ToolSet> {
625
630
  },
626
631
  onStepFinish: async (step) => {
627
632
  // console.log("onStepFinish", step);
628
- if (threadId && messageId) {
633
+ if (threadId && messageId && saveOutputMessages) {
629
634
  const saved = await this.saveStep(ctx, {
630
635
  userId,
631
636
  threadId,