import { type NextRequest, NextResponse } from "next/server" import type { ChatRequest, ChatResponse } from "@/lib/types" import { getFlags } from "@/lib/flags" /** * Get webhook URL for a specific agent from environment variables * Format: AGENT_{agentIndex}_URL */ function getAgentWebhookUrl(agentId: string): string | null { // Extract agent index from agentId (format: "agent-1", "agent-2", etc.) const match = agentId.match(/agent-(\d+)/) if (!match) { console.error("[chat] Invalid agentId format:", agentId) return null } const agentIndex = match[1] const urlKey = `AGENT_${agentIndex}_URL` const webhookUrl = process.env[urlKey] if (!webhookUrl) { console.error(`[chat] No webhook URL configured for ${urlKey}`) return null } return webhookUrl } // Helper function to convert diff tool call to markdown format function convertToDiffTool(args: any, diffToolEnabled: boolean): string { try { const { oldCode, newCode, title, language } = args if (!oldCode || !newCode) { return "Error: Missing oldCode or newCode in diff tool call" } // If diff tool is disabled, return as plain code blocks if (!diffToolEnabled) { const titleText = title || "Code Changes" return `### ${titleText}\n\n**Before:**\n\`\`\`${language || 'text'}\n${oldCode}\n\`\`\`\n\n**After:**\n\`\`\`${language || 'text'}\n${newCode}\n\`\`\`` } const diffToolCall = { oldCode: String(oldCode).replace(/\n/g, '\\n'), newCode: String(newCode).replace(/\n/g, '\\n'), title: title || "Code Changes", language: language || "text" } return `\`\`\`diff-tool\n${JSON.stringify(diffToolCall, null, 2)}\n\`\`\`` } catch (error) { console.error("[v0] Error converting diff tool:", error) return "Error: Failed to process diff tool call" } } export async function POST(request: NextRequest): Promise> { try { const body = await request.json() if (typeof body !== "object" || body === null) { return NextResponse.json({ error: "Invalid request body" }, { status: 400 }) } const { message, timestamp, sessionId, agentId, images } = body as ChatRequest // Get feature flags const flags = getFlags() // Validate required fields if (!message || typeof message !== "string") { return NextResponse.json({ error: "Message is required" }, { status: 400 }) } if (!agentId || typeof agentId !== "string") { return NextResponse.json({ error: "Agent ID is required" }, { status: 400 }) } // Check if image uploads are enabled if (images && images.length > 0 && !flags.IMAGE_UPLOADS_ENABLED) { return NextResponse.json( { error: "Image uploads are currently disabled", hint: "Contact your administrator to enable the IMAGE_UPLOADS_ENABLED flag" }, { status: 403 } ) } // Get webhook URL for the selected agent const webhookUrl = getAgentWebhookUrl(agentId) if (!webhookUrl) { return NextResponse.json( { error: `Agent ${agentId} is not properly configured` }, { status: 400 }, ) } console.log("[chat] Sending to webhook:", { agentId, message, timestamp, sessionId }) const response = await fetch(webhookUrl, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ message, timestamp, sessionId, agentId, images: images && images.length > 0 ? images : undefined, }), }) console.log("[v0] Webhook response status:", response.status) const responseText = await response.text() console.log("[v0] Webhook response body (first 200 chars):", responseText.substring(0, 200)) if (!response.ok) { // Try to parse as JSON if possible, otherwise use text let errorData try { errorData = responseText ? JSON.parse(responseText) : {} } catch { errorData = { message: responseText || "Unknown error" } } console.error("[v0] Webhook error:", errorData) return NextResponse.json( { error: errorData.message || "Failed to communicate with webhook", hint: errorData.hint, code: errorData.code, }, { status: response.status }, ) } if (!responseText) { console.log("[v0] Empty response from webhook") return NextResponse.json({ response: "The webhook received your message but didn't return a response. Please ensure your n8n workflow includes a 'Respond to Webhook' node that returns data.", hint: "Add a 'Respond to Webhook' node in your n8n workflow to send responses back to the chat.", }) } try { // Split response by newlines to get individual JSON objects const lines = responseText.trim().split("\n") const chunks: string[] = [] for (const line of lines) { if (!line.trim()) continue try { const chunk = JSON.parse(line) // Extract content from "item" type chunks if (chunk.type === "item" && chunk.content) { chunks.push(chunk.content) } // Handle diff tool calls if (chunk.type === "tool_call" && chunk.name === "show_diff") { const diffTool = convertToDiffTool(chunk.args, flags.DIFF_TOOL_ENABLED) chunks.push(diffTool) } } catch { console.log("[v0] Failed to parse line:", line) } } // Combine all chunks into a single message if (chunks.length > 0) { const fullMessage = chunks.join("") console.log("[v0] Combined message from", chunks.length, "chunks") return NextResponse.json({ response: fullMessage }) } // If no chunks found, try parsing as regular JSON const data = JSON.parse(responseText) console.log("[v0] Parsed webhook data:", data) // Check if this is a diff tool call if (data.type === "tool_call" && data.name === "show_diff") { const diffTool = convertToDiffTool(data.args, flags.DIFF_TOOL_ENABLED) return NextResponse.json({ response: diffTool }) } // Extract the response from various possible fields let responseMessage = data.response || data.message || data.output || data.text // If the response is an object, try to extract from nested fields if (typeof responseMessage === "object") { responseMessage = responseMessage.response || responseMessage.message || responseMessage.output || responseMessage.text } // If still no message found, stringify the entire response if (!responseMessage) { responseMessage = JSON.stringify(data) } return NextResponse.json({ response: responseMessage }) } catch { console.log("[v0] Response is not JSON, returning as text") // If not JSON, return the text as the response return NextResponse.json({ response: responseText }) } } catch (error) { console.error("[v0] API route error:", error) return NextResponse.json({ error: "Internal server error" }, { status: 500 }) } }