Files
inquiry_robot/aibot-bridge/server.cjs
T

153 lines
4.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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,
});
});