Spaces:
Sleeping
Sleeping
dandi commited on
Commit ·
4e18ea2
1
Parent(s): 7b1f6b1
refactor: read body in proxy, return text directly
Browse files- api/lib/proxy.js +24 -37
- api/v1/chat/completions.js +2 -2
api/lib/proxy.js
CHANGED
|
@@ -31,11 +31,10 @@ function parseRetryAfterMs(response) {
|
|
| 31 |
return null;
|
| 32 |
}
|
| 33 |
|
| 34 |
-
function isCreditExhausted(
|
| 35 |
-
if (
|
| 36 |
try {
|
| 37 |
-
|
| 38 |
-
return text.includes("exhausted") || text.includes("credit") || text.includes("quota");
|
| 39 |
} catch {
|
| 40 |
return false;
|
| 41 |
}
|
|
@@ -95,13 +94,23 @@ async function proxyToKimchi(options) {
|
|
| 95 |
|
| 96 |
clearTimeout(timer);
|
| 97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
if (!RETRYABLE_STATUSES.has(response.status)) {
|
| 99 |
-
return {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
}
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
if (isCreditExhausted(response, responseText)) {
|
| 105 |
lastError.push(new Error(`HTTP ${response.status}: credits exhausted (key ${currentIndex})`));
|
| 106 |
continue;
|
| 107 |
}
|
|
@@ -131,38 +140,16 @@ async function proxyToKimchi(options) {
|
|
| 131 |
throw new Error("Proxy retry loop exhausted");
|
| 132 |
}
|
| 133 |
|
| 134 |
-
|
| 135 |
-
const
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
if (!["transfer-encoding", "connection", "content-length"].includes(key.toLowerCase())) {
|
| 139 |
clientRes.setHeader(key, value);
|
| 140 |
}
|
| 141 |
}
|
| 142 |
|
| 143 |
-
clientRes.status(
|
| 144 |
-
|
| 145 |
-
if (contentType.includes("text/event-stream")) {
|
| 146 |
-
clientRes.setHeader("Content-Type", "text/event-stream");
|
| 147 |
-
clientRes.setHeader("Cache-Control", "no-cache");
|
| 148 |
-
clientRes.setHeader("Connection", "keep-alive");
|
| 149 |
-
|
| 150 |
-
const reader = upstreamRes.body?.getReader();
|
| 151 |
-
if (!reader) {
|
| 152 |
-
clientRes.end();
|
| 153 |
-
return;
|
| 154 |
-
}
|
| 155 |
-
|
| 156 |
-
while (true) {
|
| 157 |
-
const { done, value } = await reader.read();
|
| 158 |
-
if (done) break;
|
| 159 |
-
clientRes.write(value);
|
| 160 |
-
}
|
| 161 |
-
clientRes.end();
|
| 162 |
-
} else {
|
| 163 |
-
const body = await upstreamRes.text();
|
| 164 |
-
clientRes.end(body);
|
| 165 |
-
}
|
| 166 |
}
|
| 167 |
|
| 168 |
-
module.exports = { proxyToKimchi,
|
|
|
|
| 31 |
return null;
|
| 32 |
}
|
| 33 |
|
| 34 |
+
function isCreditExhausted(status, body) {
|
| 35 |
+
if (status !== 402 && status !== 429) return false;
|
| 36 |
try {
|
| 37 |
+
return body.includes("exhausted") || body.includes("credit") || body.includes("quota");
|
|
|
|
| 38 |
} catch {
|
| 39 |
return false;
|
| 40 |
}
|
|
|
|
| 94 |
|
| 95 |
clearTimeout(timer);
|
| 96 |
|
| 97 |
+
const responseText = await response.text().catch(() => "");
|
| 98 |
+
const responseHeaders = {};
|
| 99 |
+
response.headers.forEach((value, key) => {
|
| 100 |
+
responseHeaders[key] = value;
|
| 101 |
+
});
|
| 102 |
+
|
| 103 |
if (!RETRYABLE_STATUSES.has(response.status)) {
|
| 104 |
+
return {
|
| 105 |
+
status: response.status,
|
| 106 |
+
headers: responseHeaders,
|
| 107 |
+
body: responseText,
|
| 108 |
+
keyIndex: currentIndex,
|
| 109 |
+
attempts: attempt,
|
| 110 |
+
};
|
| 111 |
}
|
| 112 |
|
| 113 |
+
if (isCreditExhausted(response.status, responseText)) {
|
|
|
|
|
|
|
| 114 |
lastError.push(new Error(`HTTP ${response.status}: credits exhausted (key ${currentIndex})`));
|
| 115 |
continue;
|
| 116 |
}
|
|
|
|
| 140 |
throw new Error("Proxy retry loop exhausted");
|
| 141 |
}
|
| 142 |
|
| 143 |
+
function writeResponse(clientRes, result) {
|
| 144 |
+
const skipHeaders = new Set(["transfer-encoding", "connection", "content-length"]);
|
| 145 |
+
for (const [key, value] of Object.entries(result.headers)) {
|
| 146 |
+
if (!skipHeaders.has(key.toLowerCase())) {
|
|
|
|
| 147 |
clientRes.setHeader(key, value);
|
| 148 |
}
|
| 149 |
}
|
| 150 |
|
| 151 |
+
clientRes.status(result.status);
|
| 152 |
+
clientRes.end(result.body);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
}
|
| 154 |
|
| 155 |
+
module.exports = { proxyToKimchi, writeResponse };
|
api/v1/chat/completions.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
const { parseKeys, selectKey, throttleKey, isKeyThrottled } = require("../../lib/key-rotation.js");
|
| 2 |
-
const { proxyToKimchi,
|
| 3 |
|
| 4 |
const KIMCHI_UPSTREAM = "https://llm.kimchi.dev/openai/v1/chat/completions";
|
| 5 |
|
|
@@ -71,7 +71,7 @@ module.exports = async function handler(req, res) {
|
|
| 71 |
res.setHeader("X-Proxy-Attempts", String(result.attempts));
|
| 72 |
res.setHeader("X-Proxy-Elapsed-Ms", String(elapsed));
|
| 73 |
|
| 74 |
-
await
|
| 75 |
} catch (error) {
|
| 76 |
console.error("[completions proxy] error:", error);
|
| 77 |
const elapsed = startTime ? Date.now() - startTime : 0;
|
|
|
|
| 1 |
const { parseKeys, selectKey, throttleKey, isKeyThrottled } = require("../../lib/key-rotation.js");
|
| 2 |
+
const { proxyToKimchi, writeResponse } = require("../../lib/proxy.js");
|
| 3 |
|
| 4 |
const KIMCHI_UPSTREAM = "https://llm.kimchi.dev/openai/v1/chat/completions";
|
| 5 |
|
|
|
|
| 71 |
res.setHeader("X-Proxy-Attempts", String(result.attempts));
|
| 72 |
res.setHeader("X-Proxy-Elapsed-Ms", String(elapsed));
|
| 73 |
|
| 74 |
+
await writeResponse(res, result);
|
| 75 |
} catch (error) {
|
| 76 |
console.error("[completions proxy] error:", error);
|
| 77 |
const elapsed = startTime ? Date.now() - startTime : 0;
|