UNPKG

create-mf2-app

Version:

The stack AI moves fast with.

108 lines (101 loc) 3.24 kB
import { components } from "./_generated/api"; import { v } from "convex/values"; import { action, ActionCtx, mutation, MutationCtx, query, QueryCtx, } from "./_generated/server.js"; import { paginationOptsValidator, PaginationResult } from "convex/server"; import { createThread, getThreadMetadata, saveMessage, vMessage, } from "@convex-dev/agent"; import { getAuthUserId, getAuthUserIdAsString } from "./utils"; import { agent } from "./agents/simple"; import z from "zod"; export const listThreads = query({ args: { paginationOpts: paginationOptsValidator, }, handler: async (ctx, args): Promise<PaginationResult<any>> => { const userId = await getAuthUserIdAsString(ctx); const threads = await ctx.runQuery( components.agent.threads.listThreadsByUserId, { userId, paginationOpts: args.paginationOpts } ); return threads; }, }); export const createNewThread = mutation({ args: { title: v.optional(v.string()), initialMessage: v.optional(vMessage) }, handler: async (ctx, { title, initialMessage }) => { const userId = await getAuthUserIdAsString(ctx); const threadId = await createThread(ctx, components.agent, { userId, title, }); if (initialMessage) { await saveMessage(ctx, components.agent, { threadId, message: initialMessage, }); } return threadId; }, }); export const getThreadDetails = query({ args: { threadId: v.string() }, handler: async (ctx, { threadId }) => { await authorizeThreadAccess(ctx, threadId); const { title, summary } = await getThreadMetadata(ctx, components.agent, { threadId, }); return { title, summary }; }, }); export const updateThreadTitle = action({ args: { threadId: v.string() }, handler: async (ctx, { threadId }) => { await authorizeThreadAccess(ctx, threadId); const { thread } = await agent.continueThread(ctx, { threadId }); const { object: { title, summary }, } = await thread.generateObject( { mode: "json", schemaDescription: "Generate a title and summary for the thread. The title should be a single sentence that captures the main topic of the thread. The summary should be a short description of the thread that could be used to describe it to someone who hasn't read it.", schema: z.object({ title: z.string().describe("The new title for the thread"), summary: z.string().describe("The new summary for the thread"), }), prompt: "Generate a title and summary for this thread.", }, { storageOptions: { saveMessages: "none" } } ); await thread.updateMetadata({ title, summary }); }, }); export async function authorizeThreadAccess( ctx: QueryCtx | MutationCtx | ActionCtx, threadId: string, requireUser?: boolean ) { const userId = await getAuthUserIdAsString(ctx); if (requireUser && !userId) { throw new Error("Unauthorized: user is required"); } const { userId: threadUserId } = await getThreadMetadata( ctx, components.agent, { threadId } ); if (requireUser && threadUserId !== userId) { throw new Error("Unauthorized: user does not match thread user"); } }