| | import express from "express"; |
| | import { spawn } from "child_process"; |
| | import dotenv from "dotenv"; |
| | dotenv.config(); |
| |
|
| | const app = express();
|
| | app.use(express.json());
|
| |
|
| | |
| | |
| |
|
| |
|
| | const GATE_KEY = process.env.OPENCLAW_GATE_KEY;
|
| |
|
| | app.use((req, res, next) => {
|
| | if (!GATE_KEY) return next();
|
| | if (req.headers["x-openclaw-key"] !== GATE_KEY) {
|
| | return res.status(401).json({ error: "Unauthorized" });
|
| | }
|
| | next();
|
| | });
|
| |
|
| | |
| | |
| |
|
| |
|
| | const RATE_LIMIT = 30;
|
| | const WINDOW_MS = 60_000;
|
| | const ipHits = new Map();
|
| |
|
| | app.use((req, res, next) => {
|
| | const ip = req.headers["x-forwarded-for"] || req.socket.remoteAddress;
|
| | const now = Date.now();
|
| |
|
| | const record = ipHits.get(ip) || { count: 0, ts: now };
|
| | if (now - record.ts > WINDOW_MS) {
|
| | record.count = 0;
|
| | record.ts = now;
|
| | }
|
| |
|
| | record.count++;
|
| | ipHits.set(ip, record);
|
| |
|
| | if (record.count > RATE_LIMIT) {
|
| | return res.status(429).json({ error: "Rate limit exceeded" });
|
| | }
|
| | next();
|
| | });
|
| |
|
| | |
| | |
| |
|
| |
|
| | function loadPool(prefix) { |
| | const keys = Object.keys(process.env) |
| | .filter(k => k.startsWith(prefix)) |
| | .map(k => process.env[k]) |
| | .filter(Boolean); |
| | |
| | console.log(`Loaded ${keys.length} keys for ${prefix}`); |
| | return keys; |
| | } |
| |
|
| | const PROVIDERS = {
|
| | openai: loadPool("OPENAI_API_KEY_"),
|
| | deepseek: loadPool("DEEPSEEK_API_KEY_"),
|
| | gemini: loadPool("GEMINI_API_KEY_"),
|
| | openrouter: loadPool("OPENROUTER_API_KEY_"),
|
| | dashscope: loadPool("DASHSCOPE_API_KEY_")
|
| | };
|
| |
|
| | function randomKey(pool) {
|
| | return pool[Math.floor(Math.random() * pool.length)];
|
| | }
|
| |
|
| | |
| | |
| |
|
| |
|
| | const TASKS = new Set([
|
| | "market_research",
|
| | "summarize",
|
| | "classify"
|
| | ]);
|
| |
|
| | |
| | |
| |
|
| | app.use((req, res, next) => {
|
| | if (req.method === "POST" && !req.is("application/json")) {
|
| | return res.status(400).json({ error: "JSON body required" });
|
| | }
|
| | next();
|
| | });
|
| |
|
| | app.get("/", (req, res) => {
|
| | res.json({
|
| | status: "OpenClaw running",
|
| | service: "market_research_agent"
|
| | });
|
| | });
|
| |
|
| | app.post("/run", async (req, res) => { |
| | console.log('OpenClaw: Received request:', { |
| | task: req.body.task, |
| | keyword: req.body.keyword, |
| | timestamp: new Date().toISOString() |
| | }); |
| | |
| | const { task = "market_research", provider, model, ...payload } = req.body; |
| | |
| | if (!TASKS.has(task)) { |
| | return res.status(400).json({ error: "Unknown task" }); |
| | } |
| |
|
| | const providersToTry = provider |
| | ? [provider, ...Object.keys(PROVIDERS).filter(p => p !== provider)] |
| | : Object.keys(PROVIDERS); |
| |
|
| | let lastError; |
| |
|
| | for (const p of providersToTry) { |
| | const pool = PROVIDERS[p]; |
| | if (!pool || pool.length === 0) continue; |
| |
|
| | for (let i = 0; i < pool.length; i++) { |
| | const apiKey = randomKey(pool); |
| |
|
| | const env = { |
| | ...process.env, |
| | OPENCLAW_PROVIDER: p, |
| | OPENCLAW_MODEL: model || "", |
| | OPENCLAW_API_KEY: apiKey, |
| | OPENCLAW_TASK: task, |
| | OPENCLAW_TIMEOUT: "180000" |
| | }; |
| |
|
| | try { |
| | const result = await runOpenClaw(env, payload); |
| | return res.json(result); |
| | } catch (err) { |
| | lastError = err; |
| | if (!isRateLimit(err)) break; |
| | } |
| | } |
| | } |
| |
|
| | res.status(500).json({ |
| | error: "All providers failed", |
| | details: lastError?.message, |
| | keyword: req.body.keyword, |
| | timestamp: new Date().toISOString() |
| | }); |
| | }); |
| |
|
| | |
| | app.post("/api/market-research", async (req, res) => { |
| | console.log('OpenClaw: Market research request:', { |
| | keyword: req.body.keyword, |
| | timestamp: new Date().toISOString() |
| | }); |
| | |
| | const { keyword, api_key, provider = "deepseek" } = req.body; |
| | |
| | if (!keyword) { |
| | return res.status(400).json({ error: "keyword is required" }); |
| | } |
| |
|
| | |
| | let providersToTry; |
| | let apiKeys = {}; |
| |
|
| | if (api_key) { |
| | |
| | console.log('OpenClaw: Using API key from request'); |
| | apiKeys[provider] = [api_key]; |
| | providersToTry = [provider]; |
| | } else { |
| | |
| | console.log('OpenClaw: Using API keys from environment'); |
| | providersToTry = Object.keys(PROVIDERS); |
| | apiKeys = PROVIDERS; |
| | } |
| |
|
| | let lastError; |
| |
|
| | for (const p of providersToTry) { |
| | const pool = apiKeys[p]; |
| | if (!pool || pool.length === 0) { |
| | console.log(`OpenClaw: No API keys for provider ${p}`); |
| | continue; |
| | } |
| |
|
| | for (let i = 0; i < pool.length; i++) { |
| | const apiKey = pool[i]; |
| |
|
| | const env = { |
| | ...process.env, |
| | OPENCLAW_PROVIDER: p, |
| | OPENCLAW_API_KEY: apiKey, |
| | OPENCLAW_TASK: "market_research", |
| | OPENCLAW_TIMEOUT: "180000" |
| | }; |
| |
|
| | try { |
| | console.log(`OpenClaw: Trying provider ${p} with key ${apiKey.substring(0, 10)}...`); |
| | const result = await runOpenClaw(env, { keyword }); |
| | console.log('OpenClaw: Success with provider', p); |
| | return res.json(result); |
| | } catch (err) { |
| | lastError = err; |
| | console.error('OpenClaw: Provider failed:', p, err.message); |
| | if (!isRateLimit(err)) break; |
| | } |
| | } |
| | } |
| |
|
| | res.status(500).json({ |
| | error: "All providers failed", |
| | details: lastError?.message || "No API keys configured", |
| | keyword: keyword, |
| | timestamp: new Date().toISOString() |
| | }); |
| | }); |
| |
|
| | |
| | |
| |
|
| |
|
| | function runOpenClaw(env, payload) {
|
| | return new Promise((resolve, reject) => {
|
| | const proc = spawn(process.execPath, ["src/index.js"], {
|
| | cwd: "/app",
|
| | env
|
| | });
|
| |
|
| | let stdout = "";
|
| | let stderr = "";
|
| |
|
| |
|
| | proc.on("error", err => {
|
| | reject(err);
|
| | });
|
| |
|
| | proc.stdout.on("data", data => {
|
| | stdout += data.toString();
|
| | });
|
| |
|
| | proc.stderr.on("data", data => {
|
| | stderr += data.toString();
|
| | });
|
| |
|
| | proc.on("close", code => { |
| | if (code !== 0) { |
| | console.error('OpenClaw: Agent exited with code:', code); |
| | console.error('OpenClaw: stderr:', stderr); |
| | return reject( |
| | new Error(stderr || `Agent exited with code ${code}`) |
| | ); |
| | } |
| |
|
| | try { |
| | console.log('OpenClaw: Agent stdout (first 500 chars):', stdout.substring(0, 500)); |
| | const json = JSON.parse(stdout); |
| | console.log('OpenClaw: JSON parsed successfully'); |
| | resolve(json); |
| | } catch (err) { |
| | console.error('OpenClaw: JSON parse error:', err.message); |
| | console.error('OpenClaw: Raw stdout:', stdout); |
| | reject(new Error("Invalid JSON from OpenClaw agent: " + err.message)); |
| | } |
| | }); |
| |
|
| |
|
| | proc.stdin.write(JSON.stringify(payload));
|
| | proc.stdin.end();
|
| | });
|
| | }
|
| |
|
| |
|
| | function isRateLimit(err) {
|
| | const msg = err.message?.toLowerCase() || "";
|
| | return (
|
| | msg.includes("429") ||
|
| | msg.includes("rate") ||
|
| | msg.includes("quota") ||
|
| | msg.includes("limit")
|
| | );
|
| | }
|
| |
|
| |
|
| | |
| | |
| |
|
| |
|
| | const PORT = process.env.PORT || 3000; |
| | app.listen(PORT, () => { |
| | console.log(`🚀 OpenClaw Agent Gateway running on port ${PORT}`); |
| | }); |
| |
|