fix: route deepseek codex via moonbridge
This commit is contained in:
@@ -36,6 +36,10 @@ const server = createServer(async (clientReq, clientRes) => {
|
||||
await sendCodexModels(clientReq, clientRes, url);
|
||||
return;
|
||||
}
|
||||
if (clientReq.method === "POST" && url.pathname === "/v1/responses") {
|
||||
await proxyResponsesRequest(clientReq, clientRes, url);
|
||||
return;
|
||||
}
|
||||
await proxyRequest(clientReq, clientRes, url);
|
||||
} catch (error) {
|
||||
sendJson(clientRes, 502, {
|
||||
@@ -53,18 +57,55 @@ server.listen(port, "0.0.0.0", () => {
|
||||
});
|
||||
|
||||
async function sendReadiness(res) {
|
||||
const upstream = await fetchUpstreamJson("/health/readiness").catch((error) => ({ ok: false, status: 502, payload: { error: error.message } }));
|
||||
const upstream = await fetchUpstreamReadiness();
|
||||
sendJson(res, upstream.ok ? 200 : 503, {
|
||||
...healthPayload(),
|
||||
status: upstream.ok ? "ready" : "degraded",
|
||||
upstream: {
|
||||
ok: upstream.ok,
|
||||
status: upstream.status,
|
||||
source: upstream.source,
|
||||
payload: upstream.payload
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchUpstreamReadiness() {
|
||||
const health = await fetchUpstreamJson("/health/readiness")
|
||||
.then((result) => ({ ...result, source: "/health/readiness" }))
|
||||
.catch((error) => ({ ok: false, status: 502, source: "/health/readiness", payload: { error: error.message } }));
|
||||
if (health.ok) return health;
|
||||
|
||||
const models = await fetchUpstreamJson("/v1/models")
|
||||
.then((result) => ({ ...result, source: "/v1/models" }))
|
||||
.catch((error) => ({ ok: false, status: 502, source: "/v1/models", payload: { error: error.message } }));
|
||||
const modelCount = codexModelsFromUpstream(models.payload).length;
|
||||
if (models.ok && modelCount > 0) {
|
||||
return {
|
||||
...models,
|
||||
payload: {
|
||||
modelCount,
|
||||
fallbackFrom: {
|
||||
source: health.source,
|
||||
status: health.status
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...health,
|
||||
payload: {
|
||||
...health.payload,
|
||||
modelProbe: {
|
||||
ok: models.ok,
|
||||
status: models.status,
|
||||
modelCount
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function sendCodexModels(clientReq, clientRes, url) {
|
||||
const upstream = await fetchUpstreamJson(`${url.pathname}${url.search}`, clientReq.headers).catch((error) => ({ ok: false, status: 502, payload: { error: error.message } }));
|
||||
const models = codexModelsFromUpstream(upstream.payload);
|
||||
@@ -73,7 +114,7 @@ async function sendCodexModels(clientReq, clientRes, url) {
|
||||
upstream: {
|
||||
ok: upstream.ok,
|
||||
status: upstream.status,
|
||||
source: upstream.ok ? "litellm" : "fallback"
|
||||
source: upstream.ok ? "upstream" : "fallback"
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -126,6 +167,65 @@ async function proxyRequest(clientReq, clientRes, url) {
|
||||
upstreamReq.end(body);
|
||||
}
|
||||
|
||||
async function proxyResponsesRequest(clientReq, clientRes, url) {
|
||||
const startedAt = Date.now();
|
||||
const originalBody = await readRequestBody(clientReq);
|
||||
const { body, headers, decompressed, sanitizedToolTypes } = decodeRequestBody(originalBody, clientReq.headers, url.pathname);
|
||||
await proxyPreparedBody({
|
||||
clientReq,
|
||||
clientRes,
|
||||
url,
|
||||
body,
|
||||
headers,
|
||||
startedAt,
|
||||
logExtra: { decompressed, sanitizedToolTypes }
|
||||
});
|
||||
}
|
||||
|
||||
async function proxyPreparedBody({ clientReq, clientRes, url, body, headers, startedAt, logExtra = {} }) {
|
||||
const targetUrl = new URL(`${url.pathname}${url.search}`, upstreamBaseUrl);
|
||||
const requestImpl = targetUrl.protocol === "https:" ? httpsRequest : httpRequest;
|
||||
|
||||
const upstreamReq = requestImpl({
|
||||
protocol: targetUrl.protocol,
|
||||
hostname: targetUrl.hostname,
|
||||
port: targetUrl.port || (targetUrl.protocol === "https:" ? 443 : 80),
|
||||
method: clientReq.method,
|
||||
path: `${targetUrl.pathname}${targetUrl.search}`,
|
||||
headers
|
||||
}, (upstreamRes) => {
|
||||
const responseHeaders = filterResponseHeaders(upstreamRes.headers);
|
||||
clientRes.writeHead(upstreamRes.statusCode || 502, responseHeaders);
|
||||
upstreamRes.pipe(clientRes);
|
||||
upstreamRes.on("end", () => {
|
||||
process.stderr.write(JSON.stringify({
|
||||
serviceId,
|
||||
method: clientReq.method,
|
||||
path: url.pathname,
|
||||
status: upstreamRes.statusCode || null,
|
||||
elapsedMs: Date.now() - startedAt,
|
||||
...logExtra
|
||||
}) + "\n");
|
||||
});
|
||||
});
|
||||
|
||||
upstreamReq.on("error", (error) => {
|
||||
if (!clientRes.headersSent) {
|
||||
sendJson(clientRes, 502, {
|
||||
error: {
|
||||
code: "deepseek_bridge_upstream_error",
|
||||
message: "DeepSeek bridge upstream request failed",
|
||||
reason: error.message
|
||||
}
|
||||
});
|
||||
} else {
|
||||
clientRes.end();
|
||||
}
|
||||
});
|
||||
|
||||
upstreamReq.end(body);
|
||||
}
|
||||
|
||||
function decodeRequestBody(body, headers, pathname = "") {
|
||||
const encoding = String(headers["content-encoding"] || "").trim().toLowerCase();
|
||||
const decoded = encoding === "zstd" ? Buffer.from(decompress(new Uint8Array(body))) : body;
|
||||
@@ -255,7 +355,8 @@ function healthPayload() {
|
||||
status: "ok",
|
||||
upstream: upstreamBaseUrl.origin,
|
||||
model: defaultModel,
|
||||
zstdRequestBody: "decode"
|
||||
zstdRequestBody: "decode",
|
||||
responsesTools: "drop-non-function"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -30,11 +30,18 @@ test("DeepSeek Responses bridge decompresses Codex zstd body and maps models cat
|
||||
body: zstdPayload
|
||||
});
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(upstream.captured.url, "/v1/responses");
|
||||
assert.equal(upstream.captured.encoding, null);
|
||||
const forwarded = JSON.parse(upstream.captured.body);
|
||||
assert.equal(forwarded.model, "deepseek-chat");
|
||||
assert.deepEqual(forwarded.tools.map((tool) => tool.type), ["function", "function"]);
|
||||
|
||||
const readinessResponse = await fetch(`http://127.0.0.1:${bridge.port}/health/readiness`);
|
||||
assert.equal(readinessResponse.status, 200);
|
||||
const readiness = await readinessResponse.json();
|
||||
assert.equal(readiness.upstream.source, "/v1/models");
|
||||
assert.equal(readiness.upstream.payload.modelCount, 1);
|
||||
|
||||
const modelsResponse = await fetch(`http://127.0.0.1:${bridge.port}/v1/models?client_version=test`);
|
||||
assert.equal(modelsResponse.status, 200);
|
||||
const models = await modelsResponse.json();
|
||||
@@ -49,6 +56,11 @@ test("DeepSeek Responses bridge decompresses Codex zstd body and maps models cat
|
||||
async function startUpstream() {
|
||||
let captured = null;
|
||||
const server = createServer(async (request, response) => {
|
||||
if ((request.url || "").startsWith("/health/readiness")) {
|
||||
response.writeHead(404, { "content-type": "application/json" });
|
||||
response.end(JSON.stringify({ error: "not implemented" }));
|
||||
return;
|
||||
}
|
||||
if ((request.url || "").startsWith("/v1/models")) {
|
||||
response.writeHead(200, { "content-type": "application/json" });
|
||||
response.end(JSON.stringify({ data: [{ id: "deepseek-chat" }] }));
|
||||
|
||||
Reference in New Issue
Block a user