@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/routes/chat.ts +124 -72
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pedrofariasx/qwenproxy",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Local OpenAI-compatible proxy API that routes requests to Qwen (chat.qwen.ai) via Playwright browser automation.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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
- let account = getNextAccount();
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
- while (account) {
284
- const accountId = account.id;
285
- const accountEmail = account.email;
286
-
287
- if (triedAccountIds.has(accountId)) {
288
- account = getNextAvailableAccount(triedAccountIds);
289
- continue;
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
- triedAccountIds.add(accountId);
306
+ } else {
307
+ let account = getNextAccount();
308
+ const triedAccountIds = new Set<string>();
292
309
 
293
- const cooldownInfo = getAccountCooldownInfo(accountId);
294
- if (cooldownInfo && accountId !== 'global') {
295
- console.log(`[Chat] Skipping account ${accountEmail} (${accountId}) — on cooldown for ${Math.round(cooldownInfo.remainingMs / 1000)}s (${cooldownInfo.reason})`);
296
- account = getNextAvailableAccount(triedAccountIds);
297
- continue;
298
- }
310
+ while (account) {
311
+ const accountId = account.id;
312
+ const accountEmail = account.email;
299
313
 
300
- console.log(`[Chat] Routing request to account: ${accountEmail} (${accountId})`);
314
+ if (triedAccountIds.has(accountId)) {
315
+ account = getNextAvailableAccount(triedAccountIds);
316
+ continue;
317
+ }
318
+ triedAccountIds.add(accountId);
301
319
 
302
- let retries = 3;
303
- let retryDelay = 500;
304
- let success = false;
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
- while (retries > 0) {
307
- try {
308
- const result = await createQwenStream(
309
- finalPrompt,
310
- isThinkingModel,
311
- body.model,
312
- null, // Always force new chat for concurrency isolation
313
- accountId === 'global' ? undefined : accountId,
314
- undefined,
315
- pendingMultimodal.length > 0 ? pendingMultimodal : undefined
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
- } catch (err: any) {
329
- retries--;
330
-
331
- if (err.upstreamCode === 'RateLimited' || err.upstreamStatus === 429) {
332
- const hourHint = err.message?.match(/Wait about (\d+) hour/);
333
- const hours = hourHint ? parseInt(hourHint[1]) : 24;
334
- const cooldownMs = hours * 60 * 60 * 1000;
335
- markAccountRateLimited(accountId, cooldownMs, 'RateLimited');
336
- console.warn(`[Chat] Account ${accountEmail} (${accountId}) rate-limited. Entering cooldown for ${hours} hours.`);
337
- lastError = err;
338
- break;
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
- if (retries === 0) {
342
- if (err.upstreamStatus && err.upstreamStatus >= 500) {
343
- markAccountRateLimited(accountId, undefined, 'ServerError');
344
- console.warn(`[Chat] Account ${accountEmail} (${accountId}) returned server error. Marked for cooldown.`);
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
- let useDelay = retryDelay;
351
- if (err instanceof RetryableQwenStreamError && err.retryAfterMs !== undefined) {
352
- useDelay = err.retryAfterMs;
353
- }
354
- const isRetryable = err instanceof RetryableQwenStreamError || err.message?.includes('in progress') || err.message?.includes('Bad_Request');
355
- if (!isRetryable) {
356
- lastError = err;
357
- break;
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
- if (success) {
366
- break;
367
- }
392
+ if (success) {
393
+ break;
394
+ }
368
395
 
369
- account = getNextAvailableAccount(triedAccountIds);
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 ${accounts.length} accounts are currently rate-limited or on cooldown!`);
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) {