/** * Level 4: Hermes Connection * * Integration with Hermes gateway (Telegram) * * Flow: * Telegram → Hermes → This Server → Queue → Worker → Response → Hermes → Telegram * * This creates a simple HTTP server that Hermes can call via webhook or tool. */ import { Agent, type AgentTool, type AgentMessage, type AgentEvent } from "@mariozechner/pi-agent-core"; import { registerBuiltInApiProviders } from "@mariozechner/pi-ai"; import type { Model } from "@mariozechner/pi-ai"; import { exec } from "child_process"; import http from "http"; // ============== CONFIG ============== const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY || "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"; process.env.OPENROUTER_API_KEY = OPENROUTER_API_KEY; const model: Model<"openai-responses"> = { id: "stepfun/step-3.5-flash:free", name: "Step-3.5 Flash (Free)", api: "openai-responses", provider: "openrouter", baseUrl: "https://openrouter.ai/api/v1", reasoning: false, input: ["text"], output: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128000, maxTokens: 8192, }; const PORT = process.env.PORT || 3000; // ============== TASK QUEUE (Simplified) ============== interface Task { id: string; message: string; chatId: string; status: "pending" | "running" | "completed"; response?: string; } class SimpleQueue { private tasks: Task[] = []; private processing = false; add(message: string, chatId: string): string { const id = `task-${Date.now()}`; this.tasks.push({ id, message, chatId, status: "pending" }); return id; } getNext(): Task | undefined { const task = this.tasks.find(t => t.status === "pending"); if (task) { task.status = "running"; } return task; } complete(id: string, response: string) { const task = this.tasks.find(t => t.id === id); if (task) { task.status = "completed"; task.response = response; } } getByChat(chatId: string): Task | undefined { return this.tasks.find(t => t.chatId === chatId && t.status === "completed"); } size(): number { return this.tasks.length; } } // ============== AGENT ============== class HermesAgent { private agent: Agent; private chatId?: string; constructor(chatId?: string) { this.chatId = chatId; this.agent = new Agent({ initialState: { systemPrompt: "You are a helpful coding assistant. Be concise and helpful.", model: model, tools: [] as any, messages: [], }, convertToLlm: (messages: AgentMessage[]) => { return messages .filter((m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult") .map((m) => ({ role: m.role, content: m.content })); }, }); } async process(message: string): Promise { let response = ""; this.agent.subscribe((event) => { if (event.type === "message_update") { const ev = event as any; if (ev.assistantMessageEvent?.type === "text_delta") { response += ev.assistantMessageEvent.delta || ""; } } }); await this.agent.prompt(message); return response || "No response"; } } // ============== HTTP SERVER ============== const queue = new SimpleQueue(); const agent = new HermesAgent(); const server = http.createServer(async (req, res) => { // CORS headers res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "Content-Type"); if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; } // Parse URL const url = new URL(req.url || "/", `http://localhost:${PORT}`); // Routes if (url.pathname === "/health") { // Health check res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ status: "ok", queueSize: queue.size() })); return; } if (url.pathname === "/webhook" && req.method === "POST") { // Receive message from Hermes (Telegram) let body = ""; req.on("data", chunk => body += chunk); req.on("end", async () => { try { const data = JSON.parse(body); const message = data.message || data.text || data.content; const chatId = data.chat_id || data.chatId || data.from?.id || "unknown"; console.log(`📥 Received from chat ${chatId}: ${message.substring(0, 50)}...`); // Process with agent const response = await agent.process(message); console.log(`📤 Sending response: ${response.substring(0, 50)}...`); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ success: true, response, chatId, })); } catch (error: any) { console.error("❌ Error:", error.message); res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: error.message })); } }); return; } if (url.pathname === "/message" && req.method === "POST") { // Alternative endpoint: send message directly let body = ""; req.on("data", chunk => body += chunk); req.on("end", async () => { try { const data = JSON.parse(body); const message = data.message; const chatId = data.chatId || "default"; console.log(`📥 Message from ${chatId}: ${message}`); const response = await agent.process(message); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ response })); } catch (error: any) { res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: error.message })); } }); return; } if (url.pathname === "/status" && req.method === "GET") { // Get status res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ status: "running", queueSize: queue.size(), })); return; } // 404 res.writeHead(404, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Not found" })); }); // ============== MAIN ============== async function main() { console.log("🧪 Level 4: Hermes Connection\n"); registerBuiltInApiProviders(); // Start server server.listen(PORT, () => { console.log(` 🚀 Server running on http://localhost:${PORT} 📡 Endpoints: GET /health - Health check GET /status - Server status POST /webhook - Receive from Hermes (Telegram) POST /message - Send message directly 📝 Example curl: curl -X POST http://localhost:${PORT}/message \\ -H "Content-Type: application/json" \\ -d '{"message": "Hello!", "chatId": "123"}' `); }); // Handle shutdown process.on("SIGINT", () => { console.log("\n🛑 Shutting down..."); server.close(() => { console.log("✅ Server stopped"); process.exit(0); }); }); } main().catch(console.error);