### Run project locally Source: https://github.com/get-convex/agent/blob/main/CONTRIBUTING.md Install dependencies and start the development server. ```sh npm i npm run dev ``` -------------------------------- ### Running the Example Source: https://github.com/get-convex/agent/blob/main/docs/files.mdx Instructions to clone the repository, set up dependencies, and run the development server for the Convex Agent example. ```sh git clone https://github.com/get-convex/agent.git cd agent npm run setup npm run dev ``` -------------------------------- ### Clone and Setup Agent Example Project Source: https://github.com/get-convex/agent/blob/main/README.md Clones the official agent repository and initializes the development environment for testing and iteration. ```shell git clone https://github.com/get-convex/agent.git cd agent npm run setup npm run dev ``` -------------------------------- ### Basic Agent Usage Example Source: https://github.com/get-convex/agent/blob/main/docs/getting-started.mdx Demonstrates basic usage of a defined agent by creating a thread, prompting the agent for information, and returning the generated text. This action requires the agent component to be generated by running `npx convex dev`. ```typescript import { action } from "./_generated/server"; import { v } from "convex/values"; export const helloWorld = action({ args: { city: v.string() }, handler: async (ctx, { city }) => { const threadId = await createThread(ctx, components.agent); const prompt = `What is the weather in ${city}?`; const result = await agent.generateText(ctx, { threadId }, { prompt }); return result.text; }, }); ``` -------------------------------- ### Run RAG example project Source: https://github.com/get-convex/agent/blob/main/docs/rag.mdx Commands to clone and execute the RAG demonstration project. ```bash git clone https://github.com/get-convex/rag.git cd rag npm run setup npm run example ``` -------------------------------- ### Install Agent Component Package Source: https://github.com/get-convex/agent/blob/main/docs/getting-started.mdx Installs the necessary agent component package using npm. This is a prerequisite for using the agent functionality in your Convex project. ```bash npm install @convex-dev/agent ``` -------------------------------- ### Resolve dependency conflicts Source: https://github.com/get-convex/agent/blob/main/MIGRATION.md Commands to handle peer dependency issues during installation. ```bash npm install @convex-dev/agent@^0.6.0 ai@^6.0.35 @ai-sdk/openai@^3.0.10 @openrouter/ai-sdk-provider@^2.0.0 ``` ```bash npm install @convex-dev/agent@^0.6.0 --force ``` -------------------------------- ### Run Development Server Source: https://github.com/get-convex/agent/blob/main/CLAUDE.md Starts the backend, frontend, and build watch processes concurrently for development. ```bash npm run dev ``` -------------------------------- ### Build a one-off package Source: https://github.com/get-convex/agent/blob/main/CONTRIBUTING.md Clean the environment, install dependencies, and create a tarball package. ```sh npm run clean npm ci npm pack ``` -------------------------------- ### Define Your First Agent Source: https://github.com/get-convex/agent/blob/main/docs/getting-started.mdx Defines a new agent using the Agent class, specifying its name, language model, instructions, tools, and stopping conditions. This code sets up the core logic and behavior of your agent. ```typescript import { components } from "./_generated/api"; import { Agent, stepCountIs } from "@convex-dev/agent"; import { openai } from "@ai-sdk/openai"; const agent = new Agent(components.agent, { name: "My Agent", languageModel: openai.chat("gpt-4o-mini"), instructions: "You are a weather forecaster.", tools: { getWeather, getGeocoding }, stopWhen: stepCountIs(3), }); ``` -------------------------------- ### Initialize the Rate Limiter Source: https://github.com/get-convex/agent/blob/main/docs/rate-limiting.mdx Setup the rate limiter configuration with specific rules for message frequency and token consumption. ```ts import { MINUTE, RateLimiter, SECOND } from "@convex-dev/rate-limiter"; import { components } from "./_generated/api"; export const rateLimiter = new RateLimiter(components.rateLimiter, { sendMessage: { kind: "fixed window", period: 5 * SECOND, rate: 1, capacity: 2, }, globalSendMessage: { kind: "token bucket", period: MINUTE, rate: 1_000 }, tokenUsagePerUser: { kind: "token bucket", period: MINUTE, rate: 2000, capacity: 10000, }, globalTokenUsage: { kind: "token bucket", period: MINUTE, rate: 100_000 }, }); ``` -------------------------------- ### Install Specific AI Provider Source: https://github.com/get-convex/agent/blob/main/CLAUDE.md Installs a specific AI provider package, such as the OpenAI provider, ensuring compatibility with AI SDK v6. ```bash npm install @ai-sdk/openai@^3.0.10 ``` -------------------------------- ### Configure Convex Agent Component Source: https://github.com/get-convex/agent/blob/main/docs/getting-started.mdx Configures the agent component within your Convex project by importing and using it in `convex.config.ts`. This step integrates the agent into your Convex application. ```typescript // convex/convex.config.ts import { defineApp } from "convex/server"; import agent from "@convex-dev/agent/convex.config"; const app = defineApp(); app.use(agent); export default app; ``` -------------------------------- ### Install Compatible Sibling Packages Source: https://github.com/get-convex/agent/blob/main/CLAUDE.md Installs compatible versions of sibling packages like @convex-dev/rag and @convex-dev/workflow to avoid type conflicts with AI SDK v6. ```bash npm install @convex-dev/rag@^0.7.0 npm install @convex-dev/workflow@^0.3.2 ``` -------------------------------- ### Run Convex Agent Playground Locally Source: https://github.com/get-convex/agent/blob/main/docs/playground.mdx Starts the Convex Agent Playground locally. It utilizes the `VITE_CONVEX_URL` environment variable, typically sourced from `.env.local`. ```bash npx @convex-dev/agent-playground ``` -------------------------------- ### Update core dependencies Source: https://github.com/get-convex/agent/blob/main/MIGRATION.md Install the required versions of the agent and AI SDK packages to ensure compatibility. ```bash npm install @convex-dev/agent@^0.6.0 ai@^6.0.35 @ai-sdk/provider-utils@^4.0.6 ``` -------------------------------- ### Agent as a Tool Source: https://github.com/get-convex/agent/blob/main/docs/tools.mdx Integrate an agent as a tool to interact with it. This example shows creating a thread, generating text, and optionally associating child threads. ```typescript const agentTool = createTool({ description: `Ask a question to agent ${agent.name}`, args: z.object({ message: z.string().describe("The message to ask the agent"), }), handler: async (ctx, args, options): Promise => { const { userId } = ctx; const { thread } = await agent.createThread(ctx, { userId }); const result = await thread.generateText( { // Pass through all messages from the current generation prompt: [...options.messages, { role: "user", content: args.message }], }, // Save all the messages from the current generation to this thread. { storageOptions: { saveMessages: "all" } }, ); // Optionally associate the child thread with the parent thread in your own // tables. await saveThreadAsChild(ctx, ctx.threadId, thread.threadId); return result.text; }, }); ``` -------------------------------- ### Install Convex Agent Component Source: https://github.com/get-convex/agent/blob/main/README.md Installs the @convex-dev/agent package into your existing Convex project using npm. ```shell npm i @convex-dev/agent ``` -------------------------------- ### Debug dependency resolution errors Source: https://github.com/get-convex/agent/blob/main/MIGRATION.md Example of error output when dependency trees cannot be resolved. ```text npm error ERESOLVE unable to resolve dependency tree npm error peer ai@"^5.0.0" from @openrouter/ai-sdk-provider@1.0.3 ``` -------------------------------- ### Run Tests in Watch Mode Source: https://github.com/get-convex/agent/blob/main/CLAUDE.md Starts Vitest in watch mode, automatically re-running tests and type checks on file changes. ```bash npm run test:watch ``` -------------------------------- ### Define Default Agent Context Handler Source: https://github.com/get-convex/agent/blob/main/docs/context.mdx Provide a contextHandler in the Agent constructor to define default message combination logic. This example shows the default behavior of including all available messages. ```typescript const myAgent = new Agent(components.agent, { ///... contextHandler: async (ctx, args) => { // This is the default behavior. return [ ...args.search, ...args.recent, ...args.inputMessages, ...args.inputPrompt, ...args.existingResponses, ]; // Equivalent to: return args.allMessages; }, }); ``` -------------------------------- ### Run project tests and checks Source: https://github.com/get-convex/agent/blob/main/CONTRIBUTING.md Execute the full suite of cleanup, build, type checking, linting, and testing scripts. ```sh npm run clean npm run build npm run typecheck npm run lint npm run test ``` -------------------------------- ### Check provider versions Source: https://github.com/get-convex/agent/blob/main/MIGRATION.md Command to view available versions for a specific provider package. ```bash npm view @openrouter/ai-sdk-provider versions ``` -------------------------------- ### Get messages in a thread Source: https://github.com/get-convex/agent/blob/main/docs/threads.mdx Retrieve messages or UI-formatted messages from a specific thread. ```ts import { listMessages } from "@convex-dev/agent"; const messages = await listMessages(ctx, components.agent, { threadId, excludeToolMessages: true, paginationOpts: { cursor: null, numItems: 10 }, // null means start from the beginning }); ``` ```ts import { listUIMessages } from "@convex-dev/agent"; const messages = await listUIMessages(ctx, components.agent, { threadId, paginationOpts: { cursor: null, numItems: 10 }, }); ``` -------------------------------- ### Deploy project versions Source: https://github.com/get-convex/agent/blob/main/CONTRIBUTING.md Commands for releasing new versions or alpha releases. ```sh npm run release ``` ```sh npm run alpha ``` -------------------------------- ### Get threads by user Source: https://github.com/get-convex/agent/blob/main/docs/threads.mdx Retrieve all threads associated with a specific user ID. ```ts const threads = await ctx.runQuery( components.agent.threads.listThreadsByUserId, { userId, paginationOpts: args.paginationOpts }, ); ``` -------------------------------- ### Run image generation action Source: https://github.com/get-convex/agent/blob/main/docs/files.mdx Executes the generateImage action via the Convex CLI with a specific prompt. ```sh npx convex run files:generateImage:replyWithImage '{prompt: "make a picture of a cat" }' ``` -------------------------------- ### Run ESLint Source: https://github.com/get-convex/agent/blob/main/CLAUDE.md Executes ESLint for code linting to enforce code quality standards. ```bash npm run lint ``` -------------------------------- ### Full TypeScript Validation Source: https://github.com/get-convex/agent/blob/main/CLAUDE.md Performs a comprehensive TypeScript validation across the project, including example and Convex code. ```bash npm run typecheck ``` -------------------------------- ### Delete messages by order range Source: https://github.com/get-convex/agent/blob/main/docs/messages.mdx Delete messages based on order and stepOrder ranges, where the start is inclusive and the end is exclusive. ```ts // Delete all messages with the same order as a given message: await agent.deleteMessageRange(ctx, { threadId, startOrder: message.order, endOrder: message.order + 1, }); // Delete all messages with order 1 or 2. await agent.deleteMessageRange(ctx, { threadId, startOrder: 1, endOrder: 3 }); // Delete all messages with order 1 and stepOrder 2-4 await agent.deleteMessageRange(ctx, { threadId, startOrder: 1, startStepOrder: 2, endOrder: 2, endStepOrder: 5, }); ``` -------------------------------- ### Verify migration Source: https://github.com/get-convex/agent/blob/main/MIGRATION.md Run type checks and tests to ensure the migration was successful. ```bash npm run typecheck npm test ``` -------------------------------- ### Import agent utilities Source: https://github.com/get-convex/agent/blob/main/docs/messages.mdx Import utility functions from the @convex-dev/agent package. ```ts import { ... } from "@convex-dev/agent"; ``` -------------------------------- ### Implement Post-generation Usage Tracking Source: https://github.com/get-convex/agent/blob/main/docs/rate-limiting.mdx Use a usageHandler to track actual token consumption after generation. Setting reserve to true allows temporary negative balances to account for estimation discrepancies. ```typescript import { Agent, type Config } from "@convex-dev/rate-limiter"; const sharedConfig = { usageHandler: async (ctx, { usage, userId }) => { if (!userId) { return; } // We consume the token usage here, once we know the full usage. // This is too late for the first generation, but prevents further requests // until we've paid off that debt. await rateLimiter.limit(ctx, "tokenUsage", { key: userId, // You could weight different kinds of tokens differently here. count: usage.totalTokens, // Reserving the tokens means it won't fail here, but will allow it // to go negative, disallowing further requests at the `check` call below. reserve: true, }); }, } satisfies Config; // use it in your agent definitions const agent = new Agent(components.agent, { name, languageModel, ...sharedConfig, }); ``` -------------------------------- ### Create a thread Source: https://github.com/get-convex/agent/blob/main/docs/threads.mdx Initialize a new thread within a mutation or action. ```ts import { createThread } from "@convex-dev/agent"; const threadId = await createThread(ctx, components.agent); ``` ```ts const userId = await getAuthUserId(ctx); const threadId = await createThread(ctx, components.agent, { userId, title: "My thread", summary: "This is a summary of the thread", }); ``` -------------------------------- ### Helper function to get billing period Source: https://github.com/get-convex/agent/blob/main/docs/usage-tracking.mdx A utility function to determine the billing period by calculating the first day of the current month from a given timestamp. ```typescript function getBillingPeriod(at: number) { const now = new Date(at); const startOfMonth = new Date(now.getFullYear(), now.getMonth()); return startOfMonth.toISOString().split("T")[0]; } ``` -------------------------------- ### TypeScript Build Source: https://github.com/get-convex/agent/blob/main/CLAUDE.md Executes the TypeScript build process using the specified tsconfig.build.json. ```bash npm run build ``` -------------------------------- ### Direct LLM Generation Tool Source: https://github.com/get-convex/agent/blob/main/docs/tools.mdx Create a tool for direct LLM generation without managing a thread explicitly. Ensure the `myLanguageModel` is configured. ```typescript const llmTool = createTool({ description: "Ask a question to some LLM", args: z.object({ message: z.string().describe("The message to ask the LLM"), }), handler: async (ctx, args): Promise => { const result = await generateText({ system: "You are a helpful assistant.", // Pass through all messages from the current generation prompt: [...options.messages, { role: "user", content: args.message }], model: myLanguageModel, }); return result.text; }, }); ``` -------------------------------- ### Define a Basic Agent Source: https://github.com/get-convex/agent/blob/main/docs/agent-usage.mdx Instantiate a basic Agent with a name and language model. This serves as a foundation for agentic workflows. ```typescript import { components } from "./_generated/api"; import { Agent } from "@convex-dev/agent"; import { openai } from "@ai-sdk/openai"; const agent = new Agent(components.agent, { name: "Basic Agent", languageModel: openai.chat("gpt-4o-mini"), }); ``` -------------------------------- ### Implement Prompt-based RAG with Convex Agent Source: https://github.com/get-convex/agent/blob/main/docs/rag.mdx Injects retrieved context directly into the user prompt before generating a response. Use this when the query always requires external context. ```ts const context = await rag.search(ctx, { namespace: "global", query: userPrompt, limit: 10, }); const result = await agent.generateText( ctx, { threadId }, { prompt: `# Context:\n\n ${context.text}\n\n---\n\n# Question:\n\n"""${userPrompt}\n"""`, }, ); ``` -------------------------------- ### Create a tool with Convex context using createTool Source: https://github.com/get-convex/agent/blob/main/docs/tools.mdx Uses the createTool wrapper to define a tool with access to the Convex ActionCtx and agent-specific metadata. ```ts export const ideaSearch = createTool({ description: "Search for ideas in the database", args: z.object({ query: z.string().describe("The query to search for") }), handler: async (ctx, args, options): Promise> => { // ctx has agent, userId, threadId, messageId // as well as ActionCtx properties like auth, storage, runMutation, and runAction const ideas = await ctx.runQuery(api.ideas.searchIdeas, { query: args.query, }); console.log("found ideas", ideas); return ideas; }, }); ``` -------------------------------- ### Configure Message Storage Options Source: https://github.com/get-convex/agent/blob/main/docs/messages.mdx Configure how messages are saved using `storageOptions`. This allows control over saving all messages, none, or only the prompt and output messages. ```typescript const result = await thread.generateText({ messages }, { storageOptions: { saveMessages: "all" | "none" | "promptAndOutput"; }, }); ``` -------------------------------- ### Implement Server-side Approval Flow Source: https://github.com/get-convex/agent/blob/main/docs/tool-approval.mdx Manage the lifecycle of tool approval by saving messages, generating responses, submitting decisions, and resuming generation. ```ts import { approvalAgent } from "../agents/approval"; // 1. Save message and schedule generation export const sendMessage = mutation({ args: { prompt: v.string(), threadId: v.string() }, handler: async (ctx, { prompt, threadId }) => { const { messageId } = await approvalAgent.saveMessage(ctx, { threadId, prompt, }); await ctx.scheduler.runAfter(0, internal.chat.approval.generateResponse, { threadId, promptMessageId: messageId, }); return { messageId }; }, }); // 2. Generate (stops if approval is needed) export const generateResponse = internalAction({ args: { promptMessageId: v.string(), threadId: v.string() }, handler: async (ctx, { promptMessageId, threadId }) => { const result = await approvalAgent.streamText( ctx, { threadId }, { promptMessageId }, ); await result.consumeStream(); }, }); // 3. Submit an approval decision (can be a mutation or action) export const submitApproval = mutation({ args: { threadId: v.string(), approvalId: v.string(), approved: v.boolean(), reason: v.optional(v.string()), }, returns: v.object({ messageId: v.string() }), handler: async (ctx, { threadId, approvalId, approved, reason }) => { const { messageId } = approved ? await approvalAgent.approveToolCall(ctx, { threadId, approvalId, reason, }) : await approvalAgent.denyToolCall(ctx, { threadId, approvalId, reason, }); return { messageId }; }, }); // 4. Continue generation after all approvals resolved. // Pass the last approval message ID as promptMessageId so the agent // resumes generation from where the approval was issued. export const continueAfterApprovals = internalAction({ args: { threadId: v.string(), lastApprovalMessageId: v.string() }, handler: async (ctx, { threadId, lastApprovalMessageId }) => { const result = await approvalAgent.streamText( ctx, { threadId }, { promptMessageId: lastApprovalMessageId }, ); await result.consumeStream(); }, }); ``` -------------------------------- ### Storing File and Generating URL Source: https://github.com/get-convex/agent/blob/main/docs/files.mdx This snippet shows how to manually store a blob using `ctx.storage.store` and then retrieve its public URL. This URL can then be passed to the LLM. ```typescript const storageId = await ctx.storage.store(blob); const url = await ctx.storage.getUrl(storageId); await thread.generateText({ message: { role: "user", content: [ { type: "image", data: url, mimeType: blob.type }, { type: "text", text: "What is this image?" }, ], }, }); ``` -------------------------------- ### Implement SPA Redirect Logic Source: https://github.com/get-convex/agent/blob/main/playground/public/404.html Redirects users to the base agent path while storing the original path in sessionStorage for later restoration. ```javascript var base = "/agent"; var path = location.pathname + location.search + location.hash; if (path === base || path.startsWith(base + "/")) { // Store the original path for the SPA to restore sessionStorage.setItem( "redirectPath", path + location.search + location.hash, ); window.location.replace(base + "/"); } ``` -------------------------------- ### LLM Generates Tool Call to Human Agent Source: https://github.com/get-convex/agent/blob/main/docs/human-agents.mdx This action demonstrates how an LLM can generate a tool call to `askHuman`. It filters for `askHuman` tool calls and prepares them for further processing, such as saving to a support inbox. ```typescript export const ask = action({ args: { question: v.string(), threadId: v.string() }, handler: async (ctx, { question, threadId }) => { const result = await agent.generateText( ctx, { threadId }, { prompt: question, tools: { askHuman }, } ); const supportRequests = result.toolCalls .filter((tc) => tc.toolName === "askHuman") .map(({ toolCallId, args: { question } }) => ({ toolCallId, question, })); if (supportRequests.length > 0) { // Do something so the support agent knows they need to respond, // e.g. save a message to their inbox // await ctx.runMutation(internal.example.sendToSupport, { // threadId, // supportRequests, // }); } }, }); ``` -------------------------------- ### Run Tests with Typecheck Source: https://github.com/get-convex/agent/blob/main/CLAUDE.md Executes tests using Vitest, including a full TypeScript type check. ```bash npm test ``` -------------------------------- ### Define Playground API in Convex Source: https://github.com/get-convex/agent/blob/main/docs/playground.mdx Defines the Playground API by exposing functions for interacting with the agent. Requires importing `definePlaygroundAPI` and specifying agent components. Authorization is handled via API keys. ```typescript import { definePlaygroundAPI } from "@convex-dev/agent"; import { components } from "./_generated/api"; import { weatherAgent, fashionAgent } from "./example"; /** * Here we expose the API so the frontend can access it. * Authorization is handled by passing up an apiKey that can be generated * on the dashboard or via CLI via: * npx convex run --component agent apiKeys:issue */ export const { isApiKeyValid, listAgents, listUsers, listThreads, listMessages, createThread, generateText, fetchPromptContext, } = definePlaygroundAPI(components.agent, { agents: [weatherAgent, fashionAgent], }); ``` -------------------------------- ### Issue API Key via CLI Source: https://github.com/get-convex/agent/blob/main/docs/playground.mdx Issues an API key for authentication with the Convex agent playground. The name parameter can be used to generate multiple keys or reissue existing ones. ```bash npx convex run --component agent apiKeys:issue '{name:"..."}' ``` -------------------------------- ### Dynamically Define an Agent Source: https://github.com/get-convex/agent/blob/main/docs/agent-usage.mdx Create an Agent at runtime, useful for specific contexts. This allows dynamic selection of models and tools without passing full context to each tool call. ```typescript import { Agent, stepCountIs } from "@convex-dev/agent"; import { type LanguageModel } from "ai"; import type { ActionCtx } from "./_generated/server"; import type { Id } from "./_generated/dataModel"; import { components } from "./_generated/api"; function createAuthorAgent( ctx: ActionCtx, bookId: Id<"books">, model: LanguageModel, ) { return new Agent(components.agent, { name: "Author", languageModel: model, tools: { // See https://docs.convex.dev/agents/tools getChapter: getChapterTool(ctx, bookId), researchCharacter: researchCharacterTool(ctx, bookId), writeChapter: writeChapterTool(ctx, bookId), }, stopWhen: stepCountIs(10), }); } ``` -------------------------------- ### Sending a Message with File Reference Source: https://github.com/get-convex/agent/blob/main/docs/files.mdx This code shows how to send a user message that includes a file or image reference. It retrieves file parts using `getFile` and includes the `fileId` in the message metadata. This is crucial for tracking file usage. ```typescript // in your mutation const { filePart, imagePart } = await getFile(ctx, components.agent, fileId); const { messageId } = await fileAgent.saveMessage(ctx, { threadId, message: { role: "user", content: [ imagePart ?? filePart, // if it's an image, prefer that kind. { type: "text", text: "What is this image?" }, ], }, metadata: { fileIds: [fileId] }, // IMPORTANT: this tracks the file usage. }); ``` -------------------------------- ### Saving a File with storeFile Source: https://github.com/get-convex/agent/blob/main/docs/files.mdx This snippet demonstrates how to save a file to storage using the `storeFile` function. It requires the file content as a Blob, along with optional filename and SHA256 hash for metadata. The returned `file` object contains `fileId`, `url`, and `storageId`. ```typescript import { storeFile } from "@convex-dev/agent"; import { components } from "./_generated/api"; const { file } = await storeFile( ctx, components.agent, new Blob([bytes], { type: mimeType }), { filename, sha256, }, ); const { fileId, url, storageId } = file; ``` -------------------------------- ### Implement Tool-based RAG with Convex Agent Source: https://github.com/get-convex/agent/blob/main/docs/rag.mdx Exposes a search function as a tool, allowing the LLM to decide when to retrieve context. This approach maintains the tool call and response in the message history. ```ts searchContext: createTool({ description: "Search for context related to this user prompt", args: z.object({ query: z.string().describe("Describe the context you're looking for") }), handler: async (ctx, { query }) => { const context = await rag.search(ctx, { namespace: userId, query }); return context.text; }, }), ``` -------------------------------- ### Update third-party providers Source: https://github.com/get-convex/agent/blob/main/MIGRATION.md Update third-party provider packages to ensure compatibility with AI SDK v6. ```bash # For OpenRouter npm install @openrouter/ai-sdk-provider@^2.0.0 # For other providers, check their documentation for AI SDK v6 compatibility ``` -------------------------------- ### Perform Pre-flight Rate Limit Checks Source: https://github.com/get-convex/agent/blob/main/docs/rate-limiting.mdx Use these checks before processing a request to verify frequency limits and token allowance. The 'limit' method consumes tokens immediately, while 'check' validates capacity without immediate consumption. ```typescript // In the mutation that would start generating a message. await rateLimiter.limit(ctx, "sendMessage", { key: userId, throws: true }); // Also check global limit. await rateLimiter.limit(ctx, "globalSendMessage", { throws: true }); // A heuristic based on the previous token usage in the thread + the question. const count = await estimateTokens(ctx, args.threadId, args.question); // Check token usage, but don't consume the tokens yet. await rateLimiter.check(ctx, "tokenUsage", { key: userId, count: estimateTokens(args.question), throws: true, }); // Also check global limit. await rateLimiter.check(ctx, "globalTokenUsage", { count, reserve: true, throws: true, }); ``` -------------------------------- ### Consume Streams on the Client Source: https://github.com/get-convex/agent/blob/main/docs/streaming.mdx Demonstrates how to fetch streaming messages using the useUIMessages hook and how to render them smoothly using the useSmoothText hook or SmoothText component. ```typescript // Fetching messages with streaming enabled const { results, status, loadMore } = useUIMessages( api.chat.streaming.listMessages, { threadId }, { initialNumItems: 10, stream: true } ); // Smoothing text output import { useSmoothText, SmoothText } from "@convex-dev/agent/react"; // Hook usage const [visibleText] = useSmoothText(message.text, { startStreaming: message.status === "streaming", }); // Component usage ; ``` -------------------------------- ### Configure Default Agent Settings Source: https://github.com/get-convex/agent/blob/main/docs/agent-usage.mdx Define default configurations for an agent, including language and embedding models, context and storage options, and custom handlers for usage, context, and raw responses. This configuration can be extended at LLM call sites. ```typescript import { tool, stepCountIs, } from "ai"; import { openai, } from "@ai-sdk/openai"; import { z, } from "zod/v3"; import { Agent, createTool, type Config, } from "@convex-dev/agent"; import { components, } from "./_generated/api"; const sharedDefaults = { // The language model to use for the agent. languageModel: openai.chat("gpt-4o-mini"), // Embedding model to power vector search of message history (RAG). embeddingModel: openai.embedding("text-embedding-3-small"), // Used for fetching context messages. See https://docs.convex.dev/agents/context contextOptions, // Used for storing messages. See https://docs.convex.dev/agents/messages storageOptions, // Used for tracking token usage. See https://docs.convex.dev/agents/usage-tracking usageHandler: async (ctx, args) => { const { usage, model, provider, agentName, threadId, userId } = args; // ... log, save usage to your database, etc. }, // Used for filtering, modifying, or enriching the context messages. See https://docs.convex.dev/agents/context contextHandler: async (ctx, args) => { return [...customMessages, args.allMessages]; }, // Useful if you want to log or record every request and response. rawResponseHandler: async (ctx, args) => { const { request, response, agentName, threadId, userId } = args; // ... log, save request/response to your database, etc. }, // Used for limiting the number of retries when a tool call fails. Default: 3. callSettings: { maxRetries: 3, temperature: 1.0 }, } satisfies Config; const supportAgent = new Agent(components.agent, { // The default system prompt if not over-ridden. instructions: "You are a helpful assistant.", tools: { // Convex tool. See https://docs.convex.dev/agents/tools myConvexTool: createTool({ description: "My Convex tool", inputSchema: z.object({...}), // Note: annotate the return type of the execute to avoid type cycles. execute: async (ctx, input): Promise => { return "Hello, world!"; }, }), // Standard AI SDK tool myTool: tool({ description: "My tool", inputSchema: z.object({...}), execute: async () => {}}), }, // Used for limiting the number of steps when tool calls are involved. // NOTE: if you want tool calls to happen automatically with a single call, // you need to set this to something greater than 1 (the default). stopWhen: stepCountIs(5), ...sharedDefaults, }); ``` -------------------------------- ### Estimate Token Usage Source: https://github.com/get-convex/agent/blob/main/docs/rate-limiting.mdx Calculate a rough estimate of tokens required for a prompt and context. ```ts import { QueryCtx } from "./_generated/server"; import { fetchContextMessages } from "@convex-dev/agent"; import { components } from "./_generated/api"; // This is a rough estimate of the tokens that will be used. // It's not perfect, but it's a good enough estimate for a pre-generation check. export async function estimateTokens( ctx: QueryCtx, threadId: string | undefined, question: string, ) { // Assume roughly 4 characters per token const promptTokens = question.length / 4; // Assume a longer non-zero reply const estimatedOutputTokens = promptTokens * 3 + 1; const latestMessages = await fetchContextMessages(ctx, components.agent, { threadId, searchText: question, contextOptions: { recentMessages: 2 }, }); // Our new usage will roughly be the previous tokens + the question. // The previous tokens include the tokens for the full message history and // output tokens, which will be part of our new history. const lastUsageMessage = latestMessages .reverse() .find((message) => message.usage); const lastPromptTokens = lastUsageMessage?.usage?.totalTokens ?? 1; return lastPromptTokens + promptTokens + estimatedOutputTokens; } ``` -------------------------------- ### Expose agent as structured object action Source: https://github.com/get-convex/agent/blob/main/docs/workflows.mdx Exposes an agent's capability to generate structured data as a Convex action. ```ts // Similar to thread.generateObject / thread.streamObject export const getStructuredSupport = supportAgent.asObjectAction({ schema: z.object({ analysis: z.string().describe("A detailed analysis of the user's request."), instruction: z.string().describe("A suggested action to take."), }), }); ``` -------------------------------- ### Generate Text Reply with Agent Source: https://github.com/get-convex/agent/blob/main/docs/agent-usage.mdx Use an Agent to generate a text reply to a prompt within a specified thread. Best practice is to query for thread messages via a hook to receive new messages automatically. ```typescript export const generateReplyToPrompt = action({ args: { prompt: v.string(), threadId: v.string() }, handler: async (ctx, { prompt, threadId }) => { // await authorizeThreadAccess(ctx, threadId); const result = await agent.generateText(ctx, { threadId }, { prompt }); return result.text; }, }); ``` -------------------------------- ### Token Usage Rate Limiting Source: https://github.com/get-convex/agent/blob/main/docs/rate-limiting.mdx Configuration for per-user and global token usage limits. ```ts tokenUsage: { kind: "token bucket", period: MINUTE, rate: 1_000 } globalTokenUsage: { kind: "token bucket", period: MINUTE, rate: 100_000 }, ``` -------------------------------- ### Update maxSteps to stopWhen Source: https://github.com/get-convex/agent/blob/main/MIGRATION.md Transition from using 'maxSteps' to 'stopWhen' for controlling agent execution flow. ```typescript // Before await agent.generateText(ctx, { threadId }, { prompt: "...", maxSteps: 5 }) // After (maxSteps still works, but stopWhen is preferred) import { stepCountIs } from "ai" await agent.generateText(ctx, { threadId }, { prompt: "...", stopWhen: stepCountIs(5) }) ``` -------------------------------- ### Update official AI SDK providers Source: https://github.com/get-convex/agent/blob/main/MIGRATION.md Update provider packages to v3.x to match AI SDK v6 requirements. ```bash # For OpenAI npm install @ai-sdk/openai@^3.0.10 # For Anthropic npm install @ai-sdk/anthropic@^3.0.13 # For Groq npm install @ai-sdk/groq@^3.0.8 # For Google (Gemini) npm install @ai-sdk/google@^3.0.8 ``` -------------------------------- ### Log context messages Source: https://github.com/get-convex/agent/blob/main/docs/debugging.mdx Use contextHandler to inspect the full message history being sent to the LLM. ```ts const supportAgent = new Agent(components.agent, { ... contextHandler: async (ctx, { allMessages }) => { console.log("context", allMessages); return allMessages; }, }); ``` -------------------------------- ### Expose Rate Limit API Source: https://github.com/get-convex/agent/blob/main/docs/rate-limiting.mdx Define the rate limit hook API in the Convex backend. ```ts export const { getRateLimit, getServerTime } = rateLimiter.hookAPI( "sendMessage", { key: (ctx) => getAuthUserId(ctx) }, ); ``` -------------------------------- ### Use Rate Limit Hook Source: https://github.com/get-convex/agent/blob/main/docs/rate-limiting.mdx Initialize the rate limit check within a React component. ```ts import { useRateLimit } from "@convex-dev/rate-limiter/react"; //... const { status } = useRateLimit(api.example.getRateLimit); ``` -------------------------------- ### Global Message Rate Limiting Source: https://github.com/get-convex/agent/blob/main/docs/rate-limiting.mdx Configuration for global message rate limiting using a token bucket strategy. ```ts globalSendMessage: { kind: "token bucket", period: MINUTE, rate: 1_000 }, ``` -------------------------------- ### Define Tool for Asking a Human Source: https://github.com/get-convex/agent/blob/main/docs/human-agents.mdx Defines a tool named `askHuman` using `ai.tool` for LLM-generated prompts that require human input. It specifies the tool's description and expected parameters. ```typescript import { tool } from "ai"; import { z } from "zod/v3"; const askHuman = tool({ description: "Ask a human a question", parameters: z.object({ question: z.string().describe("The question to ask the human"), }), }); ``` -------------------------------- ### List Thread Messages Query Source: https://github.com/get-convex/agent/blob/main/docs/messages.mdx Expose a query to retrieve paginated messages for a thread. Requires thread access authorization and uses `listUIMessages` for UI-friendly message formatting. ```typescript import { paginationOptsValidator } from "convex/server"; import { v } from "convex/values"; import { listUIMessages } from "@convex-dev/agent"; import { components } from "./_generated/api"; export const listThreadMessages = query({ args: { threadId: v.string(), paginationOpts: paginationOptsValidator }, handler: async (ctx, args) => { await authorizeThreadAccess(ctx, threadId); const paginated = await listUIMessages(ctx, components.agent, args); // Here you could filter out / modify the documents return paginated; }, }); ``` -------------------------------- ### Convex Cron Job for Generating Invoices Source: https://github.com/get-convex/agent/blob/main/docs/usage-tracking.mdx Sets up a monthly cron job in `convex/crons.ts` to trigger the `generateInvoices` function on the 2nd day of every month at 00:00 UTC. This is used for processing end-of-period billing. ```typescript import { cronJobs } from "convex/server"; import { internal } from "./_generated/api"; const crons = cronJobs(); // Generate invoices for the previous month crons.monthly( "generateInvoices", // Wait a day after the new month starts to generate invoices { day: 2, hourUTC: 0, minuteUTC: 0 }, internal.usage.generateInvoices, {}, ); export default crons; ``` -------------------------------- ### Define Tools with Approval Source: https://github.com/get-convex/agent/blob/main/docs/tool-approval.mdx Add a needsApproval property to tools created with createTool. This can be a boolean or an async function that evaluates the tool context and input. ```ts import { createTool } from "@convex-dev/agent"; import { z } from "zod/v4"; // Always requires approval const deleteFileTool = createTool({ description: "Delete a file from the system", inputSchema: z.object({ filename: z.string().describe("The name of the file to delete"), }), needsApproval: () => true, execute: async (_ctx, input) => { return `Deleted file: ${input.filename}`; }, }); // Conditionally requires approval (only for large amounts) const transferMoneyTool = createTool({ description: "Transfer money to an account", inputSchema: z.object({ amount: z.number().describe("The amount to transfer"), toAccount: z.string().describe("The destination account"), }), needsApproval: async (_ctx, input) => { return input.amount > 100; }, execute: async (_ctx, input) => { return `Transferred $${input.amount} to ${input.toAccount}`; }, }); ``` -------------------------------- ### Handle Human Response as Tool Result Source: https://github.com/get-convex/agent/blob/main/docs/human-agents.mdx This internal action processes a human's response to a tool call. It saves the response as a tool result using `agent.saveMessage` and then continues LLM generation. ```typescript export const humanResponseAsToolCall = internalAction({ args: { humanName: v.string(), response: v.string(), toolCallId: v.string(), threadId: v.string(), messageId: v.string(), }, handler: async (ctx, args) => { await agent.saveMessage(ctx, { threadId: args.threadId, message: { role: "tool", content: [ { type: "tool-result", result: args.response, toolCallId: args.toolCallId, toolName: "askHuman", }, ], }, metadata: { provider: "human", providerMetadata: { human: { name: args.humanName }, }, }, }); // Continue generating a response from the LLM await agent.generateText( ctx, { threadId: args.threadId }, { promptMessageId: args.messageId, } ); }, }); ``` -------------------------------- ### Parse images with LLM Source: https://github.com/get-convex/agent/blob/main/docs/rag.mdx Uses generateText to transcribe image content for search indexing. ```ts const description = await thread.generateText({ message: { role: "user", content: [{ type: "image", data: url, mimeType: blob.type }], }, }); ``` -------------------------------- ### Configure custom agent context Source: https://github.com/get-convex/agent/blob/main/docs/tools.mdx Defines a custom context type for the agent to allow passing additional metadata like orgId. ```ts const myAgent = new Agent<{ orgId: string }>(...); ``` ```ts type MyCtx = ToolCtx & { orgId: string }; const myTool = createTool({ args: z.object({ ... }), description: "...", handler: async (ctx: MyCtx, args) => { // use ctx.orgId }, }); ``` -------------------------------- ### Generate structured objects with Zod Source: https://github.com/get-convex/agent/blob/main/docs/agent-usage.mdx Uses a Zod schema to enforce the structure of generated content. Note that object generation does not support tool usage directly. ```ts import { z } from "zod/v3"; const result = await thread.generateObject({ prompt: "Generate a plan based on the conversation so far", schema: z.object({...}), }); ``` -------------------------------- ### Define Usage Handler for Database Insertion Source: https://github.com/get-convex/agent/blob/main/docs/usage-tracking.mdx Implement a `usageHandler` that checks for a user ID and then uses a Convex mutation to insert raw usage data into a database table. Ensure `userId` is present before proceeding. ```typescript export const usageHandler: UsageHandler = async (ctx, args) => { if (!args.userId) { console.debug("Not tracking usage for anonymous user"); return; } await ctx.runMutation(internal.example.insertRawUsage, { userId: args.userId, agentName: args.agentName, model: args.model, provider: args.provider, usage: args.usage, providerMetadata: args.providerMetadata, }); }; ``` -------------------------------- ### Fetch Context Messages Manually Source: https://github.com/get-convex/agent/blob/main/docs/context.mdx Use `fetchContextWithPrompt` to retrieve context messages for a given prompt without performing an LLM generation. This function is used internally by the AI SDK and can be useful for debugging or custom workflows. ```typescript import { fetchContextWithPrompt } from "@convex-dev/agent"; const { messages } = await fetchContextWithPrompt(ctx, components.agent, { prompt, messages, promptMessageId, userId, threadId, contextOptions, }); ``` -------------------------------- ### Expose agent as text action Source: https://github.com/get-convex/agent/blob/main/docs/workflows.mdx Exposes an agent's text generation capability as a Convex action for use in workflows. ```ts // Similar to thread.generateText / thread.streamText export const getSupport = supportAgent.asTextAction({ stopWhen: stepCountIs(10), }); ```