qwenproxy-cli 1.6.3 → 1.6.5

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.
@@ -1915,6 +1915,7 @@ var init_mutex = __esm({
1915
1915
  locked = false;
1916
1916
  lockedAt = 0;
1917
1917
  lockedByKey = "";
1918
+ epoch = 0;
1918
1919
  async acquire(timeoutMs = 3e5, key = "") {
1919
1920
  const holdLimitMs = this.maxHoldMs;
1920
1921
  if (this.locked && Date.now() - this.lockedAt > holdLimitMs) {
@@ -1925,6 +1926,7 @@ var init_mutex = __esm({
1925
1926
  this.locked = false;
1926
1927
  this.lockedAt = 0;
1927
1928
  this.lockedByKey = "";
1929
+ this.epoch++;
1928
1930
  }
1929
1931
  if (!this.locked) {
1930
1932
  this.locked = true;
@@ -1963,10 +1965,12 @@ var init_mutex = __esm({
1963
1965
  }
1964
1966
  }
1965
1967
  createRelease() {
1968
+ const epoch = this.epoch;
1966
1969
  let released = false;
1967
1970
  return () => {
1968
1971
  if (released) return;
1969
1972
  released = true;
1973
+ if (epoch !== this.epoch) return;
1970
1974
  this.release();
1971
1975
  };
1972
1976
  }
@@ -2906,6 +2910,49 @@ var init_waf_isolation = __esm({
2906
2910
  }
2907
2911
  });
2908
2912
 
2913
+ // src/utils/timezone.ts
2914
+ function formatAsciiTimezone(date = /* @__PURE__ */ new Date()) {
2915
+ const dayName = DAYS[date.getDay()];
2916
+ const monthName = MONTHS[date.getMonth()];
2917
+ const day = String(date.getDate()).padStart(2, "0");
2918
+ const year = date.getFullYear();
2919
+ const hours = String(date.getHours()).padStart(2, "0");
2920
+ const minutes = String(date.getMinutes()).padStart(2, "0");
2921
+ const seconds = String(date.getSeconds()).padStart(2, "0");
2922
+ const offsetMinutes = -date.getTimezoneOffset();
2923
+ const sign = offsetMinutes >= 0 ? "+" : "-";
2924
+ const absOffset = Math.abs(offsetMinutes);
2925
+ const offsetHours = String(Math.floor(absOffset / 60)).padStart(2, "0");
2926
+ const offsetMins = String(absOffset % 60).padStart(2, "0");
2927
+ const tzOffset = `GMT${sign}${offsetHours}${offsetMins}`;
2928
+ return `${dayName} ${monthName} ${day} ${year} ${hours}:${minutes}:${seconds} ${tzOffset}`;
2929
+ }
2930
+ function sanitizeHeaderForFetch(val) {
2931
+ if (typeof val !== "string") return "";
2932
+ return val.replace(/[^\x00-\xFF]/g, "");
2933
+ }
2934
+ var DAYS, MONTHS;
2935
+ var init_timezone = __esm({
2936
+ "src/utils/timezone.ts"() {
2937
+ "use strict";
2938
+ DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
2939
+ MONTHS = [
2940
+ "Jan",
2941
+ "Feb",
2942
+ "Mar",
2943
+ "Apr",
2944
+ "May",
2945
+ "Jun",
2946
+ "Jul",
2947
+ "Aug",
2948
+ "Sep",
2949
+ "Oct",
2950
+ "Nov",
2951
+ "Dec"
2952
+ ];
2953
+ }
2954
+ });
2955
+
2909
2956
  // src/services/qwen-headers.ts
2910
2957
  import { v4 as uuidv4 } from "uuid";
2911
2958
  function getSystemAcceptLanguage(customLocale) {
@@ -2949,8 +2996,8 @@ function buildQwenRequestHeaders(opts) {
2949
2996
  "bx-v": opts.bxV || "2.5.37",
2950
2997
  source: "web",
2951
2998
  version: opts.version || getQwenWebVersion(),
2952
- timezone: opts.extra?.timezone || opts.extra?.Timezone || (/* @__PURE__ */ new Date()).toString().split(" (")[0],
2953
- Timezone: opts.extra?.Timezone || opts.extra?.timezone || (/* @__PURE__ */ new Date()).toString().split(" (")[0],
2999
+ timezone: opts.extra?.timezone || opts.extra?.Timezone || formatAsciiTimezone(),
3000
+ Timezone: opts.extra?.Timezone || opts.extra?.timezone || formatAsciiTimezone(),
2954
3001
  // Use the real browser client-hints when captured (anti-hardcoded); fall
2955
3002
  // back to the static fingerprint otherwise.
2956
3003
  "sec-ch-ua": opts.secChUa || `"Google Chrome";v="${getChromeMajor()}", "Chromium";v="${getChromeMajor()}", "Not.A/Brand";v="99"`,
@@ -2977,6 +3024,7 @@ var init_qwen_headers = __esm({
2977
3024
  init_qwen_url();
2978
3025
  init_config();
2979
3026
  init_fingerprint();
3027
+ init_timezone();
2980
3028
  dynamicWebVersion = null;
2981
3029
  QWEN_WEB_VERSION = config.qwen.webVersion;
2982
3030
  DEFAULT_QWEN_USER_AGENT = getDefaultQwenUserAgent(151);
@@ -3213,6 +3261,7 @@ __export(playwright_exports, {
3213
3261
  closeIdlePlaywrightAccounts: () => closeIdlePlaywrightAccounts,
3214
3262
  closePlaywrightForAccount: () => closePlaywrightForAccount,
3215
3263
  evictIdlePlaywrightContextsToLimit: () => evictIdlePlaywrightContextsToLimit,
3264
+ getAccountMutex: () => getAccountMutex,
3216
3265
  getActivePlaywrightAccountIds: () => getActivePlaywrightAccountIds,
3217
3266
  getBasicHeaders: () => getBasicHeaders,
3218
3267
  getCookies: () => getCookies,
@@ -3362,9 +3411,10 @@ function getAccountMutex(accountId) {
3362
3411
  async function recoverStuckAccountMutex(accountId, mutex, key) {
3363
3412
  if (accountMutexes.get(accountId) !== mutex) return;
3364
3413
  const lockState = mutex.state();
3365
- if (lockState.heldForMs < PLAYWRIGHT_MUTEX_WAIT_MS || lockState.heldBy.startsWith("init:") || isAccountServingStream(accountId)) {
3414
+ const minLockTimeout = Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers);
3415
+ if (lockState.heldForMs < minLockTimeout || lockState.heldBy.startsWith("init:") || lockState.heldBy.startsWith("headers:") || lockState.heldBy.startsWith("refresh:") || lockState.heldBy.startsWith("profile-reset:") || isAccountServingStream(accountId)) {
3366
3416
  logger.warn(
3367
- `[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${PLAYWRIGHT_MUTEX_WAIT_MS}ms | servingStream=${isAccountServingStream(accountId)}`
3417
+ `[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${minLockTimeout}ms | servingStream=${isAccountServingStream(accountId)}`
3368
3418
  );
3369
3419
  return;
3370
3420
  }
@@ -4166,7 +4216,8 @@ async function getBasicHeaders(accountId) {
4166
4216
  }
4167
4217
  const release = await acquireAccountMutex(
4168
4218
  accountId,
4169
- `headers:${accountId.substring(0, 12)}`
4219
+ `headers:${accountId.substring(0, 12)}`,
4220
+ Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers + 3e4)
4170
4221
  );
4171
4222
  try {
4172
4223
  touchAccountActivity(accountId);
@@ -4259,8 +4310,10 @@ async function getBasicHeaders(accountId) {
4259
4310
  return { cookie: cookie2, userAgent, bxV: bxV2, bxUa: bxUa2, bxUmidtoken: bxUmidtoken2, ...clientHints };
4260
4311
  }
4261
4312
  }
4313
+ let refreshedInThisCall = false;
4262
4314
  if (headersAge > HEADER_CACHE_TTL && !cache.refreshInProgress) {
4263
4315
  try {
4316
+ refreshedInThisCall = true;
4264
4317
  await refreshHeadersInternal(accountId);
4265
4318
  } catch (error) {
4266
4319
  if (!hadUsableHeaders) throw error;
@@ -4329,7 +4382,7 @@ async function getBasicHeaders(accountId) {
4329
4382
  } catch {
4330
4383
  }
4331
4384
  }
4332
- if (!hasRequiredQwenHeaders(cache.headers)) {
4385
+ if (!hasRequiredQwenHeaders(cache.headers) && !refreshedInThisCall) {
4333
4386
  console.log(
4334
4387
  `\u{1F504} [Playwright] Missing required anti-bot headers for ${accountId}, triggering header interception...`
4335
4388
  );
@@ -4608,6 +4661,22 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4608
4661
  }
4609
4662
  }
4610
4663
  }
4664
+ const currentCache = getHeaderCache(account.id);
4665
+ const currentCookie = currentCache?.headers?.cookie || "";
4666
+ if (currentCookie && isTokenExpiringSoon(currentCookie, 5)) {
4667
+ try {
4668
+ const refreshRes = await refreshAccountToken(account.id);
4669
+ if (refreshRes.success) {
4670
+ console.log(
4671
+ `\u26A1 [Playwright] Silently refreshed expired token restored from DB for ${maskEmail(account.email)}`
4672
+ );
4673
+ }
4674
+ } catch (err) {
4675
+ console.warn(
4676
+ `\u26A0\uFE0F [Playwright] Failed to proactively refresh expired token from DB for ${maskEmail(account.email)}: ${err?.message || err}`
4677
+ );
4678
+ }
4679
+ }
4611
4680
  return;
4612
4681
  }
4613
4682
  const cookies = await acctContext.cookies();
@@ -4895,7 +4964,7 @@ async function startEmailOtpLogin(email, options = {}) {
4895
4964
  email: normalizedEmail,
4896
4965
  requestId,
4897
4966
  version: getQwenWebVersion() || "0.3.12",
4898
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0]
4967
+ timezone: formatAsciiTimezone()
4899
4968
  }
4900
4969
  ).catch((err) => ({ ok: false, error: err?.message || String(err) }));
4901
4970
  if (!evalRes.ok || evalRes.data?.success !== true) {
@@ -4990,7 +5059,7 @@ async function verifyEmailOtpLogin(sessionId, code) {
4990
5059
  code: cleanCode,
4991
5060
  requestId,
4992
5061
  version: getQwenWebVersion() || "0.3.12",
4993
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0]
5062
+ timezone: formatAsciiTimezone()
4994
5063
  }
4995
5064
  ).catch((err) => ({ ok: false, error: err?.message || String(err) }));
4996
5065
  if (!evalRes.ok || evalRes.data?.success !== true || !evalRes.data?.data) {
@@ -5271,7 +5340,7 @@ async function loginViaApi(page, email, password) {
5271
5340
  const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
5272
5341
  const requestId = crypto3.randomUUID();
5273
5342
  const evalRes = await page.evaluate(
5274
- async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2 }) => {
5343
+ async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2, tz }) => {
5275
5344
  try {
5276
5345
  const response = await fetch(signinUrl2, {
5277
5346
  method: "POST",
@@ -5283,7 +5352,7 @@ async function loginViaApi(page, email, password) {
5283
5352
  source: "web",
5284
5353
  version: "0.3.11",
5285
5354
  "x-request-origin": "https://chat.qwen.ai",
5286
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
5355
+ timezone: tz,
5287
5356
  "x-request-id": requestId2
5288
5357
  },
5289
5358
  body: JSON.stringify({ email: email2, password: password2 })
@@ -5294,7 +5363,7 @@ async function loginViaApi(page, email, password) {
5294
5363
  return { ok: false, error: e.message };
5295
5364
  }
5296
5365
  },
5297
- { email, password: hashedPassword, signinUrl, requestId }
5366
+ { email, password: hashedPassword, signinUrl, requestId, tz: formatAsciiTimezone() }
5298
5367
  ).catch(() => null);
5299
5368
  let data = evalRes?.data;
5300
5369
  let signinSuccess = Boolean(
@@ -5303,7 +5372,7 @@ async function loginViaApi(page, email, password) {
5303
5372
  if (!signinSuccess && !data?.data?.code) {
5304
5373
  const fallbackUrl = qwenUrl("/api/v2/auths/signin");
5305
5374
  const fallbackRes = await page.evaluate(
5306
- async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2 }) => {
5375
+ async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2, tz }) => {
5307
5376
  try {
5308
5377
  const response = await fetch(fallbackUrl2, {
5309
5378
  method: "POST",
@@ -5314,7 +5383,7 @@ async function loginViaApi(page, email, password) {
5314
5383
  "content-type": "application/json",
5315
5384
  source: "web",
5316
5385
  version: "0.3.11",
5317
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
5386
+ timezone: tz,
5318
5387
  "x-request-id": requestId2
5319
5388
  },
5320
5389
  body: JSON.stringify({ email: email2, password: password2 })
@@ -5325,7 +5394,7 @@ async function loginViaApi(page, email, password) {
5325
5394
  return { ok: false, error: e.message };
5326
5395
  }
5327
5396
  },
5328
- { email, password: hashedPassword, fallbackUrl, requestId }
5397
+ { email, password: hashedPassword, fallbackUrl, requestId, tz: formatAsciiTimezone() }
5329
5398
  ).catch(() => null);
5330
5399
  if (fallbackRes?.data) {
5331
5400
  data = fallbackRes.data;
@@ -5657,7 +5726,7 @@ async function refreshAccountToken(accountId) {
5657
5726
  if (refreshToken) {
5658
5727
  try {
5659
5728
  const refreshResult = await page.evaluate(
5660
- async ({ rTok }) => {
5729
+ async ({ rTok, tz }) => {
5661
5730
  try {
5662
5731
  const response = await fetch("https://auth.qwen.ai/api/v2/auths/refresh", {
5663
5732
  method: "GET",
@@ -5667,7 +5736,7 @@ async function refreshAccountToken(accountId) {
5667
5736
  source: "web",
5668
5737
  version: "0.3.11",
5669
5738
  "x-request-origin": "https://chat.qwen.ai",
5670
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
5739
+ timezone: tz,
5671
5740
  authorization: `Bearer ${rTok}`
5672
5741
  }
5673
5742
  });
@@ -5677,7 +5746,7 @@ async function refreshAccountToken(accountId) {
5677
5746
  return { ok: false, error: e.message };
5678
5747
  }
5679
5748
  },
5680
- { rTok: refreshToken }
5749
+ { rTok: refreshToken, tz: formatAsciiTimezone() }
5681
5750
  );
5682
5751
  const json = refreshResult?.data;
5683
5752
  if (json && json.success === true && json.data) {
@@ -5715,6 +5784,7 @@ async function refreshAccountToken(accountId) {
5715
5784
  const cache = getHeaderCache(accountId);
5716
5785
  cache.headers.cookie = cookieStr;
5717
5786
  cache.lastRefresh = Date.now();
5787
+ cookieCaches.set(accountId, { cookie: cookieStr, timestamp: Date.now() });
5718
5788
  markAccountHeadersReady(accountId);
5719
5789
  saveAuthSession2(accountId, {
5720
5790
  cookie: cookieStr,
@@ -5749,6 +5819,7 @@ async function refreshAccountToken(accountId) {
5749
5819
  const cache = getHeaderCache(accountId);
5750
5820
  cache.headers.cookie = cookieStr;
5751
5821
  cache.lastRefresh = Date.now();
5822
+ cookieCaches.set(accountId, { cookie: cookieStr, timestamp: Date.now() });
5752
5823
  markAccountHeadersReady(accountId);
5753
5824
  saveAuthSession2(accountId, {
5754
5825
  cookie: cookieStr,
@@ -6803,11 +6874,11 @@ async function keepAlivePlaywrightAccount(accountId) {
6803
6874
  const now = Date.now();
6804
6875
  const currentUrl = page.url();
6805
6876
  const lastRefreshAttempt = lastProactiveTokenRefresh.get(accountId) ?? 0;
6806
- const cookie = await getCookies(accountId);
6877
+ const cookie = await getCookies(accountId, true);
6807
6878
  if (cookie && isTokenExpiringSoon(cookie, 5) && now - lastRefreshAttempt > 6e4) {
6808
6879
  lastProactiveTokenRefresh.set(accountId, now);
6809
- console.log(
6810
- `\u{1F493} [SessionKeeper] Account ${accountId} access token expiring soon (<5m); silently refreshing...`
6880
+ logger.debug(
6881
+ `[SessionKeeper] Account ${accountId} access token expiring soon (<5m); silently refreshing...`
6811
6882
  );
6812
6883
  const res = await refreshAccountToken(accountId);
6813
6884
  if (res.success) {
@@ -6815,6 +6886,9 @@ async function keepAlivePlaywrightAccount(accountId) {
6815
6886
  touchAccountActivity(accountId);
6816
6887
  return true;
6817
6888
  }
6889
+ logger.warn(
6890
+ `[SessionKeeper] Failed to silently refresh expiring token for ${accountId}: ${res.error || "unknown error"}`
6891
+ );
6818
6892
  }
6819
6893
  const lastNavigation = lastKeepAliveNavigation.get(accountId) ?? 0;
6820
6894
  const shouldNavigate = !currentUrl.startsWith(qwenOrigin()) || now - lastNavigation > config.sessionKeeper.navigationIntervalMs;
@@ -7052,6 +7126,7 @@ var init_playwright = __esm({
7052
7126
  init_qwen_headers();
7053
7127
  init_paths();
7054
7128
  init_jwt();
7129
+ init_timezone();
7055
7130
  requireLocal = createRequire(import.meta.url);
7056
7131
  contextInitHooks = [];
7057
7132
  ACCOUNT_MUTEX_MAX_HOLD_MS = 18e4;
@@ -7065,7 +7140,7 @@ var init_playwright = __esm({
7065
7140
  headerCaches = /* @__PURE__ */ new Map();
7066
7141
  HEADER_CACHE_TTL = 24 * 60 * 60 * 1e3;
7067
7142
  HEADER_REFRESH_THRESHOLD = 0.8;
7068
- COOKIE_CACHE_TTL = 15 * 60 * 1e3;
7143
+ COOKIE_CACHE_TTL = 60 * 1e3;
7069
7144
  cookieCaches = /* @__PURE__ */ new Map();
7070
7145
  lastAccountActivity = /* @__PURE__ */ new Map();
7071
7146
  lastKeepAliveNavigation = /* @__PURE__ */ new Map();
@@ -8437,6 +8512,8 @@ __export(qwen_exports, {
8437
8512
  QwenUpstreamError: () => QwenUpstreamError,
8438
8513
  QwenUpstreamUnavailableError: () => QwenUpstreamUnavailableError,
8439
8514
  RetryableQwenStreamError: () => RetryableQwenStreamError,
8515
+ STREAM_SLOT_MAX_HOLD_MS: () => STREAM_SLOT_MAX_HOLD_MS,
8516
+ acquireAccountStreamLock: () => acquireAccountStreamLock,
8440
8517
  acquireNewQwenChatSession: () => acquireNewQwenChatSession,
8441
8518
  buildCapturedQwenHeaders: () => buildCapturedQwenHeaders,
8442
8519
  buildChatNewBody: () => buildChatNewBody,
@@ -8450,10 +8527,13 @@ __export(qwen_exports, {
8450
8527
  deleteAllQwenChats: () => deleteAllQwenChats,
8451
8528
  deleteSingleQwenChat: () => deleteSingleQwenChat,
8452
8529
  disableNativeTools: () => disableNativeTools,
8530
+ ensureStaleSlotSweeperStarted: () => ensureStaleSlotSweeperStarted,
8453
8531
  fetchQwenModels: () => fetchQwenModels,
8454
8532
  fetchRemoteQwenChats: () => fetchRemoteQwenChats,
8455
8533
  flushLogicalThreadState: () => flushLogicalThreadState,
8534
+ getAccountStreamMutex: () => getAccountStreamMutex,
8456
8535
  getAnyCachedQwenModels: () => getAnyCachedQwenModels,
8536
+ getBrowserFetchHeaders: () => getBrowserFetchHeaders,
8457
8537
  getInFlightPersonalizationCount: () => getInFlightPersonalizationCount,
8458
8538
  getLogicalThreadState: () => getLogicalThreadState,
8459
8539
  getQwenErrorCode: () => getQwenErrorCode,
@@ -8469,6 +8549,8 @@ __export(qwen_exports, {
8469
8549
  requestQwenSettingsDirectFetch: () => requestQwenSettingsDirectFetch,
8470
8550
  requestQwenTextInBrowser: () => requestQwenTextInBrowser,
8471
8551
  setToolCapNotice: () => setToolCapNotice,
8552
+ stopStaleSlotSweeperForTests: () => stopStaleSlotSweeperForTests,
8553
+ sweepStaleAccountStreamSlots: () => sweepStaleAccountStreamSlots,
8472
8554
  syncQwenRequestPersonalization: () => syncQwenRequestPersonalization,
8473
8555
  updateLogicalThreadParent: () => updateLogicalThreadParent,
8474
8556
  updateLogicalThreadState: () => updateLogicalThreadState,
@@ -8755,7 +8837,7 @@ function buildCapturedQwenHeaders(headers, options = {}) {
8755
8837
  function getAccountStreamMutex(accountId) {
8756
8838
  let slots2 = accountStreamMutexes.get(accountId);
8757
8839
  if (!slots2) {
8758
- slots2 = { active: 0, queue: [] };
8840
+ slots2 = { activeSlots: /* @__PURE__ */ new Map(), queue: [] };
8759
8841
  accountStreamMutexes.set(accountId, slots2);
8760
8842
  }
8761
8843
  return slots2;
@@ -8763,27 +8845,71 @@ function getAccountStreamMutex(accountId) {
8763
8845
  function streamSlotCapacity() {
8764
8846
  return Math.max(1, config.concurrency.maxStreamsPerAccount);
8765
8847
  }
8766
- function createStreamSlotRelease(slots2) {
8848
+ function createStreamSlotRelease(slots2, slotId) {
8767
8849
  let released = false;
8768
8850
  return () => {
8769
8851
  if (released) return;
8770
8852
  released = true;
8771
- slots2.active -= 1;
8853
+ slots2.activeSlots.delete(slotId);
8772
8854
  const next = slots2.queue.shift();
8773
8855
  if (next) {
8774
- slots2.active += 1;
8775
- next();
8856
+ const nextId = nextStreamSlotId++;
8857
+ slots2.activeSlots.set(nextId, Date.now());
8858
+ next(createStreamSlotRelease(slots2, nextId));
8776
8859
  }
8777
8860
  };
8778
8861
  }
8862
+ function sweepStaleAccountStreamSlots(maxHoldMs = STREAM_SLOT_MAX_HOLD_MS) {
8863
+ const now = Date.now();
8864
+ let sweptCount = 0;
8865
+ for (const [accountId, slots2] of accountStreamMutexes.entries()) {
8866
+ for (const [slotId, startedAt] of Array.from(slots2.activeSlots.entries())) {
8867
+ if (now - startedAt >= maxHoldMs) {
8868
+ logger.warn(
8869
+ `[Qwen] Force-releasing stale stream slot | account=${accountId} | slotId=${slotId} | heldFor=${now - startedAt}ms | maxHold=${maxHoldMs}ms`
8870
+ );
8871
+ slots2.activeSlots.delete(slotId);
8872
+ sweptCount++;
8873
+ const next = slots2.queue.shift();
8874
+ if (next) {
8875
+ const nextId = nextStreamSlotId++;
8876
+ slots2.activeSlots.set(nextId, now);
8877
+ next(createStreamSlotRelease(slots2, nextId));
8878
+ }
8879
+ }
8880
+ }
8881
+ }
8882
+ return sweptCount;
8883
+ }
8884
+ function ensureStaleSlotSweeperStarted() {
8885
+ if (staleSlotSweeperTimer) return;
8886
+ staleSlotSweeperTimer = setInterval(() => {
8887
+ try {
8888
+ sweepStaleAccountStreamSlots();
8889
+ } catch (err) {
8890
+ logger.error("[Qwen] Error in stale stream slot sweeper", {
8891
+ error: err instanceof Error ? err.message : String(err)
8892
+ });
8893
+ }
8894
+ }, 6e4);
8895
+ staleSlotSweeperTimer.unref();
8896
+ }
8897
+ function stopStaleSlotSweeperForTests() {
8898
+ if (staleSlotSweeperTimer) {
8899
+ clearInterval(staleSlotSweeperTimer);
8900
+ staleSlotSweeperTimer = null;
8901
+ }
8902
+ }
8779
8903
  async function acquireAccountStreamLock(accountId) {
8904
+ ensureStaleSlotSweeperStarted();
8780
8905
  const slots2 = getAccountStreamMutex(accountId);
8781
- if (slots2.active < streamSlotCapacity()) {
8782
- slots2.active += 1;
8783
- return Promise.resolve(createStreamSlotRelease(slots2));
8906
+ if (slots2.activeSlots.size < streamSlotCapacity()) {
8907
+ const slotId = nextStreamSlotId++;
8908
+ slots2.activeSlots.set(slotId, Date.now());
8909
+ return Promise.resolve(createStreamSlotRelease(slots2, slotId));
8784
8910
  }
8785
8911
  return new Promise((resolve) => {
8786
- slots2.queue.push(() => resolve(createStreamSlotRelease(slots2)));
8912
+ slots2.queue.push((releaseFn) => resolve(releaseFn));
8787
8913
  });
8788
8914
  }
8789
8915
  function buildQwenSettingsUpdatePayload(currentSettings, instruction) {
@@ -8857,7 +8983,7 @@ async function withQwenBrowserPage(accountId, fn, targetPath, operationTimeoutMs
8857
8983
  return fn(page);
8858
8984
  },
8859
8985
  operationTimeoutMs,
8860
- Math.min(config.timeouts.page, 3e4),
8986
+ Math.max(6e4, operationTimeoutMs),
8861
8987
  recoverOnTimeout
8862
8988
  );
8863
8989
  }
@@ -8875,11 +9001,13 @@ function getBrowserFetchHeaders(headers) {
8875
9001
  "x-request-id",
8876
9002
  "x-accel-buffering"
8877
9003
  ]);
8878
- return Object.fromEntries(
8879
- Object.entries(headers).filter(
8880
- ([name]) => browserAllowedHeaders.has(name.toLowerCase())
8881
- )
8882
- );
9004
+ const result = {};
9005
+ for (const [name, value] of Object.entries(headers)) {
9006
+ if (browserAllowedHeaders.has(name.toLowerCase())) {
9007
+ result[name] = sanitizeHeaderForFetch(String(value ?? ""));
9008
+ }
9009
+ }
9010
+ return result;
8883
9011
  }
8884
9012
  async function requestQwenTextInBrowser(accountId, method, path6, headers, body, options = {}) {
8885
9013
  const url = qwenUrl(path6);
@@ -8900,7 +9028,7 @@ async function requestQwenTextInBrowser(accountId, method, path6, headers, body,
8900
9028
  browserHeaders["Content-Type"] = "application/json";
8901
9029
  }
8902
9030
  const evaluateRequest = (page) => page.evaluate(
8903
- async ({ url: url2, method: method2, headers: headers2, body: body2, referrer, timeoutMs }) => {
9031
+ async ({ url: url2, method: method2, headers: headers2, body: body2, referrer, timeoutMs, tz }) => {
8904
9032
  const controller = new AbortController();
8905
9033
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
8906
9034
  try {
@@ -8924,7 +9052,7 @@ async function requestQwenTextInBrowser(accountId, method, path6, headers, body,
8924
9052
  source: "web",
8925
9053
  version: "0.3.11",
8926
9054
  "x-request-origin": "https://chat.qwen.ai",
8927
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
9055
+ timezone: tz,
8928
9056
  authorization: `Bearer ${rTok}`
8929
9057
  },
8930
9058
  signal: AbortSignal.timeout(5e3)
@@ -8969,7 +9097,8 @@ async function requestQwenTextInBrowser(accountId, method, path6, headers, body,
8969
9097
  headers: browserHeaders,
8970
9098
  body,
8971
9099
  referrer: options.referrer,
8972
- timeoutMs: options.timeoutMs ?? Math.min(config.timeouts.page, 2e4)
9100
+ timeoutMs: options.timeoutMs ?? Math.min(config.timeouts.page, 2e4),
9101
+ tz: formatAsciiTimezone()
8973
9102
  }
8974
9103
  );
8975
9104
  const recoverOnTimeout = !options.noMutexRecovery;
@@ -9647,7 +9776,7 @@ async function disableNativeTools(accountId) {
9647
9776
  async (page) => {
9648
9777
  const requestId = crypto5.randomUUID();
9649
9778
  const response = await page.evaluate(
9650
- async ({ payload: payload2, timeoutMs, requestId: requestId2 }) => {
9779
+ async ({ payload: payload2, timeoutMs, requestId: requestId2, tz }) => {
9651
9780
  const controller = new AbortController();
9652
9781
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
9653
9782
  try {
@@ -9659,7 +9788,7 @@ async function disableNativeTools(accountId) {
9659
9788
  accept: "application/json, text/plain, */*",
9660
9789
  "content-type": "application/json",
9661
9790
  "x-request-id": requestId2,
9662
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
9791
+ timezone: tz,
9663
9792
  source: "web"
9664
9793
  },
9665
9794
  body: JSON.stringify(payload2),
@@ -9671,7 +9800,7 @@ async function disableNativeTools(accountId) {
9671
9800
  clearTimeout(timeoutId);
9672
9801
  }
9673
9802
  },
9674
- { payload, timeoutMs: config.timeouts.http, requestId }
9803
+ { payload, timeoutMs: config.timeouts.http, requestId, tz: formatAsciiTimezone() }
9675
9804
  );
9676
9805
  return response;
9677
9806
  }
@@ -9888,7 +10017,7 @@ async function deleteSingleQwenChat(accountId, chatId) {
9888
10017
  const requestHeaders = {
9889
10018
  source: "web",
9890
10019
  version: "0.2.89",
9891
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
10020
+ timezone: formatAsciiTimezone(),
9892
10021
  "x-request-id": crypto5.randomUUID(),
9893
10022
  Referer: qwenUrl(`/c/${encodeURIComponent(chatId)}`)
9894
10023
  };
@@ -9932,7 +10061,7 @@ async function fetchRemoteQwenChats(accountId) {
9932
10061
  }
9933
10062
  const requestHeaders = {
9934
10063
  version: "0.2.89",
9935
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
10064
+ timezone: formatAsciiTimezone(),
9936
10065
  "x-request-id": crypto5.randomUUID(),
9937
10066
  Referer: qwenUrl("/settings/chats")
9938
10067
  };
@@ -9984,7 +10113,7 @@ async function fetchQwenModels(accountId) {
9984
10113
  async (page) => {
9985
10114
  const requestId = crypto5.randomUUID();
9986
10115
  const response2 = await page.evaluate(
9987
- async ({ timeoutMs, requestId: requestId2 }) => {
10116
+ async ({ timeoutMs, requestId: requestId2, tz }) => {
9988
10117
  const controller = new AbortController();
9989
10118
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
9990
10119
  try {
@@ -9993,7 +10122,7 @@ async function fetchQwenModels(accountId) {
9993
10122
  headers: {
9994
10123
  accept: "application/json, text/plain, */*",
9995
10124
  "x-request-id": requestId2,
9996
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
10125
+ timezone: tz,
9997
10126
  source: "web"
9998
10127
  },
9999
10128
  signal: controller.signal
@@ -10003,7 +10132,7 @@ async function fetchQwenModels(accountId) {
10003
10132
  clearTimeout(timeoutId);
10004
10133
  }
10005
10134
  },
10006
- { timeoutMs: config.timeouts.http, requestId }
10135
+ { timeoutMs: config.timeouts.http, requestId, tz: formatAsciiTimezone() }
10007
10136
  );
10008
10137
  return response2;
10009
10138
  }
@@ -10042,7 +10171,7 @@ async function fetchQwenModels(accountId) {
10042
10171
  bxUa,
10043
10172
  bxUmidtoken,
10044
10173
  extra: {
10045
- timezone: (/* @__PURE__ */ new Date()).toString()
10174
+ timezone: formatAsciiTimezone()
10046
10175
  }
10047
10176
  }),
10048
10177
  void 0,
@@ -10805,7 +10934,7 @@ async function createQwenStreamInternal(prompt, enableThinking, modelId, forcedP
10805
10934
  clearTimeout(timeoutId);
10806
10935
  }
10807
10936
  }
10808
- var sleep4, BROWSER_STREAM_BINDING, BROWSER_ABORTERS_KEY, BROWSER_STREAM_FLUSH_BYTES, BROWSER_STREAM_FLUSH_MS, METADATA_TIMEOUT_PER_PAYLOAD_MB_MS, POST_CAPTCHA_METADATA_GRACE_MS, browserStreamStates, browserStreamBindingPages, browserStreamBindingContexts, MODEL_CACHE_TTL_MS, modelsCache, nativeToolsDisabled, disablingNativeToolsInProgress, lastSyncedPersonalizationHashes, directSettingsFetchConsecutiveFailures, DIRECT_SETTINGS_FETCH_BLOCK_THRESHOLD, directSettingsFetchBlocked, DIRECT_SETTINGS_FETCH_TIMEOUT_MS, activePersonalizationByAccount, accountStreamMutexes, QWEN_SAFE_SETTINGS_PATCH, QWEN_SAFE_SETTINGS_HASH, inFlightPersonalizationSyncs, DISABLE_TOOLS_MAX_RETRIES, DISABLE_TOOLS_BACKOFF_MS, UPSTREAM_RESPONSE_PREVIEW_BYTES;
10937
+ var sleep4, BROWSER_STREAM_BINDING, BROWSER_ABORTERS_KEY, BROWSER_STREAM_FLUSH_BYTES, BROWSER_STREAM_FLUSH_MS, METADATA_TIMEOUT_PER_PAYLOAD_MB_MS, POST_CAPTCHA_METADATA_GRACE_MS, browserStreamStates, browserStreamBindingPages, browserStreamBindingContexts, MODEL_CACHE_TTL_MS, modelsCache, nativeToolsDisabled, disablingNativeToolsInProgress, lastSyncedPersonalizationHashes, directSettingsFetchConsecutiveFailures, DIRECT_SETTINGS_FETCH_BLOCK_THRESHOLD, directSettingsFetchBlocked, DIRECT_SETTINGS_FETCH_TIMEOUT_MS, activePersonalizationByAccount, STREAM_SLOT_MAX_HOLD_MS, accountStreamMutexes, nextStreamSlotId, staleSlotSweeperTimer, QWEN_SAFE_SETTINGS_PATCH, QWEN_SAFE_SETTINGS_HASH, inFlightPersonalizationSyncs, DISABLE_TOOLS_MAX_RETRIES, DISABLE_TOOLS_BACKOFF_MS, UPSTREAM_RESPONSE_PREVIEW_BYTES;
10809
10938
  var init_qwen = __esm({
10810
10939
  "src/services/qwen.ts"() {
10811
10940
  "use strict";
@@ -10823,6 +10952,7 @@ var init_qwen = __esm({
10823
10952
  init_captcha_coordinator();
10824
10953
  init_captcha_solver();
10825
10954
  init_account_concurrency();
10955
+ init_timezone();
10826
10956
  init_qwen_errors();
10827
10957
  init_qwen_thread_state();
10828
10958
  init_qwen_chat_pool();
@@ -10852,7 +10982,10 @@ var init_qwen = __esm({
10852
10982
  directSettingsFetchBlocked = /* @__PURE__ */ new Set();
10853
10983
  DIRECT_SETTINGS_FETCH_TIMEOUT_MS = 1e4;
10854
10984
  activePersonalizationByAccount = /* @__PURE__ */ new Map();
10985
+ STREAM_SLOT_MAX_HOLD_MS = 30 * 60 * 1e3;
10855
10986
  accountStreamMutexes = /* @__PURE__ */ new Map();
10987
+ nextStreamSlotId = 1;
10988
+ staleSlotSweeperTimer = null;
10856
10989
  QWEN_SAFE_SETTINGS_PATCH = {
10857
10990
  ui: {
10858
10991
  autoTags: false,