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.
package/dist/login.js CHANGED
@@ -1892,6 +1892,7 @@ var init_mutex = __esm({
1892
1892
  locked = false;
1893
1893
  lockedAt = 0;
1894
1894
  lockedByKey = "";
1895
+ epoch = 0;
1895
1896
  async acquire(timeoutMs = 3e5, key = "") {
1896
1897
  const holdLimitMs = this.maxHoldMs;
1897
1898
  if (this.locked && Date.now() - this.lockedAt > holdLimitMs) {
@@ -1902,6 +1903,7 @@ var init_mutex = __esm({
1902
1903
  this.locked = false;
1903
1904
  this.lockedAt = 0;
1904
1905
  this.lockedByKey = "";
1906
+ this.epoch++;
1905
1907
  }
1906
1908
  if (!this.locked) {
1907
1909
  this.locked = true;
@@ -1940,10 +1942,12 @@ var init_mutex = __esm({
1940
1942
  }
1941
1943
  }
1942
1944
  createRelease() {
1945
+ const epoch = this.epoch;
1943
1946
  let released = false;
1944
1947
  return () => {
1945
1948
  if (released) return;
1946
1949
  released = true;
1950
+ if (epoch !== this.epoch) return;
1947
1951
  this.release();
1948
1952
  };
1949
1953
  }
@@ -2934,6 +2938,45 @@ var init_waf_isolation = __esm({
2934
2938
  }
2935
2939
  });
2936
2940
 
2941
+ // src/utils/timezone.ts
2942
+ function formatAsciiTimezone(date = /* @__PURE__ */ new Date()) {
2943
+ const dayName = DAYS[date.getDay()];
2944
+ const monthName = MONTHS[date.getMonth()];
2945
+ const day = String(date.getDate()).padStart(2, "0");
2946
+ const year = date.getFullYear();
2947
+ const hours = String(date.getHours()).padStart(2, "0");
2948
+ const minutes = String(date.getMinutes()).padStart(2, "0");
2949
+ const seconds = String(date.getSeconds()).padStart(2, "0");
2950
+ const offsetMinutes = -date.getTimezoneOffset();
2951
+ const sign = offsetMinutes >= 0 ? "+" : "-";
2952
+ const absOffset = Math.abs(offsetMinutes);
2953
+ const offsetHours = String(Math.floor(absOffset / 60)).padStart(2, "0");
2954
+ const offsetMins = String(absOffset % 60).padStart(2, "0");
2955
+ const tzOffset = `GMT${sign}${offsetHours}${offsetMins}`;
2956
+ return `${dayName} ${monthName} ${day} ${year} ${hours}:${minutes}:${seconds} ${tzOffset}`;
2957
+ }
2958
+ var DAYS, MONTHS;
2959
+ var init_timezone = __esm({
2960
+ "src/utils/timezone.ts"() {
2961
+ "use strict";
2962
+ DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
2963
+ MONTHS = [
2964
+ "Jan",
2965
+ "Feb",
2966
+ "Mar",
2967
+ "Apr",
2968
+ "May",
2969
+ "Jun",
2970
+ "Jul",
2971
+ "Aug",
2972
+ "Sep",
2973
+ "Oct",
2974
+ "Nov",
2975
+ "Dec"
2976
+ ];
2977
+ }
2978
+ });
2979
+
2937
2980
  // src/services/qwen-headers.ts
2938
2981
  import { v4 as uuidv4 } from "uuid";
2939
2982
  function getDefaultQwenUserAgent(major = getChromeMajor()) {
@@ -2954,6 +2997,7 @@ var init_qwen_headers = __esm({
2954
2997
  init_qwen_url();
2955
2998
  init_config();
2956
2999
  init_fingerprint();
3000
+ init_timezone();
2957
3001
  dynamicWebVersion = null;
2958
3002
  QWEN_WEB_VERSION = config.qwen.webVersion;
2959
3003
  DEFAULT_QWEN_USER_AGENT = getDefaultQwenUserAgent(151);
@@ -3190,6 +3234,7 @@ __export(playwright_exports, {
3190
3234
  closeIdlePlaywrightAccounts: () => closeIdlePlaywrightAccounts,
3191
3235
  closePlaywrightForAccount: () => closePlaywrightForAccount,
3192
3236
  evictIdlePlaywrightContextsToLimit: () => evictIdlePlaywrightContextsToLimit,
3237
+ getAccountMutex: () => getAccountMutex,
3193
3238
  getActivePlaywrightAccountIds: () => getActivePlaywrightAccountIds,
3194
3239
  getBasicHeaders: () => getBasicHeaders,
3195
3240
  getCookies: () => getCookies,
@@ -3339,9 +3384,10 @@ function getAccountMutex(accountId) {
3339
3384
  async function recoverStuckAccountMutex(accountId, mutex, key) {
3340
3385
  if (accountMutexes.get(accountId) !== mutex) return;
3341
3386
  const lockState = mutex.state();
3342
- if (lockState.heldForMs < PLAYWRIGHT_MUTEX_WAIT_MS || lockState.heldBy.startsWith("init:") || isAccountServingStream(accountId)) {
3387
+ const minLockTimeout = Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers);
3388
+ if (lockState.heldForMs < minLockTimeout || lockState.heldBy.startsWith("init:") || lockState.heldBy.startsWith("headers:") || lockState.heldBy.startsWith("refresh:") || lockState.heldBy.startsWith("profile-reset:") || isAccountServingStream(accountId)) {
3343
3389
  logger.warn(
3344
- `[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${PLAYWRIGHT_MUTEX_WAIT_MS}ms | servingStream=${isAccountServingStream(accountId)}`
3390
+ `[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${minLockTimeout}ms | servingStream=${isAccountServingStream(accountId)}`
3345
3391
  );
3346
3392
  return;
3347
3393
  }
@@ -4143,7 +4189,8 @@ async function getBasicHeaders(accountId) {
4143
4189
  }
4144
4190
  const release = await acquireAccountMutex(
4145
4191
  accountId,
4146
- `headers:${accountId.substring(0, 12)}`
4192
+ `headers:${accountId.substring(0, 12)}`,
4193
+ Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers + 3e4)
4147
4194
  );
4148
4195
  try {
4149
4196
  touchAccountActivity(accountId);
@@ -4236,8 +4283,10 @@ async function getBasicHeaders(accountId) {
4236
4283
  return { cookie: cookie2, userAgent, bxV: bxV2, bxUa: bxUa2, bxUmidtoken: bxUmidtoken2, ...clientHints };
4237
4284
  }
4238
4285
  }
4286
+ let refreshedInThisCall = false;
4239
4287
  if (headersAge > HEADER_CACHE_TTL && !cache.refreshInProgress) {
4240
4288
  try {
4289
+ refreshedInThisCall = true;
4241
4290
  await refreshHeadersInternal(accountId);
4242
4291
  } catch (error) {
4243
4292
  if (!hadUsableHeaders) throw error;
@@ -4306,7 +4355,7 @@ async function getBasicHeaders(accountId) {
4306
4355
  } catch {
4307
4356
  }
4308
4357
  }
4309
- if (!hasRequiredQwenHeaders(cache.headers)) {
4358
+ if (!hasRequiredQwenHeaders(cache.headers) && !refreshedInThisCall) {
4310
4359
  console.log(
4311
4360
  `\u{1F504} [Playwright] Missing required anti-bot headers for ${accountId}, triggering header interception...`
4312
4361
  );
@@ -4585,6 +4634,22 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4585
4634
  }
4586
4635
  }
4587
4636
  }
4637
+ const currentCache = getHeaderCache(account.id);
4638
+ const currentCookie = currentCache?.headers?.cookie || "";
4639
+ if (currentCookie && isTokenExpiringSoon(currentCookie, 5)) {
4640
+ try {
4641
+ const refreshRes = await refreshAccountToken(account.id);
4642
+ if (refreshRes.success) {
4643
+ console.log(
4644
+ `\u26A1 [Playwright] Silently refreshed expired token restored from DB for ${maskEmail(account.email)}`
4645
+ );
4646
+ }
4647
+ } catch (err) {
4648
+ console.warn(
4649
+ `\u26A0\uFE0F [Playwright] Failed to proactively refresh expired token from DB for ${maskEmail(account.email)}: ${err?.message || err}`
4650
+ );
4651
+ }
4652
+ }
4588
4653
  return;
4589
4654
  }
4590
4655
  const cookies = await acctContext.cookies();
@@ -4872,7 +4937,7 @@ async function startEmailOtpLogin(email, options = {}) {
4872
4937
  email: normalizedEmail,
4873
4938
  requestId,
4874
4939
  version: getQwenWebVersion() || "0.3.12",
4875
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0]
4940
+ timezone: formatAsciiTimezone()
4876
4941
  }
4877
4942
  ).catch((err) => ({ ok: false, error: err?.message || String(err) }));
4878
4943
  if (!evalRes.ok || evalRes.data?.success !== true) {
@@ -4967,7 +5032,7 @@ async function verifyEmailOtpLogin(sessionId, code) {
4967
5032
  code: cleanCode,
4968
5033
  requestId,
4969
5034
  version: getQwenWebVersion() || "0.3.12",
4970
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0]
5035
+ timezone: formatAsciiTimezone()
4971
5036
  }
4972
5037
  ).catch((err) => ({ ok: false, error: err?.message || String(err) }));
4973
5038
  if (!evalRes.ok || evalRes.data?.success !== true || !evalRes.data?.data) {
@@ -5248,7 +5313,7 @@ async function loginViaApi(page, email, password) {
5248
5313
  const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
5249
5314
  const requestId = crypto3.randomUUID();
5250
5315
  const evalRes = await page.evaluate(
5251
- async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2 }) => {
5316
+ async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2, tz }) => {
5252
5317
  try {
5253
5318
  const response = await fetch(signinUrl2, {
5254
5319
  method: "POST",
@@ -5260,7 +5325,7 @@ async function loginViaApi(page, email, password) {
5260
5325
  source: "web",
5261
5326
  version: "0.3.11",
5262
5327
  "x-request-origin": "https://chat.qwen.ai",
5263
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
5328
+ timezone: tz,
5264
5329
  "x-request-id": requestId2
5265
5330
  },
5266
5331
  body: JSON.stringify({ email: email2, password: password2 })
@@ -5271,7 +5336,7 @@ async function loginViaApi(page, email, password) {
5271
5336
  return { ok: false, error: e.message };
5272
5337
  }
5273
5338
  },
5274
- { email, password: hashedPassword, signinUrl, requestId }
5339
+ { email, password: hashedPassword, signinUrl, requestId, tz: formatAsciiTimezone() }
5275
5340
  ).catch(() => null);
5276
5341
  let data = evalRes?.data;
5277
5342
  let signinSuccess = Boolean(
@@ -5280,7 +5345,7 @@ async function loginViaApi(page, email, password) {
5280
5345
  if (!signinSuccess && !data?.data?.code) {
5281
5346
  const fallbackUrl = qwenUrl("/api/v2/auths/signin");
5282
5347
  const fallbackRes = await page.evaluate(
5283
- async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2 }) => {
5348
+ async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2, tz }) => {
5284
5349
  try {
5285
5350
  const response = await fetch(fallbackUrl2, {
5286
5351
  method: "POST",
@@ -5291,7 +5356,7 @@ async function loginViaApi(page, email, password) {
5291
5356
  "content-type": "application/json",
5292
5357
  source: "web",
5293
5358
  version: "0.3.11",
5294
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
5359
+ timezone: tz,
5295
5360
  "x-request-id": requestId2
5296
5361
  },
5297
5362
  body: JSON.stringify({ email: email2, password: password2 })
@@ -5302,7 +5367,7 @@ async function loginViaApi(page, email, password) {
5302
5367
  return { ok: false, error: e.message };
5303
5368
  }
5304
5369
  },
5305
- { email, password: hashedPassword, fallbackUrl, requestId }
5370
+ { email, password: hashedPassword, fallbackUrl, requestId, tz: formatAsciiTimezone() }
5306
5371
  ).catch(() => null);
5307
5372
  if (fallbackRes?.data) {
5308
5373
  data = fallbackRes.data;
@@ -5634,7 +5699,7 @@ async function refreshAccountToken(accountId) {
5634
5699
  if (refreshToken) {
5635
5700
  try {
5636
5701
  const refreshResult = await page.evaluate(
5637
- async ({ rTok }) => {
5702
+ async ({ rTok, tz }) => {
5638
5703
  try {
5639
5704
  const response = await fetch("https://auth.qwen.ai/api/v2/auths/refresh", {
5640
5705
  method: "GET",
@@ -5644,7 +5709,7 @@ async function refreshAccountToken(accountId) {
5644
5709
  source: "web",
5645
5710
  version: "0.3.11",
5646
5711
  "x-request-origin": "https://chat.qwen.ai",
5647
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
5712
+ timezone: tz,
5648
5713
  authorization: `Bearer ${rTok}`
5649
5714
  }
5650
5715
  });
@@ -5654,7 +5719,7 @@ async function refreshAccountToken(accountId) {
5654
5719
  return { ok: false, error: e.message };
5655
5720
  }
5656
5721
  },
5657
- { rTok: refreshToken }
5722
+ { rTok: refreshToken, tz: formatAsciiTimezone() }
5658
5723
  );
5659
5724
  const json = refreshResult?.data;
5660
5725
  if (json && json.success === true && json.data) {
@@ -5692,6 +5757,7 @@ async function refreshAccountToken(accountId) {
5692
5757
  const cache = getHeaderCache(accountId);
5693
5758
  cache.headers.cookie = cookieStr;
5694
5759
  cache.lastRefresh = Date.now();
5760
+ cookieCaches.set(accountId, { cookie: cookieStr, timestamp: Date.now() });
5695
5761
  markAccountHeadersReady(accountId);
5696
5762
  saveAuthSession2(accountId, {
5697
5763
  cookie: cookieStr,
@@ -5726,6 +5792,7 @@ async function refreshAccountToken(accountId) {
5726
5792
  const cache = getHeaderCache(accountId);
5727
5793
  cache.headers.cookie = cookieStr;
5728
5794
  cache.lastRefresh = Date.now();
5795
+ cookieCaches.set(accountId, { cookie: cookieStr, timestamp: Date.now() });
5729
5796
  markAccountHeadersReady(accountId);
5730
5797
  saveAuthSession2(accountId, {
5731
5798
  cookie: cookieStr,
@@ -6780,11 +6847,11 @@ async function keepAlivePlaywrightAccount(accountId) {
6780
6847
  const now = Date.now();
6781
6848
  const currentUrl = page.url();
6782
6849
  const lastRefreshAttempt = lastProactiveTokenRefresh.get(accountId) ?? 0;
6783
- const cookie = await getCookies(accountId);
6850
+ const cookie = await getCookies(accountId, true);
6784
6851
  if (cookie && isTokenExpiringSoon(cookie, 5) && now - lastRefreshAttempt > 6e4) {
6785
6852
  lastProactiveTokenRefresh.set(accountId, now);
6786
- console.log(
6787
- `\u{1F493} [SessionKeeper] Account ${accountId} access token expiring soon (<5m); silently refreshing...`
6853
+ logger.debug(
6854
+ `[SessionKeeper] Account ${accountId} access token expiring soon (<5m); silently refreshing...`
6788
6855
  );
6789
6856
  const res = await refreshAccountToken(accountId);
6790
6857
  if (res.success) {
@@ -6792,6 +6859,9 @@ async function keepAlivePlaywrightAccount(accountId) {
6792
6859
  touchAccountActivity(accountId);
6793
6860
  return true;
6794
6861
  }
6862
+ logger.warn(
6863
+ `[SessionKeeper] Failed to silently refresh expiring token for ${accountId}: ${res.error || "unknown error"}`
6864
+ );
6795
6865
  }
6796
6866
  const lastNavigation = lastKeepAliveNavigation.get(accountId) ?? 0;
6797
6867
  const shouldNavigate = !currentUrl.startsWith(qwenOrigin()) || now - lastNavigation > config.sessionKeeper.navigationIntervalMs;
@@ -7029,6 +7099,7 @@ var init_playwright = __esm({
7029
7099
  init_qwen_headers();
7030
7100
  init_paths();
7031
7101
  init_jwt();
7102
+ init_timezone();
7032
7103
  requireLocal = createRequire(import.meta.url);
7033
7104
  contextInitHooks = [];
7034
7105
  ACCOUNT_MUTEX_MAX_HOLD_MS = 18e4;
@@ -7042,7 +7113,7 @@ var init_playwright = __esm({
7042
7113
  headerCaches = /* @__PURE__ */ new Map();
7043
7114
  HEADER_CACHE_TTL = 24 * 60 * 60 * 1e3;
7044
7115
  HEADER_REFRESH_THRESHOLD = 0.8;
7045
- COOKIE_CACHE_TTL = 15 * 60 * 1e3;
7116
+ COOKIE_CACHE_TTL = 60 * 1e3;
7046
7117
  cookieCaches = /* @__PURE__ */ new Map();
7047
7118
  lastAccountActivity = /* @__PURE__ */ new Map();
7048
7119
  lastKeepAliveNavigation = /* @__PURE__ */ new Map();
@@ -2001,6 +2001,7 @@ var init_mutex = __esm({
2001
2001
  locked = false;
2002
2002
  lockedAt = 0;
2003
2003
  lockedByKey = "";
2004
+ epoch = 0;
2004
2005
  async acquire(timeoutMs = 3e5, key = "") {
2005
2006
  const holdLimitMs = this.maxHoldMs;
2006
2007
  if (this.locked && Date.now() - this.lockedAt > holdLimitMs) {
@@ -2011,6 +2012,7 @@ var init_mutex = __esm({
2011
2012
  this.locked = false;
2012
2013
  this.lockedAt = 0;
2013
2014
  this.lockedByKey = "";
2015
+ this.epoch++;
2014
2016
  }
2015
2017
  if (!this.locked) {
2016
2018
  this.locked = true;
@@ -2049,10 +2051,12 @@ var init_mutex = __esm({
2049
2051
  }
2050
2052
  }
2051
2053
  createRelease() {
2054
+ const epoch = this.epoch;
2052
2055
  let released = false;
2053
2056
  return () => {
2054
2057
  if (released) return;
2055
2058
  released = true;
2059
+ if (epoch !== this.epoch) return;
2056
2060
  this.release();
2057
2061
  };
2058
2062
  }
@@ -2715,6 +2719,45 @@ var init_waf_isolation = __esm({
2715
2719
  }
2716
2720
  });
2717
2721
 
2722
+ // src/utils/timezone.ts
2723
+ function formatAsciiTimezone(date = /* @__PURE__ */ new Date()) {
2724
+ const dayName = DAYS[date.getDay()];
2725
+ const monthName = MONTHS[date.getMonth()];
2726
+ const day = String(date.getDate()).padStart(2, "0");
2727
+ const year = date.getFullYear();
2728
+ const hours = String(date.getHours()).padStart(2, "0");
2729
+ const minutes = String(date.getMinutes()).padStart(2, "0");
2730
+ const seconds = String(date.getSeconds()).padStart(2, "0");
2731
+ const offsetMinutes = -date.getTimezoneOffset();
2732
+ const sign = offsetMinutes >= 0 ? "+" : "-";
2733
+ const absOffset = Math.abs(offsetMinutes);
2734
+ const offsetHours = String(Math.floor(absOffset / 60)).padStart(2, "0");
2735
+ const offsetMins = String(absOffset % 60).padStart(2, "0");
2736
+ const tzOffset = `GMT${sign}${offsetHours}${offsetMins}`;
2737
+ return `${dayName} ${monthName} ${day} ${year} ${hours}:${minutes}:${seconds} ${tzOffset}`;
2738
+ }
2739
+ var DAYS, MONTHS;
2740
+ var init_timezone = __esm({
2741
+ "src/utils/timezone.ts"() {
2742
+ "use strict";
2743
+ DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
2744
+ MONTHS = [
2745
+ "Jan",
2746
+ "Feb",
2747
+ "Mar",
2748
+ "Apr",
2749
+ "May",
2750
+ "Jun",
2751
+ "Jul",
2752
+ "Aug",
2753
+ "Sep",
2754
+ "Oct",
2755
+ "Nov",
2756
+ "Dec"
2757
+ ];
2758
+ }
2759
+ });
2760
+
2718
2761
  // src/services/qwen-headers.ts
2719
2762
  import { v4 as uuidv4 } from "uuid";
2720
2763
  function getDefaultQwenUserAgent(major = getChromeMajor()) {
@@ -2732,6 +2775,7 @@ var init_qwen_headers = __esm({
2732
2775
  init_qwen_url();
2733
2776
  init_config();
2734
2777
  init_fingerprint();
2778
+ init_timezone();
2735
2779
  dynamicWebVersion = null;
2736
2780
  QWEN_WEB_VERSION = config.qwen.webVersion;
2737
2781
  DEFAULT_QWEN_USER_AGENT = getDefaultQwenUserAgent(151);
@@ -3104,9 +3148,10 @@ function getAccountMutex(accountId) {
3104
3148
  async function recoverStuckAccountMutex(accountId, mutex, key) {
3105
3149
  if (accountMutexes.get(accountId) !== mutex) return;
3106
3150
  const lockState = mutex.state();
3107
- if (lockState.heldForMs < PLAYWRIGHT_MUTEX_WAIT_MS || lockState.heldBy.startsWith("init:") || isAccountServingStream(accountId)) {
3151
+ const minLockTimeout = Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers);
3152
+ if (lockState.heldForMs < minLockTimeout || lockState.heldBy.startsWith("init:") || lockState.heldBy.startsWith("headers:") || lockState.heldBy.startsWith("refresh:") || lockState.heldBy.startsWith("profile-reset:") || isAccountServingStream(accountId)) {
3108
3153
  logger.warn(
3109
- `[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${PLAYWRIGHT_MUTEX_WAIT_MS}ms | servingStream=${isAccountServingStream(accountId)}`
3154
+ `[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${minLockTimeout}ms | servingStream=${isAccountServingStream(accountId)}`
3110
3155
  );
3111
3156
  return;
3112
3157
  }
@@ -4020,6 +4065,22 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
4020
4065
  }
4021
4066
  }
4022
4067
  }
4068
+ const currentCache = getHeaderCache(account.id);
4069
+ const currentCookie = currentCache?.headers?.cookie || "";
4070
+ if (currentCookie && isTokenExpiringSoon(currentCookie, 5)) {
4071
+ try {
4072
+ const refreshRes = await refreshAccountToken(account.id);
4073
+ if (refreshRes.success) {
4074
+ console.log(
4075
+ `\u26A1 [Playwright] Silently refreshed expired token restored from DB for ${maskEmail(account.email)}`
4076
+ );
4077
+ }
4078
+ } catch (err) {
4079
+ console.warn(
4080
+ `\u26A0\uFE0F [Playwright] Failed to proactively refresh expired token from DB for ${maskEmail(account.email)}: ${err?.message || err}`
4081
+ );
4082
+ }
4083
+ }
4023
4084
  return;
4024
4085
  }
4025
4086
  const cookies = await acctContext.cookies();
@@ -4277,7 +4338,7 @@ async function loginViaApi(page, email, password) {
4277
4338
  const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
4278
4339
  const requestId = crypto3.randomUUID();
4279
4340
  const evalRes = await page.evaluate(
4280
- async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2 }) => {
4341
+ async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2, tz }) => {
4281
4342
  try {
4282
4343
  const response = await fetch(signinUrl2, {
4283
4344
  method: "POST",
@@ -4289,7 +4350,7 @@ async function loginViaApi(page, email, password) {
4289
4350
  source: "web",
4290
4351
  version: "0.3.11",
4291
4352
  "x-request-origin": "https://chat.qwen.ai",
4292
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
4353
+ timezone: tz,
4293
4354
  "x-request-id": requestId2
4294
4355
  },
4295
4356
  body: JSON.stringify({ email: email2, password: password2 })
@@ -4300,7 +4361,7 @@ async function loginViaApi(page, email, password) {
4300
4361
  return { ok: false, error: e.message };
4301
4362
  }
4302
4363
  },
4303
- { email, password: hashedPassword, signinUrl, requestId }
4364
+ { email, password: hashedPassword, signinUrl, requestId, tz: formatAsciiTimezone() }
4304
4365
  ).catch(() => null);
4305
4366
  let data = evalRes?.data;
4306
4367
  let signinSuccess = Boolean(
@@ -4309,7 +4370,7 @@ async function loginViaApi(page, email, password) {
4309
4370
  if (!signinSuccess && !data?.data?.code) {
4310
4371
  const fallbackUrl = qwenUrl("/api/v2/auths/signin");
4311
4372
  const fallbackRes = await page.evaluate(
4312
- async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2 }) => {
4373
+ async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2, tz }) => {
4313
4374
  try {
4314
4375
  const response = await fetch(fallbackUrl2, {
4315
4376
  method: "POST",
@@ -4320,7 +4381,7 @@ async function loginViaApi(page, email, password) {
4320
4381
  "content-type": "application/json",
4321
4382
  source: "web",
4322
4383
  version: "0.3.11",
4323
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
4384
+ timezone: tz,
4324
4385
  "x-request-id": requestId2
4325
4386
  },
4326
4387
  body: JSON.stringify({ email: email2, password: password2 })
@@ -4331,7 +4392,7 @@ async function loginViaApi(page, email, password) {
4331
4392
  return { ok: false, error: e.message };
4332
4393
  }
4333
4394
  },
4334
- { email, password: hashedPassword, fallbackUrl, requestId }
4395
+ { email, password: hashedPassword, fallbackUrl, requestId, tz: formatAsciiTimezone() }
4335
4396
  ).catch(() => null);
4336
4397
  if (fallbackRes?.data) {
4337
4398
  data = fallbackRes.data;
@@ -4663,7 +4724,7 @@ async function refreshAccountToken(accountId) {
4663
4724
  if (refreshToken) {
4664
4725
  try {
4665
4726
  const refreshResult = await page.evaluate(
4666
- async ({ rTok }) => {
4727
+ async ({ rTok, tz }) => {
4667
4728
  try {
4668
4729
  const response = await fetch("https://auth.qwen.ai/api/v2/auths/refresh", {
4669
4730
  method: "GET",
@@ -4673,7 +4734,7 @@ async function refreshAccountToken(accountId) {
4673
4734
  source: "web",
4674
4735
  version: "0.3.11",
4675
4736
  "x-request-origin": "https://chat.qwen.ai",
4676
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
4737
+ timezone: tz,
4677
4738
  authorization: `Bearer ${rTok}`
4678
4739
  }
4679
4740
  });
@@ -4683,7 +4744,7 @@ async function refreshAccountToken(accountId) {
4683
4744
  return { ok: false, error: e.message };
4684
4745
  }
4685
4746
  },
4686
- { rTok: refreshToken }
4747
+ { rTok: refreshToken, tz: formatAsciiTimezone() }
4687
4748
  );
4688
4749
  const json = refreshResult?.data;
4689
4750
  if (json && json.success === true && json.data) {
@@ -4721,6 +4782,7 @@ async function refreshAccountToken(accountId) {
4721
4782
  const cache = getHeaderCache(accountId);
4722
4783
  cache.headers.cookie = cookieStr;
4723
4784
  cache.lastRefresh = Date.now();
4785
+ cookieCaches.set(accountId, { cookie: cookieStr, timestamp: Date.now() });
4724
4786
  markAccountHeadersReady(accountId);
4725
4787
  saveAuthSession2(accountId, {
4726
4788
  cookie: cookieStr,
@@ -4755,6 +4817,7 @@ async function refreshAccountToken(accountId) {
4755
4817
  const cache = getHeaderCache(accountId);
4756
4818
  cache.headers.cookie = cookieStr;
4757
4819
  cache.lastRefresh = Date.now();
4820
+ cookieCaches.set(accountId, { cookie: cookieStr, timestamp: Date.now() });
4758
4821
  markAccountHeadersReady(accountId);
4759
4822
  saveAuthSession2(accountId, {
4760
4823
  cookie: cookieStr,
@@ -5635,6 +5698,7 @@ var init_playwright = __esm({
5635
5698
  init_qwen_headers();
5636
5699
  init_paths();
5637
5700
  init_jwt();
5701
+ init_timezone();
5638
5702
  requireLocal = createRequire(import.meta.url);
5639
5703
  contextInitHooks = [];
5640
5704
  ACCOUNT_MUTEX_MAX_HOLD_MS = 18e4;
@@ -5644,7 +5708,7 @@ var init_playwright = __esm({
5644
5708
  inFlightAccountInits = /* @__PURE__ */ new Map();
5645
5709
  headerCaches = /* @__PURE__ */ new Map();
5646
5710
  HEADER_CACHE_TTL = 24 * 60 * 60 * 1e3;
5647
- COOKIE_CACHE_TTL = 15 * 60 * 1e3;
5711
+ COOKIE_CACHE_TTL = 60 * 1e3;
5648
5712
  cookieCaches = /* @__PURE__ */ new Map();
5649
5713
  lastAccountActivity = /* @__PURE__ */ new Map();
5650
5714
  lastKeepAliveNavigation = /* @__PURE__ */ new Map();
@@ -5926,7 +5990,7 @@ function clearPersonalizationDbCache(accountId) {
5926
5990
  return 0;
5927
5991
  }
5928
5992
  }
5929
- var BROWSER_STREAM_BINDING, browserStreamStates, browserStreamBindingContexts, MODEL_CACHE_TTL_MS, lastSyncedPersonalizationHashes, activePersonalizationByAccount, QWEN_SAFE_SETTINGS_PATCH, QWEN_SAFE_SETTINGS_HASH, UPSTREAM_RESPONSE_PREVIEW_BYTES;
5993
+ var BROWSER_STREAM_BINDING, browserStreamStates, browserStreamBindingContexts, MODEL_CACHE_TTL_MS, lastSyncedPersonalizationHashes, activePersonalizationByAccount, STREAM_SLOT_MAX_HOLD_MS, QWEN_SAFE_SETTINGS_PATCH, QWEN_SAFE_SETTINGS_HASH, UPSTREAM_RESPONSE_PREVIEW_BYTES;
5930
5994
  var init_qwen = __esm({
5931
5995
  "src/services/qwen.ts"() {
5932
5996
  "use strict";
@@ -5944,6 +6008,7 @@ var init_qwen = __esm({
5944
6008
  init_captcha_coordinator();
5945
6009
  init_captcha_solver();
5946
6010
  init_account_concurrency();
6011
+ init_timezone();
5947
6012
  init_qwen_errors();
5948
6013
  init_qwen_thread_state();
5949
6014
  init_qwen_chat_pool();
@@ -5959,6 +6024,7 @@ var init_qwen = __esm({
5959
6024
  MODEL_CACHE_TTL_MS = 60 * 60 * 1e3;
5960
6025
  lastSyncedPersonalizationHashes = /* @__PURE__ */ new Map();
5961
6026
  activePersonalizationByAccount = /* @__PURE__ */ new Map();
6027
+ STREAM_SLOT_MAX_HOLD_MS = 30 * 60 * 1e3;
5962
6028
  QWEN_SAFE_SETTINGS_PATCH = {
5963
6029
  ui: {
5964
6030
  autoTags: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwenproxy-cli",
3
- "version": "1.6.3",
3
+ "version": "1.6.5",
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": {
package/src/core/mutex.ts CHANGED
@@ -14,6 +14,7 @@ export class Mutex {
14
14
  private locked = false;
15
15
  private lockedAt = 0;
16
16
  private lockedByKey = "";
17
+ private epoch = 0;
17
18
 
18
19
  constructor(
19
20
  public readonly name: string = "unnamed",
@@ -35,6 +36,7 @@ export class Mutex {
35
36
  this.locked = false;
36
37
  this.lockedAt = 0;
37
38
  this.lockedByKey = "";
39
+ this.epoch++;
38
40
  }
39
41
 
40
42
  if (!this.locked) {
@@ -78,10 +80,12 @@ export class Mutex {
78
80
  }
79
81
 
80
82
  private createRelease(): () => void {
83
+ const epoch = this.epoch;
81
84
  let released = false;
82
85
  return () => {
83
86
  if (released) return;
84
87
  released = true;
88
+ if (epoch !== this.epoch) return;
85
89
  this.release();
86
90
  };
87
91
  }
@@ -17,6 +17,6 @@ export function getAppVersion(): string {
17
17
  }
18
18
  }
19
19
  } catch {}
20
- cachedVersion = "v1.6.3";
20
+ cachedVersion = "v1.6.5";
21
21
  return cachedVersion;
22
22
  }
@@ -316,9 +316,14 @@ export function translateOpenAIToAnthropic(
316
316
  };
317
317
 
318
318
  const hasToolUse = content.some((b) => b.type === "tool_use");
319
- const stopReason = hasToolUse
320
- ? "tool_use"
321
- : stopReasonMap[choice.finish_reason || "stop"] || "end_turn";
319
+ const stopReason =
320
+ choice.finish_reason === "length"
321
+ ? "max_tokens"
322
+ : choice.finish_reason === "content_filter"
323
+ ? "end_turn"
324
+ : hasToolUse
325
+ ? "tool_use"
326
+ : stopReasonMap[choice.finish_reason || "stop"] || "end_turn";
322
327
 
323
328
  return {
324
329
  id: generateMessageId(),
@@ -498,11 +503,14 @@ export function translateStreamChunk(
498
503
  state.currentBlockType = null;
499
504
  }
500
505
 
501
- const stopReason = state.hasEmittedToolUse
502
- ? "tool_use"
503
- : choice.finish_reason === "length"
506
+ const stopReason =
507
+ choice.finish_reason === "length"
504
508
  ? "max_tokens"
505
- : "end_turn";
509
+ : choice.finish_reason === "content_filter"
510
+ ? "end_turn"
511
+ : state.hasEmittedToolUse
512
+ ? "tool_use"
513
+ : "end_turn";
506
514
 
507
515
  events.push(
508
516
  JSON.stringify({
@@ -231,8 +231,10 @@ export function applyUpstreamUsage(
231
231
  if (!candidate || typeof candidate !== "object") return;
232
232
 
233
233
  const usage = candidate as Record<string, unknown>;
234
- const promptTokens = asFiniteNumber(usage.input_tokens);
235
- const completionTokens = asFiniteNumber(usage.output_tokens);
234
+ const promptTokens =
235
+ asFiniteNumber(usage.input_tokens) ?? asFiniteNumber(usage.prompt_tokens);
236
+ const completionTokens =
237
+ asFiniteNumber(usage.output_tokens) ?? asFiniteNumber(usage.completion_tokens);
236
238
  const totalTokens = asFiniteNumber(usage.total_tokens);
237
239
 
238
240
  if (promptTokens !== null) {
@@ -390,7 +390,9 @@ export function isNetworkLikeError(err: unknown): boolean {
390
390
  message.includes("socket hang up") ||
391
391
  message.includes("econnreset") ||
392
392
  message.includes("econnrefused") ||
393
- message.includes("etimedout")
393
+ message.includes("etimedout") ||
394
+ message.includes("stream ended before response headers") ||
395
+ message.includes("timed out waiting for response headers")
394
396
  );
395
397
  }
396
398