/** * AIBOT 长连接桥(骨架 + HTTP 转发) * * 本文件职责: * - /health 存活 * - POST /forward 把已解析消息转到智能体 WECOM_AIBOT_INGRESS_URL(默认 8910/internal/aibot/inbound) * - 默认不连企微 Bot 长连接(并存期禁止双连);WECOM_AIBOT_ENABLED=true 时仅打警告 * * 禁止:在本进程写工单/六态/Graph。 */ "use strict"; const http = require("http"); const { URL } = require("url"); const { startLongConnectionLoop } = require("./long_connection.cjs"); const port = Number(process.env.AIBOT_BRIDGE_PORT || 8913); const bind = process.env.AIBOT_BRIDGE_BIND || "127.0.0.1"; const enabled = String(process.env.WECOM_AIBOT_ENABLED || "false").toLowerCase() === "true"; const ingressUrl = process.env.WECOM_AIBOT_INGRESS_URL || "http://127.0.0.1:8910/internal/aibot/inbound"; const bridgeToken = process.env.WECOM_AIBOT_BRIDGE_TOKEN || ""; function readBody(req) { return new Promise((resolve, reject) => { const chunks = []; req.on("data", (c) => chunks.push(c)); req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); req.on("error", reject); }); } function sendJson(res, code, obj) { const body = JSON.stringify(obj); res.writeHead(code, { "Content-Type": "application/json; charset=utf-8" }); res.end(body); } function forwardToAgent(payloadText) { return new Promise((resolve, reject) => { let target; try { target = new URL(ingressUrl); } catch (e) { reject(e); return; } const data = Buffer.from(payloadText, "utf8"); const req = http.request( { protocol: target.protocol, hostname: target.hostname, port: target.port || 80, path: target.pathname + (target.search || ""), method: "POST", headers: { "Content-Type": "application/json; charset=utf-8", "Content-Length": data.length, "X-Aibot-Bridge-Token": bridgeToken, }, timeout: 5000, }, (res) => { const chunks = []; res.on("data", (c) => chunks.push(c)); res.on("end", () => { resolve({ status: res.statusCode, body: Buffer.concat(chunks).toString("utf8"), }); }); } ); req.on("error", reject); req.on("timeout", () => { req.destroy(); reject(new Error("ingress_timeout")); }); req.write(data); req.end(); }); } const server = http.createServer(async (req, res) => { const url = (req.url || "").split("?")[0]; if (req.method === "GET" && (url === "/health" || url === "/health/")) { sendJson(res, 200, { status: "UP", service: "aibot-bridge", bind, port, wecomAibotEnabled: enabled, ingressUrl, longConnectionShell: true, note: enabled ? "enabled:长连接帧循环壳已起(默认无 Bot SDK);勿与现网双连" : "Bot 长连接默认关闭;可用 POST /forward 测转发", }); return; } // 测试/后续 Bot 回调:把 JSON 原样转到智能体(不做业务) if (req.method === "POST" && (url === "/forward" || url === "/forward/")) { try { const raw = await readBody(req); if (!raw) { sendJson(res, 400, { error: "empty_body" }); return; } // 轻量校验 JSON JSON.parse(raw); const out = await forwardToAgent(raw); sendJson(res, 200, { status: "forwarded", ingressStatus: out.status, ingressBody: (() => { try { return JSON.parse(out.body); } catch (_) { return out.body; } })(), }); } catch (e) { sendJson(res, 502, { error: "forward_failed", message: String(e && e.message ? e.message : e) }); } return; } sendJson(res, 404, { error: "not_found", path: req.url }); }); server.listen(port, bind, () => { // eslint-disable-next-line no-console console.log( `[aibot-bridge] http://${bind}:${port}/health forward→${ingressUrl} enabled=${enabled}` ); if (enabled) { // eslint-disable-next-line no-console console.warn( "[aibot-bridge] WECOM_AIBOT_ENABLED=true:请确认已停现网桥且不会双连同一 Bot" ); } // 传输壳:enabled 时跑心跳循环;未接 SDK 前不会产生入站帧 startLongConnectionLoop({ enabled, forwardToAgent, }); });