Juanoto2012 commited on
Commit
351fa98
verified
1 Parent(s): d9c6f10

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +21 -28
server.js CHANGED
@@ -9,7 +9,6 @@ const PORT = 7860;
9
  // --- CONFIGURACI脫N DE SEGURIDAD Y PRIVACIDAD ---
10
  app.set('trust proxy', 1);
11
  app.disable('x-powered-by'); // Oculta que estamos usando Express
12
-
13
  app.use(cors());
14
 
15
  // Inyecci贸n de cabeceras de seguridad est谩ndar
@@ -39,11 +38,6 @@ const PROVIDERS = [
39
  id: "llm7",
40
  url: "https://api.llm7.io/v1/chat/completions"
41
  },
42
- {
43
- id: "airforce",
44
- url: "https://api.airforce/v1/chat/completions",
45
- imageUrl: "https://api.airforce/v1/images/generations"
46
- },
47
  {
48
  id: "ventarys-mirror",
49
  url: "https://ventarys-mirror-1.hf.space/v1/chat/completions",
@@ -53,8 +47,7 @@ const PROVIDERS = [
53
 
54
  const MAX_PER_PROVIDER = 3;
55
  const QUEUE_TIMEOUT = 25000;
56
-
57
- let currentLoad = { "llm7": 0, "airforce": 0, "ventarys-mirror": 0 };
58
 
59
  // --- RATE LIMITING (Basado en la IP real del usuario) ---
60
  const limiter = rateLimit({
@@ -91,17 +84,17 @@ async function fetchAllModels() {
91
 
92
  if (provider.apiKey) fetchHeaders["Authorization"] = `Bearer ${provider.apiKey}`;
93
  if (provider.proxySecret) fetchHeaders["X-Proxy-Secret"] = provider.proxySecret;
94
-
95
  try {
96
  const resp = await fetch(modelsUrl, { method: "GET", headers: fetchHeaders });
97
  if (!resp.ok) return [];
98
 
99
  const json = await resp.json();
100
  let modelsArray = [];
101
-
102
  if (Array.isArray(json)) modelsArray = json;
103
  else if (json && Array.isArray(json.data)) modelsArray = json.data;
104
-
105
  if (modelsArray.length > 0) {
106
  return modelsArray
107
  // 1. Filtrar modelos de audio/m煤sica
@@ -126,7 +119,7 @@ async function fetchAllModels() {
126
  return [];
127
  }
128
  });
129
-
130
  const results = await Promise.allSettled(fetchPromises);
131
  let allModels = [];
132
  results.forEach(result => {
@@ -156,7 +149,7 @@ app.get('/v1/models', async (req, res) => {
156
  m.type = 'text';
157
  return m;
158
  });
159
-
160
  res.json({ object: "list", data: textModels });
161
  } catch (error) {
162
  logError("ProxyMain", "Error recuperando modelos de texto.");
@@ -175,18 +168,18 @@ app.get('/v1/images/models', async (req, res) => {
175
  m.type = 'image';
176
  return m;
177
  });
178
-
179
  const baseImages = [
180
  { id: "flux", object: "model", type: "image", owned_by: "system", tier: "standard" },
181
  { id: "dall-e-3", object: "model", type: "image", owned_by: "system", tier: "standard" }
182
  ];
183
-
184
  baseImages.forEach(baseMod => {
185
  if (!imageModels.find(m => m.id.toLowerCase() === baseMod.id)) {
186
  imageModels.push(baseMod);
187
  }
188
  });
189
-
190
  res.json({ object: "list", data: imageModels });
191
  } catch (error) {
192
  logError("ProxyMain", "Error recuperando modelos de imagen.");
@@ -201,7 +194,7 @@ app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req
201
  let availableProviders = isImage ? PROVIDERS.filter(p => p.imageUrl) : [...PROVIDERS];
202
  const startTime = Date.now();
203
  let responseSent = false;
204
-
205
  while (availableProviders.length > 0 && Date.now() - startTime < QUEUE_TIMEOUT) {
206
  let selectedProvider = null;
207
 
@@ -214,12 +207,12 @@ app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req
214
  break;
215
  }
216
  }
217
-
218
  if (!selectedProvider) {
219
  await new Promise(r => setTimeout(r, 1500));
220
  continue;
221
  }
222
-
223
  let isReleased = false;
224
  const releaseSlot = () => {
225
  if (!isReleased) {
@@ -227,7 +220,7 @@ app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req
227
  isReleased = true;
228
  }
229
  };
230
-
231
  try {
232
  let targetUrl = isImage ? selectedProvider.imageUrl : selectedProvider.url;
233
  let reqMethod = "POST";
@@ -236,30 +229,30 @@ app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req
236
 
237
  if (selectedProvider.apiKey) fetchHeaders["Authorization"] = `Bearer ${selectedProvider.apiKey}`;
238
  if (selectedProvider.proxySecret) fetchHeaders["X-Proxy-Secret"] = selectedProvider.proxySecret;
239
-
240
  const response = await fetch(targetUrl, {
241
  method: reqMethod,
242
  headers: fetchHeaders,
243
  body: reqBody
244
  });
245
-
246
  if (!response.ok) {
247
  logError(selectedProvider.id, `Fallo con c贸digo HTTP ${response.status}`);
248
  releaseSlot();
249
  availableProviders = availableProviders.filter(p => p.id !== selectedProvider.id);
250
  continue;
251
  }
252
-
253
  // Sanitizaci贸n: Evitar devolver cookies o cabeceras de servidor del proveedor original
254
  const responseHeaders = new Headers(response.headers);
255
  responseHeaders.delete('set-cookie');
256
  responseHeaders.delete('server');
257
  responseHeaders.delete('x-powered-by');
258
  responseHeaders.delete('cf-ray');
259
-
260
  if (isImage) {
261
  const contentType = responseHeaders.get("content-type") || "";
262
-
263
  if (contentType.includes("application/json")) {
264
  const jsonResp = await response.json();
265
 
@@ -318,7 +311,7 @@ app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req
318
  'Cache-Control': 'no-cache',
319
  'Connection': 'keep-alive'
320
  });
321
-
322
  if (response.body) {
323
  const stream = Readable.fromWeb(response.body);
324
  stream.pipe(res);
@@ -336,14 +329,14 @@ app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req
336
 
337
  responseSent = true;
338
  return;
339
-
340
  } catch (err) {
341
  logError(selectedProvider.id, `Excepci贸n de red conectando al upstream.`);
342
  releaseSlot();
343
  availableProviders = availableProviders.filter(p => p.id !== selectedProvider.id);
344
  }
345
  }
346
-
347
  if (!responseSent) {
348
  logError("ProxyMain", "Todos los proveedores fallaron o se agot贸 el tiempo de espera.");
349
  return res.status(503).json({ error: { message: "El servicio no est谩 disponible temporalmente. Int茅ntalo de nuevo.", code: 503 } });
 
9
  // --- CONFIGURACI脫N DE SEGURIDAD Y PRIVACIDAD ---
10
  app.set('trust proxy', 1);
11
  app.disable('x-powered-by'); // Oculta que estamos usando Express
 
12
  app.use(cors());
13
 
14
  // Inyecci贸n de cabeceras de seguridad est谩ndar
 
38
  id: "llm7",
39
  url: "https://api.llm7.io/v1/chat/completions"
40
  },
 
 
 
 
 
41
  {
42
  id: "ventarys-mirror",
43
  url: "https://ventarys-mirror-1.hf.space/v1/chat/completions",
 
47
 
48
  const MAX_PER_PROVIDER = 3;
49
  const QUEUE_TIMEOUT = 25000;
50
+ let currentLoad = { "llm7": 0, "ventarys-mirror": 0 };
 
51
 
52
  // --- RATE LIMITING (Basado en la IP real del usuario) ---
53
  const limiter = rateLimit({
 
84
 
85
  if (provider.apiKey) fetchHeaders["Authorization"] = `Bearer ${provider.apiKey}`;
86
  if (provider.proxySecret) fetchHeaders["X-Proxy-Secret"] = provider.proxySecret;
87
+
88
  try {
89
  const resp = await fetch(modelsUrl, { method: "GET", headers: fetchHeaders });
90
  if (!resp.ok) return [];
91
 
92
  const json = await resp.json();
93
  let modelsArray = [];
94
+
95
  if (Array.isArray(json)) modelsArray = json;
96
  else if (json && Array.isArray(json.data)) modelsArray = json.data;
97
+
98
  if (modelsArray.length > 0) {
99
  return modelsArray
100
  // 1. Filtrar modelos de audio/m煤sica
 
119
  return [];
120
  }
121
  });
122
+
123
  const results = await Promise.allSettled(fetchPromises);
124
  let allModels = [];
125
  results.forEach(result => {
 
149
  m.type = 'text';
150
  return m;
151
  });
152
+
153
  res.json({ object: "list", data: textModels });
154
  } catch (error) {
155
  logError("ProxyMain", "Error recuperando modelos de texto.");
 
168
  m.type = 'image';
169
  return m;
170
  });
171
+
172
  const baseImages = [
173
  { id: "flux", object: "model", type: "image", owned_by: "system", tier: "standard" },
174
  { id: "dall-e-3", object: "model", type: "image", owned_by: "system", tier: "standard" }
175
  ];
176
+
177
  baseImages.forEach(baseMod => {
178
  if (!imageModels.find(m => m.id.toLowerCase() === baseMod.id)) {
179
  imageModels.push(baseMod);
180
  }
181
  });
182
+
183
  res.json({ object: "list", data: imageModels });
184
  } catch (error) {
185
  logError("ProxyMain", "Error recuperando modelos de imagen.");
 
194
  let availableProviders = isImage ? PROVIDERS.filter(p => p.imageUrl) : [...PROVIDERS];
195
  const startTime = Date.now();
196
  let responseSent = false;
197
+
198
  while (availableProviders.length > 0 && Date.now() - startTime < QUEUE_TIMEOUT) {
199
  let selectedProvider = null;
200
 
 
207
  break;
208
  }
209
  }
210
+
211
  if (!selectedProvider) {
212
  await new Promise(r => setTimeout(r, 1500));
213
  continue;
214
  }
215
+
216
  let isReleased = false;
217
  const releaseSlot = () => {
218
  if (!isReleased) {
 
220
  isReleased = true;
221
  }
222
  };
223
+
224
  try {
225
  let targetUrl = isImage ? selectedProvider.imageUrl : selectedProvider.url;
226
  let reqMethod = "POST";
 
229
 
230
  if (selectedProvider.apiKey) fetchHeaders["Authorization"] = `Bearer ${selectedProvider.apiKey}`;
231
  if (selectedProvider.proxySecret) fetchHeaders["X-Proxy-Secret"] = selectedProvider.proxySecret;
232
+
233
  const response = await fetch(targetUrl, {
234
  method: reqMethod,
235
  headers: fetchHeaders,
236
  body: reqBody
237
  });
238
+
239
  if (!response.ok) {
240
  logError(selectedProvider.id, `Fallo con c贸digo HTTP ${response.status}`);
241
  releaseSlot();
242
  availableProviders = availableProviders.filter(p => p.id !== selectedProvider.id);
243
  continue;
244
  }
245
+
246
  // Sanitizaci贸n: Evitar devolver cookies o cabeceras de servidor del proveedor original
247
  const responseHeaders = new Headers(response.headers);
248
  responseHeaders.delete('set-cookie');
249
  responseHeaders.delete('server');
250
  responseHeaders.delete('x-powered-by');
251
  responseHeaders.delete('cf-ray');
252
+
253
  if (isImage) {
254
  const contentType = responseHeaders.get("content-type") || "";
255
+
256
  if (contentType.includes("application/json")) {
257
  const jsonResp = await response.json();
258
 
 
311
  'Cache-Control': 'no-cache',
312
  'Connection': 'keep-alive'
313
  });
314
+
315
  if (response.body) {
316
  const stream = Readable.fromWeb(response.body);
317
  stream.pipe(res);
 
329
 
330
  responseSent = true;
331
  return;
332
+
333
  } catch (err) {
334
  logError(selectedProvider.id, `Excepci贸n de red conectando al upstream.`);
335
  releaseSlot();
336
  availableProviders = availableProviders.filter(p => p.id !== selectedProvider.id);
337
  }
338
  }
339
+
340
  if (!responseSent) {
341
  logError("ProxyMain", "Todos los proveedores fallaron o se agot贸 el tiempo de espera.");
342
  return res.status(503).json({ error: { message: "El servicio no est谩 disponible temporalmente. Int茅ntalo de nuevo.", code: 503 } });