// relay v0.3.0 // Core relay server for Lyra project // Handles incoming chat requests and forwards them to Cortex services import express from "express"; import dotenv from "dotenv"; import cors from "cors"; dotenv.config(); const app = express(); app.use(cors()); app.use(express.json()); const PORT = Number(process.env.PORT || 7078); // Cortex endpoints (only these are used now) const CORTEX_REASON = process.env.CORTEX_REASON_URL || "http://cortex:7081/reason"; // ----------------------------------------------------- // Helper request wrapper // ----------------------------------------------------- async function postJSON(url, data) { const resp = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); const raw = await resp.text(); let json; try { json = raw ? JSON.parse(raw) : null; } catch (e) { throw new Error(`Non-JSON from ${url}: ${raw}`); } if (!resp.ok) { throw new Error(json?.detail || json?.error || raw); } return json; } // ----------------------------------------------------- // The unified chat handler // ----------------------------------------------------- async function handleChatRequest(session_id, user_msg) { let reason; // 1. → Cortex.reason (main pipeline) try { reason = await postJSON(CORTEX_REASON, { session_id, user_prompt: user_msg }); } catch (e) { console.error("Relay → Cortex.reason error:", e.message); throw new Error(`cortex_reason_failed: ${e.message}`); } // Correct persona field const persona = reason.persona || reason.final_output || "(no persona text)"; // Return final answer return { session_id, reply: persona }; } // ----------------------------------------------------- // HEALTHCHECK // ----------------------------------------------------- app.get("/_health", (_, res) => { res.json({ ok: true }); }); // ----------------------------------------------------- // OPENAI-COMPATIBLE ENDPOINT // ----------------------------------------------------- app.post("/v1/chat/completions", async (req, res) => { try { const session_id = req.body.session_id || req.body.user || "default"; const messages = req.body.messages || []; const lastMessage = messages[messages.length - 1]; const user_msg = lastMessage?.content || ""; if (!user_msg) { return res.status(400).json({ error: "No message content provided" }); } console.log(`Relay (v1) → received: "${user_msg}"`); const result = await handleChatRequest(session_id, user_msg); res.json({ id: `chatcmpl-${Date.now()}`, object: "chat.completion", created: Math.floor(Date.now() / 1000), model: "lyra", choices: [{ index: 0, message: { role: "assistant", content: result.reply }, finish_reason: "stop" }], usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 } }); } catch (err) { console.error("Relay v1 fatal:", err); res.status(500).json({ error: { message: err.message || String(err), type: "server_error", code: "relay_failed" } }); } }); // ----------------------------------------------------- // MAIN ENDPOINT (Lyra-native UI) // ----------------------------------------------------- app.post("/chat", async (req, res) => { try { const session_id = req.body.session_id || "default"; const user_msg = req.body.message || ""; console.log(`Relay → received: "${user_msg}"`); const result = await handleChatRequest(session_id, user_msg); res.json(result); } catch (err) { console.error("Relay fatal:", err); res.status(500).json({ error: "relay_failed", detail: err.message || String(err) }); } }); // ----------------------------------------------------- // SESSION ENDPOINTS (for UI) // ----------------------------------------------------- // In-memory session storage (could be replaced with a database) const sessions = new Map(); app.get("/sessions/:id", (req, res) => { const sessionId = req.params.id; const history = sessions.get(sessionId) || []; res.json(history); }); app.post("/sessions/:id", (req, res) => { const sessionId = req.params.id; const history = req.body; sessions.set(sessionId, history); res.json({ ok: true, saved: history.length }); }); // ----------------------------------------------------- app.listen(PORT, () => { console.log(`Relay is online on port ${PORT}`); });