@convex-dev/agent
Version:
A agent component for Convex.
236 lines • 9.32 kB
JavaScript
import { actionGeneric, mutationGeneric, paginationOptsValidator, queryGeneric, } from "convex/server";
import { v } from "convex/values";
import { createThread as createThread_, listMessages as listMessages_, deserializeMessage, vContextOptions, vMessage, vMessageDoc, vPaginationResult, vStorageOptions, vThreadDoc, } from "./index.js";
// Playground API definition
export function definePlaygroundAPI(component, { agents: agentsOrFn, userNameLookup, }) {
function validateAgents(agents) {
for (const agent of agents) {
if (!agent.options.name) {
console.warn(`Agent has no name (instructions: ${agent.options.instructions})`);
}
}
}
async function validateApiKey(ctx, apiKey) {
await ctx.runQuery(component.apiKeys.validate, { apiKey });
}
const isApiKeyValid = queryGeneric({
args: { apiKey: v.string() },
handler: async (ctx, args) => {
try {
await validateApiKey(ctx, args.apiKey);
return true;
}
catch {
return false;
}
},
returns: v.boolean(),
});
async function getAgents(ctx, args) {
const agents = Array.isArray(agentsOrFn)
? agentsOrFn
: await agentsOrFn(ctx, args);
validateAgents(agents);
return agents.map((agent, i) => ({
name: agent.options.name ?? `Agent ${i} (missing 'name')`,
agent,
}));
}
// List all agents
const listAgents = queryGeneric({
args: {
apiKey: v.string(),
userId: v.optional(v.string()),
threadId: v.optional(v.string()),
},
handler: async (ctx, args) => {
const agents = await getAgents(ctx, {
userId: args.userId,
threadId: args.threadId,
});
await validateApiKey(ctx, args.apiKey);
return agents.map(({ name, agent }) => ({
name,
instructions: agent.options.instructions,
contextOptions: agent.options.contextOptions,
storageOptions: agent.options.storageOptions,
maxRetries: agent.options.callSettings?.maxRetries,
tools: agent.options.tools ? Object.keys(agent.options.tools) : [],
}));
},
});
const listUsers = queryGeneric({
args: { apiKey: v.string(), paginationOpts: paginationOptsValidator },
handler: async (ctx, args) => {
await validateApiKey(ctx, args.apiKey);
const users = await ctx.runQuery(component.users.listUsersWithThreads, {
paginationOpts: args.paginationOpts,
});
return {
...users,
page: await Promise.all(users.page.map(async (userId) => ({
_id: userId,
name: userNameLookup ? await userNameLookup(ctx, userId) : userId,
}))),
};
},
returns: vPaginationResult(v.object({ _id: v.string(), name: v.string() })),
});
// List threads for a user (query)
const listThreads = queryGeneric({
args: {
apiKey: v.string(),
userId: v.optional(v.string()),
paginationOpts: paginationOptsValidator,
},
handler: async (ctx, args) => {
await validateApiKey(ctx, args.apiKey);
const results = await ctx.runQuery(component.threads.listThreadsByUserId, {
userId: args.userId,
paginationOpts: args.paginationOpts,
order: "desc",
});
return {
...results,
page: await Promise.all(results.page.map(async (thread) => {
const { page: [last], } = await ctx.runQuery(component.messages.listMessagesByThreadId, {
threadId: thread._id,
order: "desc",
paginationOpts: { numItems: 1, cursor: null },
});
return {
...thread,
lastAgentName: last?.agentName,
latestMessage: last?.text,
lastMessageAt: last?._creationTime,
};
})),
};
},
returns: vPaginationResult(v.object({
...vThreadDoc.fields,
lastAgentName: v.optional(v.string()),
latestMessage: v.optional(v.string()),
lastMessageAt: v.optional(v.number()),
})),
});
// List messages for a thread (query)
const listMessages = queryGeneric({
args: {
apiKey: v.string(),
threadId: v.string(),
paginationOpts: paginationOptsValidator,
},
handler: async (ctx, args) => {
await validateApiKey(ctx, args.apiKey);
return listMessages_(ctx, component, {
threadId: args.threadId,
paginationOpts: args.paginationOpts,
statuses: ["success", "failed", "pending"],
});
},
returns: vPaginationResult(vMessageDoc),
});
// Create a thread (mutation)
const createThread = mutationGeneric({
args: {
apiKey: v.string(),
userId: v.string(),
title: v.optional(v.string()),
summary: v.optional(v.string()),
/** @deprecated Unused. */
agentName: v.optional(v.string()),
},
handler: async (ctx, args) => {
await validateApiKey(ctx, args.apiKey);
const threadId = await createThread_(ctx, component, {
userId: args.userId,
title: args.title,
summary: args.summary,
});
return { threadId };
},
returns: v.object({ threadId: v.string() }),
});
// Send a message (action)
const generateText = actionGeneric({
args: {
apiKey: v.string(),
agentName: v.string(),
userId: v.string(),
threadId: v.string(),
// Options for generateText
contextOptions: v.optional(vContextOptions),
storageOptions: v.optional(vStorageOptions),
// Args passed through to generateText
prompt: v.optional(v.string()),
messages: v.optional(v.array(vMessage)),
system: v.optional(v.string()),
},
handler: async (ctx, args) => {
const { apiKey, agentName, userId, threadId, contextOptions, storageOptions, system, messages, ...rest } = args;
await validateApiKey(ctx, apiKey);
const agents = await getAgents(ctx, {
userId: args.userId,
threadId: args.threadId,
});
const namedAgent = agents.find(({ name }) => name === agentName);
if (!namedAgent)
throw new Error(`Unknown agent: ${agentName}`);
const { agent } = namedAgent;
const { messageId, text } = await agent.generateText(ctx, { threadId, userId }, {
...rest,
...(system ? { system } : {}),
...(messages ? { messages: messages.map(deserializeMessage) } : {}),
}, { contextOptions, storageOptions });
return { messageId, text };
},
});
// Fetch prompt context (action)
const fetchPromptContext = actionGeneric({
args: {
apiKey: v.string(),
agentName: v.string(),
userId: v.optional(v.string()),
threadId: v.optional(v.string()),
messages: v.array(vMessage),
contextOptions: vContextOptions,
beforeMessageId: v.optional(v.string()),
},
handler: async (ctx, args) => {
await validateApiKey(ctx, args.apiKey);
const agents = await getAgents(ctx, {
userId: args.userId,
threadId: args.threadId,
});
const namedAgent = agents.find(({ name }) => name === args.agentName);
if (!namedAgent)
throw new Error(`Unknown agent: ${args.agentName}`);
const { agent } = namedAgent;
const contextOptions = args.contextOptions;
if (args.beforeMessageId) {
contextOptions.recentMessages =
(contextOptions.recentMessages ?? 10) + 1;
}
const messages = await agent.fetchContextMessages(ctx, {
userId: args.userId,
threadId: args.threadId,
messages: args.messages.map(deserializeMessage),
contextOptions: args.contextOptions,
upToAndIncludingMessageId: args.beforeMessageId,
});
return messages.filter((m) => !args.beforeMessageId || m._id !== args.beforeMessageId);
},
});
return {
isApiKeyValid,
listUsers,
listThreads,
listMessages,
listAgents,
createThread,
generateText,
fetchPromptContext,
};
}
//# sourceMappingURL=definePlaygroundAPI.js.map