218 lines
7.1 KiB
TypeScript

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<NextResponse<ChatResponse>> {
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 })
}
}