@pedrofariasx/qwenproxy 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/routes/chat.ts +124 -72
package/package.json
CHANGED
package/src/routes/chat.ts
CHANGED
|
@@ -272,48 +272,75 @@ export async function chatCompletions(c: Context) {
|
|
|
272
272
|
const isNewSession = !messages.some(m => m.role === 'assistant');
|
|
273
273
|
|
|
274
274
|
// Account selection with fallback on rate-limit/failure
|
|
275
|
-
|
|
276
|
-
const triedAccountIds = new Set<string>();
|
|
277
|
-
let lastError: any = null;
|
|
278
|
-
|
|
275
|
+
const isGuestModeOnly = process.env.QWEN_GUEST_MODE_ONLY?.toLowerCase() === 'true';
|
|
279
276
|
let stream: ReadableStream | undefined;
|
|
280
277
|
let uiSessionId = '';
|
|
281
278
|
const completionId = 'chatcmpl-' + crypto.randomUUID();
|
|
279
|
+
let lastError: any = null;
|
|
282
280
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
281
|
+
if (isGuestModeOnly) {
|
|
282
|
+
console.log('[Chat] Guest mode only enabled. Bypassing account rotation.');
|
|
283
|
+
try {
|
|
284
|
+
const result = await createQwenStream(
|
|
285
|
+
finalPrompt,
|
|
286
|
+
isThinkingModel,
|
|
287
|
+
body.model,
|
|
288
|
+
null,
|
|
289
|
+
'guest',
|
|
290
|
+
undefined,
|
|
291
|
+
pendingMultimodal.length > 0 ? pendingMultimodal : undefined
|
|
292
|
+
);
|
|
293
|
+
stream = result.stream;
|
|
294
|
+
uiSessionId = result.uiSessionId;
|
|
295
|
+
registerStream(completionId, {
|
|
296
|
+
abortController: result.controller,
|
|
297
|
+
accountId: 'guest',
|
|
298
|
+
uiSessionId: result.uiSessionId,
|
|
299
|
+
targetResponseId: '',
|
|
300
|
+
headers: result.headers,
|
|
301
|
+
});
|
|
302
|
+
} catch (err: any) {
|
|
303
|
+
console.error('[Chat] Guest mode failed:', err.message);
|
|
304
|
+
throw err;
|
|
290
305
|
}
|
|
291
|
-
|
|
306
|
+
} else {
|
|
307
|
+
let account = getNextAccount();
|
|
308
|
+
const triedAccountIds = new Set<string>();
|
|
292
309
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
account = getNextAvailableAccount(triedAccountIds);
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
310
|
+
while (account) {
|
|
311
|
+
const accountId = account.id;
|
|
312
|
+
const accountEmail = account.email;
|
|
299
313
|
|
|
300
|
-
|
|
314
|
+
if (triedAccountIds.has(accountId)) {
|
|
315
|
+
account = getNextAvailableAccount(triedAccountIds);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
triedAccountIds.add(accountId);
|
|
301
319
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
320
|
+
const cooldownInfo = getAccountCooldownInfo(accountId);
|
|
321
|
+
if (cooldownInfo && accountId !== 'global') {
|
|
322
|
+
console.log(`[Chat] Skipping account ${accountEmail} (${accountId}) — on cooldown for ${Math.round(cooldownInfo.remainingMs / 1000)}s (${cooldownInfo.reason})`);
|
|
323
|
+
account = getNextAvailableAccount(triedAccountIds);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
305
326
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
327
|
+
console.log(`[Chat] Routing request to account: ${accountEmail} (${accountId})`);
|
|
328
|
+
|
|
329
|
+
let retries = 3;
|
|
330
|
+
let retryDelay = 500;
|
|
331
|
+
let success = false;
|
|
332
|
+
|
|
333
|
+
while (retries > 0) {
|
|
334
|
+
try {
|
|
335
|
+
const result = await createQwenStream(
|
|
336
|
+
finalPrompt,
|
|
337
|
+
isThinkingModel,
|
|
338
|
+
body.model,
|
|
339
|
+
null, // Always force new chat for concurrency isolation
|
|
340
|
+
accountId === 'global' ? undefined : accountId,
|
|
341
|
+
undefined,
|
|
342
|
+
pendingMultimodal.length > 0 ? pendingMultimodal : undefined
|
|
343
|
+
);
|
|
317
344
|
stream = result.stream;
|
|
318
345
|
uiSessionId = result.uiSessionId;
|
|
319
346
|
registerStream(completionId, {
|
|
@@ -325,59 +352,84 @@ export async function chatCompletions(c: Context) {
|
|
|
325
352
|
});
|
|
326
353
|
success = true;
|
|
327
354
|
break;
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
355
|
+
} catch (err: any) {
|
|
356
|
+
retries--;
|
|
357
|
+
|
|
358
|
+
if (err.upstreamCode === 'RateLimited' || err.upstreamStatus === 429) {
|
|
359
|
+
const hourHint = err.message?.match(/Wait about (\d+) hour/);
|
|
360
|
+
const hours = hourHint ? parseInt(hourHint[1]) : 24;
|
|
361
|
+
const cooldownMs = hours * 60 * 60 * 1000;
|
|
362
|
+
markAccountRateLimited(accountId, cooldownMs, 'RateLimited');
|
|
363
|
+
console.warn(`[Chat] Account ${accountEmail} (${accountId}) rate-limited. Entering cooldown for ${hours} hours.`);
|
|
364
|
+
lastError = err;
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
340
367
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
368
|
+
if (retries === 0) {
|
|
369
|
+
if (err.upstreamStatus && err.upstreamStatus >= 500) {
|
|
370
|
+
markAccountRateLimited(accountId, undefined, 'ServerError');
|
|
371
|
+
console.warn(`[Chat] Account ${accountEmail} (${accountId}) returned server error. Marked for cooldown.`);
|
|
372
|
+
}
|
|
373
|
+
lastError = err;
|
|
374
|
+
break;
|
|
345
375
|
}
|
|
346
|
-
lastError = err;
|
|
347
|
-
break;
|
|
348
|
-
}
|
|
349
376
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
377
|
+
let useDelay = retryDelay;
|
|
378
|
+
if (err instanceof RetryableQwenStreamError && err.retryAfterMs !== undefined) {
|
|
379
|
+
useDelay = err.retryAfterMs;
|
|
380
|
+
}
|
|
381
|
+
const isRetryable = err instanceof RetryableQwenStreamError || err.message?.includes('in progress') || err.message?.includes('Bad_Request');
|
|
382
|
+
if (!isRetryable) {
|
|
383
|
+
lastError = err;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
console.warn(`[Chat] Qwen request failed for ${accountEmail}, retrying in ${useDelay}ms... (${retries} left)`);
|
|
387
|
+
await new Promise(r => setTimeout(r, useDelay));
|
|
388
|
+
retryDelay = Math.min(retryDelay * 2, 5000);
|
|
358
389
|
}
|
|
359
|
-
console.warn(`[Chat] Qwen request failed for ${accountEmail}, retrying in ${useDelay}ms... (${retries} left)`);
|
|
360
|
-
await new Promise(r => setTimeout(r, useDelay));
|
|
361
|
-
retryDelay = Math.min(retryDelay * 2, 5000);
|
|
362
390
|
}
|
|
363
|
-
}
|
|
364
391
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
392
|
+
if (success) {
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
368
395
|
|
|
369
|
-
|
|
396
|
+
account = getNextAvailableAccount(triedAccountIds);
|
|
397
|
+
}
|
|
370
398
|
}
|
|
371
399
|
|
|
372
400
|
if (!stream) {
|
|
373
401
|
removeStream(completionId);
|
|
374
|
-
// Check if all accounts are on cooldown
|
|
375
402
|
const accounts = loadAccounts();
|
|
376
|
-
const allOnCooldown = accounts.every(a => getAccountCooldownInfo(a.id) !== null);
|
|
403
|
+
const allOnCooldown = accounts.length === 0 || accounts.every(a => getAccountCooldownInfo(a.id) !== null);
|
|
404
|
+
|
|
377
405
|
if (allOnCooldown) {
|
|
378
|
-
console.warn(`[Chat] CRITICAL: All
|
|
406
|
+
console.warn(`[Chat] CRITICAL: All accounts are rate-limited, on cooldown, or none configured! Falling back to GUEST mode.`);
|
|
407
|
+
try {
|
|
408
|
+
const result = await createQwenStream(
|
|
409
|
+
finalPrompt,
|
|
410
|
+
isThinkingModel,
|
|
411
|
+
body.model,
|
|
412
|
+
null,
|
|
413
|
+
'guest',
|
|
414
|
+
undefined,
|
|
415
|
+
pendingMultimodal.length > 0 ? pendingMultimodal : undefined
|
|
416
|
+
);
|
|
417
|
+
stream = result.stream;
|
|
418
|
+
uiSessionId = result.uiSessionId;
|
|
419
|
+
registerStream(completionId, {
|
|
420
|
+
abortController: result.controller,
|
|
421
|
+
accountId: 'guest',
|
|
422
|
+
uiSessionId: result.uiSessionId,
|
|
423
|
+
targetResponseId: '',
|
|
424
|
+
headers: result.headers,
|
|
425
|
+
});
|
|
426
|
+
} catch (guestErr: any) {
|
|
427
|
+
console.error('[Chat] Guest mode also failed:', guestErr.message);
|
|
428
|
+
throw lastError || new Error('All accounts and guest mode failed');
|
|
429
|
+
}
|
|
430
|
+
} else {
|
|
431
|
+
throw lastError || new Error('All accounts failed');
|
|
379
432
|
}
|
|
380
|
-
throw lastError || new Error('All accounts failed');
|
|
381
433
|
}
|
|
382
434
|
|
|
383
435
|
if (!isStream) {
|