qwenproxy-cli 1.6.6 → 1.6.8

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.
@@ -3594,6 +3594,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
3594
3594
  resolveInit = res;
3595
3595
  rejectInit = rej;
3596
3596
  });
3597
+ inFlightDeferred.catch(() => {
3598
+ });
3597
3599
  inFlightAccountInits.set(account.id, inFlightDeferred);
3598
3600
  const initPromise = (async () => {
3599
3601
  const release = await acquireAccountMutex(
@@ -5491,7 +5493,10 @@ function isPlaywrightAlreadyClosedMessage(message) {
5491
5493
  return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
5492
5494
  }
5493
5495
  function isBenignTeardownError(error) {
5494
- if (!error) return false;
5496
+ if (!error) return true;
5497
+ if (typeof error === "object" && Object.keys(error).length === 0 && !(error instanceof Error)) {
5498
+ return true;
5499
+ }
5495
5500
  const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
5496
5501
  return isPlaywrightAlreadyClosedMessage(message);
5497
5502
  }
@@ -19,7 +19,10 @@ function isPlaywrightAlreadyClosedMessage(message) {
19
19
  return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
20
20
  }
21
21
  function isBenignTeardownError(error) {
22
- if (!error) return false;
22
+ if (!error) return true;
23
+ if (typeof error === "object" && Object.keys(error).length === 0 && !(error instanceof Error)) {
24
+ return true;
25
+ }
23
26
  const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
24
27
  return isPlaywrightAlreadyClosedMessage(message);
25
28
  }
@@ -4453,6 +4456,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4453
4456
  resolveInit = res;
4454
4457
  rejectInit = rej;
4455
4458
  });
4459
+ inFlightDeferred.catch(() => {
4460
+ });
4456
4461
  inFlightAccountInits.set(account.id, inFlightDeferred);
4457
4462
  const initPromise = (async () => {
4458
4463
  const release = await acquireAccountMutex(
@@ -7380,6 +7385,10 @@ function stripThinkingSuffix(model) {
7380
7385
  reasoningMode: "thinking"
7381
7386
  };
7382
7387
  }
7388
+ const lower = normalizedModel.toLowerCase();
7389
+ if (lower.includes("omni-flash") || lower.includes("turbo")) {
7390
+ return { baseModel: normalizedModel, enableThinking: false, reasoningMode: "fast" };
7391
+ }
7383
7392
  return { baseModel: normalizedModel, enableThinking: true, reasoningMode: "auto" };
7384
7393
  }
7385
7394
  function mapKnownModelAlias(model) {
@@ -7747,6 +7756,14 @@ async function recoverBaxiaCaptcha(accountId, label, options = {}) {
7747
7756
  );
7748
7757
  const challengeUrl = options.challengeBody ? extractBaxiaChallengeUrl(options.challengeBody, config.qwen.baseUrl) : null;
7749
7758
  activeCaptchaRecoveries.add(accountId);
7759
+ try {
7760
+ const { abortActiveBrowserStreamsForAccount: abortActiveBrowserStreamsForAccount2 } = await Promise.resolve().then(() => (init_qwen(), qwen_exports));
7761
+ abortActiveBrowserStreamsForAccount2(
7762
+ accountId,
7763
+ "Account stream interrupted by captcha challenge recovery"
7764
+ );
7765
+ } catch {
7766
+ }
7750
7767
  try {
7751
7768
  const solved = await withAccountPage(
7752
7769
  accountId,
@@ -8523,6 +8540,7 @@ __export(qwen_exports, {
8523
8540
  QwenUpstreamUnavailableError: () => QwenUpstreamUnavailableError,
8524
8541
  RetryableQwenStreamError: () => RetryableQwenStreamError,
8525
8542
  STREAM_SLOT_MAX_HOLD_MS: () => STREAM_SLOT_MAX_HOLD_MS,
8543
+ abortActiveBrowserStreamsForAccount: () => abortActiveBrowserStreamsForAccount,
8526
8544
  acquireAccountStreamLock: () => acquireAccountStreamLock,
8527
8545
  acquireNewQwenChatSession: () => acquireNewQwenChatSession,
8528
8546
  buildCapturedQwenHeaders: () => buildCapturedQwenHeaders,
@@ -8569,6 +8587,27 @@ __export(qwen_exports, {
8569
8587
  });
8570
8588
  import crypto5 from "crypto";
8571
8589
  import { v4 as uuidv43 } from "uuid";
8590
+ function abortActiveBrowserStreamsForAccount(accountId, reason = "Account browser stream interrupted (page context unloaded)") {
8591
+ let count = 0;
8592
+ for (const [requestId, state] of browserStreamStates) {
8593
+ if (state.accountId === accountId && !state.done) {
8594
+ count++;
8595
+ handleBrowserStreamEvent(requestId, {
8596
+ type: "error",
8597
+ message: reason,
8598
+ errorName: "AbortError"
8599
+ });
8600
+ }
8601
+ }
8602
+ if (count > 0) {
8603
+ logger.debug("[Qwen] Aborted active browser streams for account", {
8604
+ accountId,
8605
+ count,
8606
+ reason
8607
+ });
8608
+ }
8609
+ return count;
8610
+ }
8572
8611
  async function registerBrowserContextStreamBinding(context) {
8573
8612
  if (browserStreamBindingContexts.has(context)) return;
8574
8613
  browserStreamBindingContexts.add(context);
@@ -9217,7 +9256,6 @@ async function requestQwenPersonalizationInBrowser(accountId, method, path6, hea
9217
9256
  headers,
9218
9257
  payload === void 0 ? void 0 : JSON.stringify(payload),
9219
9258
  {
9220
- settingsPage: true,
9221
9259
  referrer: qwenUrl("/settings/personalization"),
9222
9260
  noMutexRecovery: true
9223
9261
  }
@@ -9263,6 +9301,7 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
9263
9301
  }
9264
9302
  const requestId = uuidv43();
9265
9303
  const state = {
9304
+ accountId,
9266
9305
  chunks: [],
9267
9306
  done: false,
9268
9307
  error: null,
@@ -9279,10 +9318,15 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
9279
9318
  let settled = false;
9280
9319
  let abortListener;
9281
9320
  let cancelPromise;
9321
+ let pageCleanup;
9282
9322
  const cleanup = () => {
9283
9323
  if (settled) return;
9284
9324
  settled = true;
9285
9325
  if (abortListener) signal.removeEventListener("abort", abortListener);
9326
+ try {
9327
+ pageCleanup?.();
9328
+ } catch {
9329
+ }
9286
9330
  browserStreamStates.delete(requestId);
9287
9331
  };
9288
9332
  const cancel = () => {
@@ -9316,6 +9360,39 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
9316
9360
  accountId,
9317
9361
  async (page) => {
9318
9362
  await ensureBrowserStreamBinding(page);
9363
+ if (typeof page.on === "function") {
9364
+ const onFrameNavigated = (frame) => {
9365
+ if (!settled && typeof page.mainFrame === "function" && frame === page.mainFrame()) {
9366
+ const url2 = typeof frame.url === "function" ? frame.url() : String(frame.url || "");
9367
+ if (url2.startsWith(qwenOrigin()) && !url2.includes("/auth") && !url2.includes("_____tmd_____") && !url2.includes("/punish")) {
9368
+ return;
9369
+ }
9370
+ handleBrowserStreamEvent(requestId, {
9371
+ type: "error",
9372
+ message: "Qwen browser page navigated away during stream",
9373
+ errorName: "AbortError"
9374
+ });
9375
+ }
9376
+ };
9377
+ const onPageClosed = () => {
9378
+ if (!settled) {
9379
+ handleBrowserStreamEvent(requestId, {
9380
+ type: "error",
9381
+ message: "Qwen browser page closed during stream",
9382
+ errorName: "AbortError"
9383
+ });
9384
+ }
9385
+ };
9386
+ page.on("framenavigated", onFrameNavigated);
9387
+ page.on("close", onPageClosed);
9388
+ pageCleanup = () => {
9389
+ try {
9390
+ page.off?.("framenavigated", onFrameNavigated);
9391
+ page.off?.("close", onPageClosed);
9392
+ } catch {
9393
+ }
9394
+ };
9395
+ }
9319
9396
  if (config.captcha.enabled) {
9320
9397
  captchaWatcher = startBaxiaCaptchaWatcher(
9321
9398
  page,
@@ -9843,7 +9920,6 @@ async function disableNativeTools(accountId) {
9843
9920
  requestHeaders,
9844
9921
  JSON.stringify(payload),
9845
9922
  {
9846
- settingsPage: true,
9847
9923
  referrer: qwenUrl("/settings/personalization")
9848
9924
  }
9849
9925
  );
@@ -10500,7 +10576,7 @@ async function createQwenStreamInternal(prompt, enableThinking, modelId, forcedP
10500
10576
  }
10501
10577
  });
10502
10578
  }
10503
- const baseTimeoutMs = enableThinking ? config.timeouts.reasoningModelTimeout : config.timeouts.idleStreamTimeout;
10579
+ const baseTimeoutMs = config.timeouts.idleStreamTimeout;
10504
10580
  const dynamicIdleTimeoutMs = computeDynamicIdleTimeout({
10505
10581
  enableThinking,
10506
10582
  parallelEscape: options?.parallelEscape,
@@ -10516,9 +10592,10 @@ async function createQwenStreamInternal(prompt, enableThinking, modelId, forcedP
10516
10592
  dynamicTimeout: dynamicIdleTimeoutMs
10517
10593
  });
10518
10594
  const firstChunkDeadlineMs = enableThinking ? Math.max(
10595
+ config.timeouts.reasoningModelTimeout,
10519
10596
  config.timeouts.firstChunkTimeout,
10520
10597
  config.timeouts.timeToFirstByte
10521
- ) : void 0;
10598
+ ) : Math.max(config.timeouts.firstChunkTimeout, config.timeouts.timeToFirstByte);
10522
10599
  return addIdleTimeoutToStream(
10523
10600
  stream,
10524
10601
  controller2,
package/dist/index.js CHANGED
@@ -156,7 +156,10 @@ function isPlaywrightAlreadyClosedMessage(message) {
156
156
  return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
157
157
  }
158
158
  function isBenignTeardownError(error) {
159
- if (!error) return false;
159
+ if (!error) return true;
160
+ if (typeof error === "object" && Object.keys(error).length === 0 && !(error instanceof Error)) {
161
+ return true;
162
+ }
160
163
  const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
161
164
  return isPlaywrightAlreadyClosedMessage(message);
162
165
  }
@@ -5563,6 +5566,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
5563
5566
  resolveInit = res;
5564
5567
  rejectInit = rej;
5565
5568
  });
5569
+ inFlightDeferred.catch(() => {
5570
+ });
5566
5571
  inFlightAccountInits.set(account.id, inFlightDeferred);
5567
5572
  const initPromise = (async () => {
5568
5573
  const release = await acquireAccountMutex(
@@ -8728,6 +8733,10 @@ function stripThinkingSuffix(model) {
8728
8733
  reasoningMode: "thinking"
8729
8734
  };
8730
8735
  }
8736
+ const lower = normalizedModel.toLowerCase();
8737
+ if (lower.includes("omni-flash") || lower.includes("turbo")) {
8738
+ return { baseModel: normalizedModel, enableThinking: false, reasoningMode: "fast" };
8739
+ }
8731
8740
  return { baseModel: normalizedModel, enableThinking: true, reasoningMode: "auto" };
8732
8741
  }
8733
8742
  function mapKnownModelAlias(model) {
@@ -9137,6 +9146,14 @@ async function recoverBaxiaCaptcha(accountId, label, options = {}) {
9137
9146
  );
9138
9147
  const challengeUrl = options.challengeBody ? extractBaxiaChallengeUrl(options.challengeBody, config.qwen.baseUrl) : null;
9139
9148
  activeCaptchaRecoveries.add(accountId);
9149
+ try {
9150
+ const { abortActiveBrowserStreamsForAccount: abortActiveBrowserStreamsForAccount2 } = await Promise.resolve().then(() => (init_qwen(), qwen_exports));
9151
+ abortActiveBrowserStreamsForAccount2(
9152
+ accountId,
9153
+ "Account stream interrupted by captcha challenge recovery"
9154
+ );
9155
+ } catch {
9156
+ }
9140
9157
  try {
9141
9158
  const solved = await withAccountPage(
9142
9159
  accountId,
@@ -11713,6 +11730,7 @@ __export(qwen_exports, {
11713
11730
  QwenUpstreamUnavailableError: () => QwenUpstreamUnavailableError,
11714
11731
  RetryableQwenStreamError: () => RetryableQwenStreamError,
11715
11732
  STREAM_SLOT_MAX_HOLD_MS: () => STREAM_SLOT_MAX_HOLD_MS,
11733
+ abortActiveBrowserStreamsForAccount: () => abortActiveBrowserStreamsForAccount,
11716
11734
  acquireAccountStreamLock: () => acquireAccountStreamLock,
11717
11735
  acquireNewQwenChatSession: () => acquireNewQwenChatSession,
11718
11736
  buildCapturedQwenHeaders: () => buildCapturedQwenHeaders,
@@ -11759,6 +11777,27 @@ __export(qwen_exports, {
11759
11777
  });
11760
11778
  import crypto7 from "crypto";
11761
11779
  import { v4 as uuidv43 } from "uuid";
11780
+ function abortActiveBrowserStreamsForAccount(accountId, reason = "Account browser stream interrupted (page context unloaded)") {
11781
+ let count = 0;
11782
+ for (const [requestId, state] of browserStreamStates) {
11783
+ if (state.accountId === accountId && !state.done) {
11784
+ count++;
11785
+ handleBrowserStreamEvent(requestId, {
11786
+ type: "error",
11787
+ message: reason,
11788
+ errorName: "AbortError"
11789
+ });
11790
+ }
11791
+ }
11792
+ if (count > 0) {
11793
+ logger.debug("[Qwen] Aborted active browser streams for account", {
11794
+ accountId,
11795
+ count,
11796
+ reason
11797
+ });
11798
+ }
11799
+ return count;
11800
+ }
11762
11801
  async function registerBrowserContextStreamBinding(context) {
11763
11802
  if (browserStreamBindingContexts.has(context)) return;
11764
11803
  browserStreamBindingContexts.add(context);
@@ -12407,7 +12446,6 @@ async function requestQwenPersonalizationInBrowser(accountId, method, path21, he
12407
12446
  headers,
12408
12447
  payload === void 0 ? void 0 : JSON.stringify(payload),
12409
12448
  {
12410
- settingsPage: true,
12411
12449
  referrer: qwenUrl("/settings/personalization"),
12412
12450
  noMutexRecovery: true
12413
12451
  }
@@ -12453,6 +12491,7 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
12453
12491
  }
12454
12492
  const requestId = uuidv43();
12455
12493
  const state = {
12494
+ accountId,
12456
12495
  chunks: [],
12457
12496
  done: false,
12458
12497
  error: null,
@@ -12469,10 +12508,15 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
12469
12508
  let settled = false;
12470
12509
  let abortListener;
12471
12510
  let cancelPromise;
12511
+ let pageCleanup;
12472
12512
  const cleanup = () => {
12473
12513
  if (settled) return;
12474
12514
  settled = true;
12475
12515
  if (abortListener) signal.removeEventListener("abort", abortListener);
12516
+ try {
12517
+ pageCleanup?.();
12518
+ } catch {
12519
+ }
12476
12520
  browserStreamStates.delete(requestId);
12477
12521
  };
12478
12522
  const cancel = () => {
@@ -12506,6 +12550,39 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
12506
12550
  accountId,
12507
12551
  async (page) => {
12508
12552
  await ensureBrowserStreamBinding(page);
12553
+ if (typeof page.on === "function") {
12554
+ const onFrameNavigated = (frame) => {
12555
+ if (!settled && typeof page.mainFrame === "function" && frame === page.mainFrame()) {
12556
+ const url2 = typeof frame.url === "function" ? frame.url() : String(frame.url || "");
12557
+ if (url2.startsWith(qwenOrigin()) && !url2.includes("/auth") && !url2.includes("_____tmd_____") && !url2.includes("/punish")) {
12558
+ return;
12559
+ }
12560
+ handleBrowserStreamEvent(requestId, {
12561
+ type: "error",
12562
+ message: "Qwen browser page navigated away during stream",
12563
+ errorName: "AbortError"
12564
+ });
12565
+ }
12566
+ };
12567
+ const onPageClosed = () => {
12568
+ if (!settled) {
12569
+ handleBrowserStreamEvent(requestId, {
12570
+ type: "error",
12571
+ message: "Qwen browser page closed during stream",
12572
+ errorName: "AbortError"
12573
+ });
12574
+ }
12575
+ };
12576
+ page.on("framenavigated", onFrameNavigated);
12577
+ page.on("close", onPageClosed);
12578
+ pageCleanup = () => {
12579
+ try {
12580
+ page.off?.("framenavigated", onFrameNavigated);
12581
+ page.off?.("close", onPageClosed);
12582
+ } catch {
12583
+ }
12584
+ };
12585
+ }
12509
12586
  if (config.captcha.enabled) {
12510
12587
  captchaWatcher = startBaxiaCaptchaWatcher(
12511
12588
  page,
@@ -13033,7 +13110,6 @@ async function disableNativeTools(accountId) {
13033
13110
  requestHeaders,
13034
13111
  JSON.stringify(payload),
13035
13112
  {
13036
- settingsPage: true,
13037
13113
  referrer: qwenUrl("/settings/personalization")
13038
13114
  }
13039
13115
  );
@@ -13690,7 +13766,7 @@ async function createQwenStreamInternal(prompt, enableThinking, modelId, forcedP
13690
13766
  }
13691
13767
  });
13692
13768
  }
13693
- const baseTimeoutMs = enableThinking ? config.timeouts.reasoningModelTimeout : config.timeouts.idleStreamTimeout;
13769
+ const baseTimeoutMs = config.timeouts.idleStreamTimeout;
13694
13770
  const dynamicIdleTimeoutMs = computeDynamicIdleTimeout({
13695
13771
  enableThinking,
13696
13772
  parallelEscape: options?.parallelEscape,
@@ -13706,9 +13782,10 @@ async function createQwenStreamInternal(prompt, enableThinking, modelId, forcedP
13706
13782
  dynamicTimeout: dynamicIdleTimeoutMs
13707
13783
  });
13708
13784
  const firstChunkDeadlineMs = enableThinking ? Math.max(
13785
+ config.timeouts.reasoningModelTimeout,
13709
13786
  config.timeouts.firstChunkTimeout,
13710
13787
  config.timeouts.timeToFirstByte
13711
- ) : void 0;
13788
+ ) : Math.max(config.timeouts.firstChunkTimeout, config.timeouts.timeToFirstByte);
13712
13789
  return addIdleTimeoutToStream(
13713
13790
  stream,
13714
13791
  controller2,
@@ -22948,7 +23025,7 @@ Please retry your tool call(s) with correct JSON and valid tool names from the a
22948
23025
  stack: err.stack?.split("\n").slice(0, 3).join(" | "),
22949
23026
  completionId
22950
23027
  };
22951
- if (retryable) {
23028
+ if (retryable || isNetworkLikeError(err) || isAbortError(err)) {
22952
23029
  if (activeAccountId && isNetworkLikeError(err)) {
22953
23030
  noteMidStreamNetworkFailure(activeAccountId);
22954
23031
  }
@@ -27018,7 +27095,7 @@ function getAppVersion() {
27018
27095
  }
27019
27096
  } catch {
27020
27097
  }
27021
- cachedVersion = "v1.6.6";
27098
+ cachedVersion = "v1.6.8";
27022
27099
  return cachedVersion;
27023
27100
  }
27024
27101
  var cachedVersion;
package/dist/login.js CHANGED
@@ -1108,7 +1108,10 @@ function isPlaywrightAlreadyClosedMessage(message) {
1108
1108
  return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
1109
1109
  }
1110
1110
  function isBenignTeardownError(error) {
1111
- if (!error) return false;
1111
+ if (!error) return true;
1112
+ if (typeof error === "object" && Object.keys(error).length === 0 && !(error instanceof Error)) {
1113
+ return true;
1114
+ }
1112
1115
  const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
1113
1116
  return isPlaywrightAlreadyClosedMessage(message);
1114
1117
  }
@@ -4426,6 +4429,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4426
4429
  resolveInit = res;
4427
4430
  rejectInit = rej;
4428
4431
  });
4432
+ inFlightDeferred.catch(() => {
4433
+ });
4429
4434
  inFlightAccountInits.set(account.id, inFlightDeferred);
4430
4435
  const initPromise = (async () => {
4431
4436
  const release = await acquireAccountMutex(
@@ -2825,7 +2825,10 @@ function isPlaywrightAlreadyClosedMessage(message) {
2825
2825
  return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
2826
2826
  }
2827
2827
  function isBenignTeardownError(error) {
2828
- if (!error) return false;
2828
+ if (!error) return true;
2829
+ if (typeof error === "object" && Object.keys(error).length === 0 && !(error instanceof Error)) {
2830
+ return true;
2831
+ }
2829
2832
  const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
2830
2833
  return isPlaywrightAlreadyClosedMessage(message);
2831
2834
  }
@@ -3857,6 +3860,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
3857
3860
  resolveInit = res;
3858
3861
  rejectInit = rej;
3859
3862
  });
3863
+ inFlightDeferred.catch(() => {
3864
+ });
3860
3865
  inFlightAccountInits.set(account.id, inFlightDeferred);
3861
3866
  const initPromise = (async () => {
3862
3867
  const release = await acquireAccountMutex(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwenproxy-cli",
3
- "version": "1.6.6",
3
+ "version": "1.6.8",
4
4
  "description": "High-performance OpenAI & Anthropic compatible API gateway for Qwen with multi-account rotation, interactive TUI, and resilient tool calling.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -76,6 +76,12 @@ export function stripThinkingSuffix(model: string): {
76
76
  };
77
77
  }
78
78
 
79
+ // Default for non-thinking models (omni-flash, turbo)
80
+ const lower = normalizedModel.toLowerCase();
81
+ if (lower.includes("omni-flash") || lower.includes("turbo")) {
82
+ return { baseModel: normalizedModel, enableThinking: false, reasoningMode: "fast" };
83
+ }
84
+
79
85
  // Default: auto thinking (Qwen decides)
80
86
  return { baseModel: normalizedModel, enableThinking: true, reasoningMode: "auto" };
81
87
  }
@@ -27,7 +27,14 @@ export function isPlaywrightAlreadyClosedMessage(message: string): boolean {
27
27
  }
28
28
 
29
29
  export function isBenignTeardownError(error: unknown): boolean {
30
- if (!error) return false;
30
+ if (!error) return true;
31
+ if (
32
+ typeof error === "object" &&
33
+ Object.keys(error).length === 0 &&
34
+ !(error instanceof Error)
35
+ ) {
36
+ return true;
37
+ }
31
38
  const message =
32
39
  error instanceof Error
33
40
  ? error.message
@@ -17,6 +17,6 @@ export function getAppVersion(): string {
17
17
  }
18
18
  }
19
19
  } catch {}
20
- cachedVersion = "v1.6.6";
20
+ cachedVersion = "v1.6.8";
21
21
  return cachedVersion;
22
22
  }
@@ -2819,7 +2819,7 @@ export async function processStreamingResponse(
2819
2819
  completionId,
2820
2820
  };
2821
2821
 
2822
- if (retryable) {
2822
+ if (retryable || isNetworkLikeError(err) || isAbortError(err)) {
2823
2823
  if (activeAccountId && isNetworkLikeError(err)) {
2824
2824
  noteMidStreamNetworkFailure(activeAccountId);
2825
2825
  }
@@ -125,6 +125,13 @@ export async function recoverBaxiaCaptcha(
125
125
  : null;
126
126
 
127
127
  activeCaptchaRecoveries.add(accountId);
128
+ try {
129
+ const { abortActiveBrowserStreamsForAccount } = await import("./qwen.ts");
130
+ abortActiveBrowserStreamsForAccount(
131
+ accountId,
132
+ "Account stream interrupted by captcha challenge recovery",
133
+ );
134
+ } catch {}
128
135
  try {
129
136
  // recoverOnTimeout: false ensures that if the captcha solve times out,
130
137
  // withAccountPage does NOT destructively kill the browser context, which
@@ -1535,6 +1535,8 @@ export async function initPlaywrightForAccount(
1535
1535
  resolveInit = res;
1536
1536
  rejectInit = rej;
1537
1537
  });
1538
+ // Prevent UnhandledRejection if rejectInit is called when no concurrent callers are awaiting
1539
+ inFlightDeferred.catch(() => {});
1538
1540
  inFlightAccountInits.set(account.id, inFlightDeferred);
1539
1541
 
1540
1542
  const initPromise = (async () => {
@@ -117,6 +117,7 @@ interface BrowserStreamMetadata {
117
117
  }
118
118
 
119
119
  interface BrowserStreamState {
120
+ accountId?: string;
120
121
  chunks: Uint8Array[];
121
122
  done: boolean;
122
123
  error: Error | null;
@@ -128,6 +129,36 @@ const browserStreamStates = new Map<string, BrowserStreamState>();
128
129
  const browserStreamBindingPages = new WeakSet<object>();
129
130
  const browserStreamBindingContexts = new WeakSet<object>();
130
131
 
132
+ /**
133
+ * Abort all active browser streams for an account immediately.
134
+ * Called when an account page is navigated by captcha recovery, closed, or reset,
135
+ * so in-flight streams fail fast (retryable) instead of stalling for minutes.
136
+ */
137
+ export function abortActiveBrowserStreamsForAccount(
138
+ accountId: string,
139
+ reason = "Account browser stream interrupted (page context unloaded)",
140
+ ): number {
141
+ let count = 0;
142
+ for (const [requestId, state] of browserStreamStates) {
143
+ if (state.accountId === accountId && !state.done) {
144
+ count++;
145
+ handleBrowserStreamEvent(requestId, {
146
+ type: "error",
147
+ message: reason,
148
+ errorName: "AbortError",
149
+ });
150
+ }
151
+ }
152
+ if (count > 0) {
153
+ logger.debug("[Qwen] Aborted active browser streams for account", {
154
+ accountId,
155
+ count,
156
+ reason,
157
+ });
158
+ }
159
+ return count;
160
+ }
161
+
131
162
  export async function registerBrowserContextStreamBinding(
132
163
  context: BrowserContext,
133
164
  ): Promise<void> {
@@ -1194,7 +1225,6 @@ async function requestQwenPersonalizationInBrowser(
1194
1225
  headers,
1195
1226
  payload === undefined ? undefined : JSON.stringify(payload),
1196
1227
  {
1197
- settingsPage: true,
1198
1228
  referrer: qwenUrl("/settings/personalization"),
1199
1229
  noMutexRecovery: true,
1200
1230
  },
@@ -1263,6 +1293,7 @@ async function createQwenBrowserResponse(
1263
1293
 
1264
1294
  const requestId = uuidv4();
1265
1295
  const state: BrowserStreamState = {
1296
+ accountId,
1266
1297
  chunks: [],
1267
1298
  done: false,
1268
1299
  error: null,
@@ -1283,10 +1314,14 @@ async function createQwenBrowserResponse(
1283
1314
  let settled = false;
1284
1315
  let abortListener: (() => void) | undefined;
1285
1316
  let cancelPromise: Promise<void> | undefined;
1317
+ let pageCleanup: (() => void) | undefined;
1286
1318
  const cleanup = () => {
1287
1319
  if (settled) return;
1288
1320
  settled = true;
1289
1321
  if (abortListener) signal.removeEventListener("abort", abortListener);
1322
+ try {
1323
+ pageCleanup?.();
1324
+ } catch {}
1290
1325
  browserStreamStates.delete(requestId);
1291
1326
  };
1292
1327
  const cancel = () => {
@@ -1327,6 +1362,44 @@ async function createQwenBrowserResponse(
1327
1362
  accountId,
1328
1363
  async (page) => {
1329
1364
  await ensureBrowserStreamBinding(page);
1365
+ if (typeof (page as any).on === "function") {
1366
+ const onFrameNavigated = (frame: any) => {
1367
+ if (!settled && typeof (page as any).mainFrame === "function" && frame === (page as any).mainFrame()) {
1368
+ const url = typeof frame.url === "function" ? frame.url() : String(frame.url || "");
1369
+ // Safe SPA in-app routing within Qwen Web (e.g. /c/<id>, /#) must NEVER abort active streams
1370
+ if (
1371
+ url.startsWith(qwenOrigin()) &&
1372
+ !url.includes("/auth") &&
1373
+ !url.includes("_____tmd_____") &&
1374
+ !url.includes("/punish")
1375
+ ) {
1376
+ return;
1377
+ }
1378
+ handleBrowserStreamEvent(requestId, {
1379
+ type: "error",
1380
+ message: "Qwen browser page navigated away during stream",
1381
+ errorName: "AbortError",
1382
+ });
1383
+ }
1384
+ };
1385
+ const onPageClosed = () => {
1386
+ if (!settled) {
1387
+ handleBrowserStreamEvent(requestId, {
1388
+ type: "error",
1389
+ message: "Qwen browser page closed during stream",
1390
+ errorName: "AbortError",
1391
+ });
1392
+ }
1393
+ };
1394
+ (page as any).on("framenavigated", onFrameNavigated);
1395
+ (page as any).on("close", onPageClosed);
1396
+ pageCleanup = () => {
1397
+ try {
1398
+ (page as any).off?.("framenavigated", onFrameNavigated);
1399
+ (page as any).off?.("close", onPageClosed);
1400
+ } catch {}
1401
+ };
1402
+ }
1330
1403
  if (config.captcha.enabled) {
1331
1404
  captchaWatcher = startBaxiaCaptchaWatcher(
1332
1405
  page,
@@ -2049,7 +2122,6 @@ export async function disableNativeTools(accountId?: string): Promise<void> {
2049
2122
  requestHeaders,
2050
2123
  JSON.stringify(payload),
2051
2124
  {
2052
- settingsPage: true,
2053
2125
  referrer: qwenUrl("/settings/personalization"),
2054
2126
  },
2055
2127
  );
@@ -3011,13 +3083,9 @@ async function createQwenStreamInternal(
3011
3083
  }
3012
3084
 
3013
3085
  // Dynamic idle timeout based on model type and payload size
3014
- // Reasoning models (thinking enabled): use REASONING_MODEL_TIMEOUT as base
3015
- // Non-reasoning models: use IDLE_STREAM_TIMEOUT as base
3016
- // Both add 30s per MB of payload. Parallel-escape streams get a tight cap
3017
- // ONLY when non-thinking (see computeDynamicIdleTimeout).
3018
- const baseTimeoutMs = enableThinking
3019
- ? config.timeouts.reasoningModelTimeout
3020
- : config.timeouts.idleStreamTimeout;
3086
+ // Base timeout for gaps between chunks is governed by IDLE_STREAM_TIMEOUT (default 60s)
3087
+ // plus dynamic payload scaling (30s per MB).
3088
+ const baseTimeoutMs = config.timeouts.idleStreamTimeout;
3021
3089
  const dynamicIdleTimeoutMs = computeDynamicIdleTimeout({
3022
3090
  enableThinking,
3023
3091
  parallelEscape: options?.parallelEscape,
@@ -3034,15 +3102,15 @@ async function createQwenStreamInternal(
3034
3102
  dynamicTimeout: dynamicIdleTimeoutMs,
3035
3103
  });
3036
3104
 
3037
- // Thinking models idle at 600s — fine for gaps AFTER data flows, but a
3038
- // stream that produced NOTHING in the first-chunk window is dead. Fail
3039
- // fast (retryable) so the account slot is not held for 10 minutes.
3105
+ // Thinking models legitimately pause before first chunk (up to reasoningModelTimeout).
3106
+ // After the first chunk, the normal dynamicIdleTimeoutMs (60s+) governs chunk gaps.
3040
3107
  const firstChunkDeadlineMs = enableThinking
3041
3108
  ? Math.max(
3109
+ config.timeouts.reasoningModelTimeout,
3042
3110
  config.timeouts.firstChunkTimeout,
3043
3111
  config.timeouts.timeToFirstByte,
3044
3112
  )
3045
- : undefined;
3113
+ : Math.max(config.timeouts.firstChunkTimeout, config.timeouts.timeToFirstByte);
3046
3114
 
3047
3115
  return addIdleTimeoutToStream(
3048
3116
  stream,