Initial commit: kage-research project files
This commit is contained in:
253
level4.ts
Normal file
253
level4.ts
Normal file
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* 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<string> {
|
||||
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);
|
||||
Reference in New Issue
Block a user