import { Router } from "express"; import { getSession, addMessage, getConversationForAPI } from "../services/sessionStore.js"; const router = Router(); const ANTHROPIC_API_URL = "https://api.anthropic.com"; /** * Build entity context string for the system prompt */ function buildEntityContext(entities) { if (!entities || entities.length === 0) { return "\n\nThe diagram is currently empty."; } const entityList = entities.map(e => { const shape = e.template?.replace('#', '').replace('-template', '') || 'unknown'; const pos = e.position || { x: 0, y: 0, z: 0 }; return `- ${e.text || '(no label)'} (${shape}, ${e.color || 'unknown'}) at (${pos.x?.toFixed(1)}, ${pos.y?.toFixed(1)}, ${pos.z?.toFixed(1)})`; }).join('\n'); return `\n\n## Current Diagram State\nThe diagram currently contains ${entities.length} entities:\n${entityList}`; } // Express 5 uses named parameters for wildcards router.post("/*path", async (req, res) => { const apiKey = process.env.ANTHROPIC_API_KEY; if (!apiKey) { return res.status(500).json({ error: "API key not configured" }); } // Get the path after /api/claude (e.g., /v1/messages) // Express 5 returns path segments as an array const pathParam = req.params.path; const path = "/" + (Array.isArray(pathParam) ? pathParam.join("/") : pathParam || ""); // Check for session-based request const { sessionId, ...requestBody } = req.body; let modifiedBody = requestBody; if (sessionId) { const session = getSession(sessionId); if (session) { console.log(`[Claude API] Session ${sessionId}: ${session.entities.length} entities, ${session.conversationHistory.length} messages in history`); // Inject entity context into system prompt if (modifiedBody.system) { const entityContext = buildEntityContext(session.entities); console.log(`[Claude API] Entity context:`, entityContext); modifiedBody.system += entityContext; } // Get conversation history and merge with current messages const historyMessages = getConversationForAPI(sessionId); if (historyMessages.length > 0 && modifiedBody.messages) { // Filter out any duplicate messages (in case client sent history too) const currentContent = modifiedBody.messages[modifiedBody.messages.length - 1]?.content; const filteredHistory = historyMessages.filter(msg => msg.content !== currentContent); modifiedBody.messages = [...filteredHistory, ...modifiedBody.messages]; console.log(`[Claude API] Merged ${filteredHistory.length} history messages with ${modifiedBody.messages.length - filteredHistory.length} new messages`); } } else { console.log(`[Claude API] Session ${sessionId} not found`); } } try { const response = await fetch(`${ANTHROPIC_API_URL}${path}`, { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": apiKey, "anthropic-version": "2023-06-01", }, body: JSON.stringify(modifiedBody), }); const data = await response.json(); // If session exists and response is successful, store messages if (sessionId && response.ok && data.content) { const session = getSession(sessionId); if (session) { // Store the user message if it was new (only if it's a string, not tool results) const userMessage = requestBody.messages?.[requestBody.messages.length - 1]; if (userMessage && userMessage.role === 'user' && typeof userMessage.content === 'string') { addMessage(sessionId, { role: 'user', content: userMessage.content }); } // Store the assistant response (text only, not tool use blocks) const assistantContent = data.content .filter(c => c.type === 'text') .map(c => c.text) .join('\n'); if (assistantContent) { addMessage(sessionId, { role: 'assistant', content: assistantContent }); } } } res.status(response.status).json(data); } catch (error) { console.error("Claude API error:", error.message); res.status(500).json({ error: "Failed to proxy request to Claude API" }); } }); export default router;