153 lines
4.3 KiB
JavaScript
153 lines
4.3 KiB
JavaScript
/**
|
||
* 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,
|
||
});
|
||
});
|