dandi commited on
Commit
4e18ea2
·
1 Parent(s): 7b1f6b1

refactor: read body in proxy, return text directly

Browse files
Files changed (2) hide show
  1. api/lib/proxy.js +24 -37
  2. 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(response, body) {
35
- if (response.status !== 402 && response.status !== 429) return false;
36
  try {
37
- const text = typeof body === "string" ? body : "";
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 { response, keyIndex: currentIndex, attempts: attempt };
 
 
 
 
 
 
100
  }
101
 
102
- const responseText = await response.text().catch(() => "");
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
- async function streamResponse(clientRes, upstreamRes) {
135
- const contentType = upstreamRes.headers.get("content-type") || "";
136
-
137
- for (const [key, value] of upstreamRes.headers.entries()) {
138
- if (!["transfer-encoding", "connection", "content-length"].includes(key.toLowerCase())) {
139
  clientRes.setHeader(key, value);
140
  }
141
  }
142
 
143
- clientRes.status(upstreamRes.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, streamResponse };
 
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, streamResponse } = require("../../lib/proxy.js");
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 streamResponse(res, result.response);
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;