import { openai } from "@/lib/openai-client"; import { hf } from "@/lib/hf-client"; import { getModels, type ModelID } from "@/lib/models"; import { saveChat } from "@/lib/chat-store"; import { nanoid } from "nanoid"; import { db } from "@/lib/db"; import { chats } from "@/lib/db/schema"; import { eq, and } from "drizzle-orm"; import { initializeMCPClients, type MCPServerConfig } from "@/lib/mcp-client"; import { generateTitle } from "@/app/actions"; import { createOpenAIStream } from "@/lib/openai-stream"; import type { ChatCompletionTool, ChatCompletionMessageParam, } from "openai/resources"; export const runtime = "nodejs"; // Allow streaming responses up to 120 seconds export const maxDuration = 120; export const dynamic = "force-dynamic"; function mcpToolsToOpenAITools( tools: Record ): ChatCompletionTool[] { return Object.entries(tools).map( ([name, schema]): ChatCompletionTool => ({ type: "function", function: { name, parameters: schema.parameters }, }) ); } export async function POST(req: Request) { const { messages, chatId, selectedModel, userId, mcpServers = [], }: { messages: ChatCompletionMessageParam[]; chatId?: string; selectedModel: ModelID; userId: string; mcpServers?: MCPServerConfig[]; } = await req.json(); if (!userId) { return new Response(JSON.stringify({ error: "User ID is required" }), { status: 400, headers: { "Content-Type": "application/json" }, }); } const id = chatId || nanoid(); // Check if chat already exists for the given ID // If not, create it now let isNewChat = false; if (chatId) { try { const existingChat = await db.query.chats.findFirst({ where: and(eq(chats.id, chatId), eq(chats.userId, userId)), }); isNewChat = !existingChat; } catch (error) { console.error("Error checking for existing chat:", error); isNewChat = true; } } else { // No ID provided, definitely new isNewChat = true; } // If it's a new chat, save it immediately if (isNewChat && messages.length > 0) { try { // Generate a title based on first user message const userMessage = messages.find((m) => m.role === "user"); let title = "New Chat"; if (userMessage && typeof userMessage.content === "string") { try { // The generateTitle function expects a UIMessage[], let's adapt title = await generateTitle([ { role: "user", content: userMessage.content, id: "temp-id" }, ]); } catch (error) { console.error("Error generating title:", error); } } // Save the chat immediately so it appears in the sidebar await saveChat({ id, userId, title, messages: [], // Messages will be saved by the client }); } catch (error) { console.error("Error saving new chat:", error); } } const { tools, cleanup } = await initializeMCPClients(mcpServers, req.signal); const hfModels = await getModels(); const client = hfModels.includes(selectedModel) ? hf : openai; const openAITools = mcpToolsToOpenAITools(tools); const completion = await client.chat.completions.create( { model: selectedModel, stream: true, messages, ...(openAITools.length > 0 && { tools: openAITools, tool_choice: "auto", }), }, { signal: req.signal } ); const stream = createOpenAIStream(completion, { onFinal() { cleanup(); }, }); return new Response(stream, { headers: { "Content-Type": "text/event-stream", "X-Chat-ID": id, }, }); }