create-mf2-app
Version:
The stack AI moves fast with.
108 lines (101 loc) • 3.24 kB
text/typescript
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");
}
}