68 lines
2.3 KiB
JavaScript
68 lines
2.3 KiB
JavaScript
const { log, err } = require("../logger");
|
|
|
|
const DEFAULT_LOCAL_ROUTER = "http://localhost:20128";
|
|
const ROUTER_BASE = String(process.env.MITM_ROUTER_BASE || DEFAULT_LOCAL_ROUTER)
|
|
.trim()
|
|
.replace(/\/+$/, "") || DEFAULT_LOCAL_ROUTER;
|
|
const API_KEY = process.env.ROUTER_API_KEY;
|
|
|
|
// Headers that must not be forwarded to 9Router
|
|
const STRIP_HEADERS = new Set([
|
|
"host", "content-length", "connection", "transfer-encoding",
|
|
"content-type", "authorization"
|
|
]);
|
|
|
|
/**
|
|
* Send body to 9Router at the given path and return the fetch Response object.
|
|
* Optionally forwards client headers (stripped of hop-by-hop / overridden keys).
|
|
*/
|
|
async function fetchRouter(openaiBody, path = "/v1/chat/completions", clientHeaders = {}) {
|
|
const forwarded = {};
|
|
for (const [k, v] of Object.entries(clientHeaders)) {
|
|
if (!STRIP_HEADERS.has(k.toLowerCase())) forwarded[k] = v;
|
|
}
|
|
|
|
const response = await fetch(`${ROUTER_BASE}${path}`, {
|
|
method: "POST",
|
|
headers: {
|
|
...forwarded,
|
|
"Content-Type": "application/json",
|
|
...(API_KEY && { "Authorization": `Bearer ${API_KEY}` })
|
|
},
|
|
body: JSON.stringify(openaiBody)
|
|
});
|
|
|
|
// Forward response as-is (status + body). pipeSSE will propagate status.
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Pipe SSE stream from router directly to client response.
|
|
* Optional dumper tees the stream into a debug file.
|
|
*/
|
|
async function pipeSSE(routerRes, res, dumper) {
|
|
const ct = routerRes.headers.get("content-type") || "application/json";
|
|
const status = routerRes.status || 200;
|
|
const resHeaders = { "Content-Type": ct, "Cache-Control": "no-cache", "Connection": "keep-alive" };
|
|
if (ct.includes("text/event-stream")) resHeaders["X-Accel-Buffering"] = "no";
|
|
res.writeHead(status, resHeaders);
|
|
if (dumper) dumper.writeHeader(routerRes.status, Object.fromEntries(routerRes.headers));
|
|
|
|
if (!routerRes.body) {
|
|
const text = await routerRes.text().catch(() => "");
|
|
if (dumper) { dumper.writeChunk(text); dumper.end(); }
|
|
res.end(text);
|
|
return;
|
|
}
|
|
|
|
const reader = routerRes.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) { if (dumper) dumper.end(); res.end(); break; }
|
|
if (dumper) dumper.writeChunk(value);
|
|
res.write(decoder.decode(value, { stream: true }));
|
|
}
|
|
}
|
|
|
|
module.exports = { fetchRouter, pipeSSE };
|