openclaw / server.js
getzero11's picture
Upload 2 files
2bf73d1 verified
raw
history blame
7.6 kB
import express from "express";
import { spawn } from "child_process";
import dotenv from "dotenv";
dotenv.config();
const app = express();
app.use(express.json());
/* ===============================
1. BASIC AUTH
================================ */
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();
});
/* ===============================
2. RATE LIMITING (IP-based)
================================ */
const RATE_LIMIT = 30; // requests
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();
});
/* ===============================
3. LOAD KEY POOLS
================================ */
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)];
}
/* ===============================
4. TASK ROUTING
================================ */
const TASKS = new Set([
"market_research",
"summarize",
"classify"
]);
/* ===============================
5. RUN AGENT
================================ */
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()
});
});
// Add specific market research endpoint for n8n compatibility
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" });
}
// Use API key from request or fallback to environment
let providersToTry;
let apiKeys = {};
if (api_key) {
// Use API key from n8n request
console.log('OpenClaw: Using API key from request');
apiKeys[provider] = [api_key];
providersToTry = [provider];
} else {
// Fallback to environment variables (for local testing)
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()
});
});
/* ===============================
6. EXECUTION + JSON PARSE
================================ */
function runOpenClaw(env, payload) {
return new Promise((resolve, reject) => {
const proc = spawn(process.execPath, ["src/index.js"], {
cwd: "/app",
env
});
let stdout = "";
let stderr = "";
// CRITICAL: handle spawn errors
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));
}
});
// SEND INPUT TO AGENT
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")
);
}
/* ===============================
7. START
================================ */
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`🚀 OpenClaw Agent Gateway running on port ${PORT}`);
});