"use server"; import { generateObject, generateText, jsonSchema, LanguageModel, type UIMessage, } from "ai"; import { CREATE_THREAD_TITLE_PROMPT, generateExampleToolSchemaPrompt, } from "lib/ai/prompts"; import type { ChatModel, ChatThread } from "app-types/chat"; import { agentRepository, chatExportRepository, chatRepository, mcpMcpToolCustomizationRepository, mcpServerCustomizationRepository, } from "lib/db/repository"; import { customModelProvider } from "lib/ai/models"; import { toAny } from "lib/utils"; import { McpServerCustomizationsPrompt, MCPToolInfo } from "app-types/mcp"; import { serverCache } from "lib/cache"; import { CacheKeys } from "lib/cache/cache-keys"; import { getSession } from "auth/server"; import logger from "logger"; import { JSONSchema7 } from "json-schema"; import { ObjectJsonSchema7 } from "app-types/util"; import { jsonSchemaToZod } from "lib/json-schema-to-zod"; import { Agent } from "app-types/agent"; export async function getUserId() { const session = await getSession(); const userId = session?.user?.id; if (!userId) { throw new Error("User not found"); } return userId; } export async function generateTitleFromUserMessageAction({ message, model, }: { message: UIMessage; model: LanguageModel }) { const session = await getSession(); if (!session) { throw new Error("Unauthorized"); } const prompt = toAny(message.parts?.at(-1))?.text || "unknown"; const { text: title } = await generateText({ model, system: CREATE_THREAD_TITLE_PROMPT, prompt, }); return title.trim(); } export async function selectThreadWithMessagesAction(threadId: string) { const session = await getSession(); if (!session) { throw new Error("Unauthorized"); } const thread = await chatRepository.selectThread(threadId); if (!thread) { logger.error("Thread not found", threadId); return null; } if (thread.userId !== session?.user.id) { return null; } const messages = await chatRepository.selectMessagesByThreadId(threadId); return { ...thread, messages: messages ?? [] }; } export async function deleteMessageAction(messageId: string) { await chatRepository.deleteChatMessage(messageId); } export async function deleteThreadAction(threadId: string) { await chatRepository.deleteThread(threadId); } export async function deleteMessagesByChatIdAfterTimestampAction( messageId: string, ) { "use server"; await chatRepository.deleteMessagesByChatIdAfterTimestamp(messageId); } export async function updateThreadAction( id: string, thread: Partial>, ) { const userId = await getUserId(); await chatRepository.updateThread(id, { ...thread, userId }); } export async function deleteThreadsAction() { const userId = await getUserId(); await chatRepository.deleteAllThreads(userId); } export async function deleteUnarchivedThreadsAction() { const userId = await getUserId(); await chatRepository.deleteUnarchivedThreads(userId); } export async function generateExampleToolSchemaAction(options: { model?: ChatModel; toolInfo: MCPToolInfo; prompt?: string; }) { const model = customModelProvider.getModel(options.model); const schema = jsonSchema( toAny({ ...options.toolInfo.inputSchema, properties: options.toolInfo.inputSchema?.properties ?? {}, additionalProperties: false, }), ); const { object } = await generateObject({ model, schema, prompt: generateExampleToolSchemaPrompt({ toolInfo: options.toolInfo, prompt: options.prompt, }), }); return object; } export async function rememberMcpServerCustomizationsAction(userId: string) { const key = CacheKeys.mcpServerCustomizations(userId); const cachedMcpServerCustomizations = await serverCache.get>(key); if (cachedMcpServerCustomizations) { return cachedMcpServerCustomizations; } const mcpServerCustomizations = await mcpServerCustomizationRepository.selectByUserId(userId); const mcpToolCustomizations = await mcpMcpToolCustomizationRepository.selectByUserId(userId); const serverIds: string[] = [ ...mcpServerCustomizations.map( (mcpServerCustomization) => mcpServerCustomization.mcpServerId, ), ...mcpToolCustomizations.map( (mcpToolCustomization) => mcpToolCustomization.mcpServerId, ), ]; const prompts = Array.from(new Set(serverIds)).reduce( (acc, serverId) => { const sc = mcpServerCustomizations.find((v) => v.mcpServerId == serverId); const tc = mcpToolCustomizations.filter( (mcpToolCustomization) => mcpToolCustomization.mcpServerId === serverId, ); const data: McpServerCustomizationsPrompt = { name: sc?.serverName || tc[0]?.serverName || "", id: serverId, prompt: sc?.prompt || "", tools: tc.reduce( (acc, v) => { acc[v.toolName] = v.prompt || ""; return acc; }, {} as Record, ), }; acc[serverId] = data; return acc; }, {} as Record, ); serverCache.set(key, prompts, 1000 * 60 * 30); // 30 minutes return prompts; } export async function generateObjectAction({ model, prompt, schema, }: { model?: ChatModel; prompt: { system?: string; user?: string; }; schema: JSONSchema7 | ObjectJsonSchema7; }) { const result = await generateObject({ model: customModelProvider.getModel(model), system: prompt.system, prompt: prompt.user || "", schema: jsonSchemaToZod(schema), }); return result.object; } export async function rememberAgentAction( agent: string | undefined, userId: string, ) { if (!agent) return undefined; const key = CacheKeys.agentInstructions(agent); let cachedAgent = await serverCache.get(key); if (!cachedAgent) { cachedAgent = await agentRepository.selectAgentById(agent, userId); await serverCache.set(key, cachedAgent); } return cachedAgent as Agent | undefined; } export async function exportChatAction({ threadId, expiresAt, }: { threadId: string; expiresAt?: Date; }) { const userId = await getUserId(); const isAccess = await chatRepository.checkAccess(threadId, userId); if (!isAccess) { return new Response("Unauthorized", { status: 401 }); } return await chatExportRepository.exportChat({ threadId, exporterId: userId, expiresAt: expiresAt ?? undefined, }); }