qwenproxy-cli 1.6.4 → 1.6.6
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/clean-cache.js +84 -11
- package/dist/delete-chats.js +183 -45
- package/dist/index.js +267 -77
- package/dist/login.js +92 -16
- package/dist/reset-cooldowns.js +87 -12
- package/package.json +1 -1
- package/src/core/mutex.ts +4 -0
- package/src/core/version.ts +1 -1
- package/src/routes/anthropic/translate.ts +15 -7
- package/src/routes/chat/helpers.ts +4 -2
- package/src/routes/chat/streaming.ts +66 -31
- package/src/routes/responses/adapter.ts +4 -1
- package/src/services/context-meter.ts +1 -1
- package/src/services/playwright.ts +57 -20
- package/src/services/qwen-headers.ts +3 -2
- package/src/services/qwen.ts +87 -30
- package/src/tools/tool-call-heuristics.ts +88 -0
- package/src/utils/timezone.ts +56 -0
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);
|
|
@@ -4376,6 +4420,13 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4376
4420
|
await existingInit;
|
|
4377
4421
|
return;
|
|
4378
4422
|
}
|
|
4423
|
+
let resolveInit;
|
|
4424
|
+
let rejectInit;
|
|
4425
|
+
const inFlightDeferred = new Promise((res, rej) => {
|
|
4426
|
+
resolveInit = res;
|
|
4427
|
+
rejectInit = rej;
|
|
4428
|
+
});
|
|
4429
|
+
inFlightAccountInits.set(account.id, inFlightDeferred);
|
|
4379
4430
|
const initPromise = (async () => {
|
|
4380
4431
|
const release = await acquireAccountMutex(
|
|
4381
4432
|
account.id,
|
|
@@ -4590,6 +4641,22 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4590
4641
|
}
|
|
4591
4642
|
}
|
|
4592
4643
|
}
|
|
4644
|
+
const currentCache = getHeaderCache(account.id);
|
|
4645
|
+
const currentCookie = currentCache?.headers?.cookie || "";
|
|
4646
|
+
if (currentCookie && isTokenExpiringSoon(currentCookie, 5)) {
|
|
4647
|
+
try {
|
|
4648
|
+
const refreshRes = await refreshAccountToken(account.id);
|
|
4649
|
+
if (refreshRes.success) {
|
|
4650
|
+
console.log(
|
|
4651
|
+
`\u26A1 [Playwright] Silently refreshed expired token restored from DB for ${maskEmail(account.email)}`
|
|
4652
|
+
);
|
|
4653
|
+
}
|
|
4654
|
+
} catch (err) {
|
|
4655
|
+
console.warn(
|
|
4656
|
+
`\u26A0\uFE0F [Playwright] Failed to proactively refresh expired token from DB for ${maskEmail(account.email)}: ${err?.message || err}`
|
|
4657
|
+
);
|
|
4658
|
+
}
|
|
4659
|
+
}
|
|
4593
4660
|
return;
|
|
4594
4661
|
}
|
|
4595
4662
|
const cookies = await acctContext.cookies();
|
|
@@ -4644,9 +4711,12 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4644
4711
|
release();
|
|
4645
4712
|
}
|
|
4646
4713
|
})();
|
|
4647
|
-
inFlightAccountInits.set(account.id, initPromise);
|
|
4648
4714
|
try {
|
|
4649
4715
|
await initPromise;
|
|
4716
|
+
resolveInit();
|
|
4717
|
+
} catch (error) {
|
|
4718
|
+
rejectInit(error);
|
|
4719
|
+
throw error;
|
|
4650
4720
|
} finally {
|
|
4651
4721
|
inFlightAccountInits.delete(account.id);
|
|
4652
4722
|
}
|
|
@@ -4877,7 +4947,7 @@ async function startEmailOtpLogin(email, options = {}) {
|
|
|
4877
4947
|
email: normalizedEmail,
|
|
4878
4948
|
requestId,
|
|
4879
4949
|
version: getQwenWebVersion() || "0.3.12",
|
|
4880
|
-
timezone: (
|
|
4950
|
+
timezone: formatAsciiTimezone()
|
|
4881
4951
|
}
|
|
4882
4952
|
).catch((err) => ({ ok: false, error: err?.message || String(err) }));
|
|
4883
4953
|
if (!evalRes.ok || evalRes.data?.success !== true) {
|
|
@@ -4972,7 +5042,7 @@ async function verifyEmailOtpLogin(sessionId, code) {
|
|
|
4972
5042
|
code: cleanCode,
|
|
4973
5043
|
requestId,
|
|
4974
5044
|
version: getQwenWebVersion() || "0.3.12",
|
|
4975
|
-
timezone: (
|
|
5045
|
+
timezone: formatAsciiTimezone()
|
|
4976
5046
|
}
|
|
4977
5047
|
).catch((err) => ({ ok: false, error: err?.message || String(err) }));
|
|
4978
5048
|
if (!evalRes.ok || evalRes.data?.success !== true || !evalRes.data?.data) {
|
|
@@ -5253,7 +5323,7 @@ async function loginViaApi(page, email, password) {
|
|
|
5253
5323
|
const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
|
|
5254
5324
|
const requestId = crypto3.randomUUID();
|
|
5255
5325
|
const evalRes = await page.evaluate(
|
|
5256
|
-
async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2 }) => {
|
|
5326
|
+
async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2, tz }) => {
|
|
5257
5327
|
try {
|
|
5258
5328
|
const response = await fetch(signinUrl2, {
|
|
5259
5329
|
method: "POST",
|
|
@@ -5265,7 +5335,7 @@ async function loginViaApi(page, email, password) {
|
|
|
5265
5335
|
source: "web",
|
|
5266
5336
|
version: "0.3.11",
|
|
5267
5337
|
"x-request-origin": "https://chat.qwen.ai",
|
|
5268
|
-
timezone:
|
|
5338
|
+
timezone: tz,
|
|
5269
5339
|
"x-request-id": requestId2
|
|
5270
5340
|
},
|
|
5271
5341
|
body: JSON.stringify({ email: email2, password: password2 })
|
|
@@ -5276,7 +5346,7 @@ async function loginViaApi(page, email, password) {
|
|
|
5276
5346
|
return { ok: false, error: e.message };
|
|
5277
5347
|
}
|
|
5278
5348
|
},
|
|
5279
|
-
{ email, password: hashedPassword, signinUrl, requestId }
|
|
5349
|
+
{ email, password: hashedPassword, signinUrl, requestId, tz: formatAsciiTimezone() }
|
|
5280
5350
|
).catch(() => null);
|
|
5281
5351
|
let data = evalRes?.data;
|
|
5282
5352
|
let signinSuccess = Boolean(
|
|
@@ -5285,7 +5355,7 @@ async function loginViaApi(page, email, password) {
|
|
|
5285
5355
|
if (!signinSuccess && !data?.data?.code) {
|
|
5286
5356
|
const fallbackUrl = qwenUrl("/api/v2/auths/signin");
|
|
5287
5357
|
const fallbackRes = await page.evaluate(
|
|
5288
|
-
async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2 }) => {
|
|
5358
|
+
async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2, tz }) => {
|
|
5289
5359
|
try {
|
|
5290
5360
|
const response = await fetch(fallbackUrl2, {
|
|
5291
5361
|
method: "POST",
|
|
@@ -5296,7 +5366,7 @@ async function loginViaApi(page, email, password) {
|
|
|
5296
5366
|
"content-type": "application/json",
|
|
5297
5367
|
source: "web",
|
|
5298
5368
|
version: "0.3.11",
|
|
5299
|
-
timezone:
|
|
5369
|
+
timezone: tz,
|
|
5300
5370
|
"x-request-id": requestId2
|
|
5301
5371
|
},
|
|
5302
5372
|
body: JSON.stringify({ email: email2, password: password2 })
|
|
@@ -5307,7 +5377,7 @@ async function loginViaApi(page, email, password) {
|
|
|
5307
5377
|
return { ok: false, error: e.message };
|
|
5308
5378
|
}
|
|
5309
5379
|
},
|
|
5310
|
-
{ email, password: hashedPassword, fallbackUrl, requestId }
|
|
5380
|
+
{ email, password: hashedPassword, fallbackUrl, requestId, tz: formatAsciiTimezone() }
|
|
5311
5381
|
).catch(() => null);
|
|
5312
5382
|
if (fallbackRes?.data) {
|
|
5313
5383
|
data = fallbackRes.data;
|
|
@@ -5639,7 +5709,7 @@ async function refreshAccountToken(accountId) {
|
|
|
5639
5709
|
if (refreshToken) {
|
|
5640
5710
|
try {
|
|
5641
5711
|
const refreshResult = await page.evaluate(
|
|
5642
|
-
async ({ rTok }) => {
|
|
5712
|
+
async ({ rTok, tz }) => {
|
|
5643
5713
|
try {
|
|
5644
5714
|
const response = await fetch("https://auth.qwen.ai/api/v2/auths/refresh", {
|
|
5645
5715
|
method: "GET",
|
|
@@ -5649,7 +5719,7 @@ async function refreshAccountToken(accountId) {
|
|
|
5649
5719
|
source: "web",
|
|
5650
5720
|
version: "0.3.11",
|
|
5651
5721
|
"x-request-origin": "https://chat.qwen.ai",
|
|
5652
|
-
timezone:
|
|
5722
|
+
timezone: tz,
|
|
5653
5723
|
authorization: `Bearer ${rTok}`
|
|
5654
5724
|
}
|
|
5655
5725
|
});
|
|
@@ -5659,7 +5729,7 @@ async function refreshAccountToken(accountId) {
|
|
|
5659
5729
|
return { ok: false, error: e.message };
|
|
5660
5730
|
}
|
|
5661
5731
|
},
|
|
5662
|
-
{ rTok: refreshToken }
|
|
5732
|
+
{ rTok: refreshToken, tz: formatAsciiTimezone() }
|
|
5663
5733
|
);
|
|
5664
5734
|
const json = refreshResult?.data;
|
|
5665
5735
|
if (json && json.success === true && json.data) {
|
|
@@ -5697,6 +5767,7 @@ async function refreshAccountToken(accountId) {
|
|
|
5697
5767
|
const cache = getHeaderCache(accountId);
|
|
5698
5768
|
cache.headers.cookie = cookieStr;
|
|
5699
5769
|
cache.lastRefresh = Date.now();
|
|
5770
|
+
cookieCaches.set(accountId, { cookie: cookieStr, timestamp: Date.now() });
|
|
5700
5771
|
markAccountHeadersReady(accountId);
|
|
5701
5772
|
saveAuthSession2(accountId, {
|
|
5702
5773
|
cookie: cookieStr,
|
|
@@ -5731,6 +5802,7 @@ async function refreshAccountToken(accountId) {
|
|
|
5731
5802
|
const cache = getHeaderCache(accountId);
|
|
5732
5803
|
cache.headers.cookie = cookieStr;
|
|
5733
5804
|
cache.lastRefresh = Date.now();
|
|
5805
|
+
cookieCaches.set(accountId, { cookie: cookieStr, timestamp: Date.now() });
|
|
5734
5806
|
markAccountHeadersReady(accountId);
|
|
5735
5807
|
saveAuthSession2(accountId, {
|
|
5736
5808
|
cookie: cookieStr,
|
|
@@ -6785,11 +6857,11 @@ async function keepAlivePlaywrightAccount(accountId) {
|
|
|
6785
6857
|
const now = Date.now();
|
|
6786
6858
|
const currentUrl = page.url();
|
|
6787
6859
|
const lastRefreshAttempt = lastProactiveTokenRefresh.get(accountId) ?? 0;
|
|
6788
|
-
const cookie = await getCookies(accountId);
|
|
6860
|
+
const cookie = await getCookies(accountId, true);
|
|
6789
6861
|
if (cookie && isTokenExpiringSoon(cookie, 5) && now - lastRefreshAttempt > 6e4) {
|
|
6790
6862
|
lastProactiveTokenRefresh.set(accountId, now);
|
|
6791
|
-
|
|
6792
|
-
|
|
6863
|
+
logger.debug(
|
|
6864
|
+
`[SessionKeeper] Account ${accountId} access token expiring soon (<5m); silently refreshing...`
|
|
6793
6865
|
);
|
|
6794
6866
|
const res = await refreshAccountToken(accountId);
|
|
6795
6867
|
if (res.success) {
|
|
@@ -6797,6 +6869,9 @@ async function keepAlivePlaywrightAccount(accountId) {
|
|
|
6797
6869
|
touchAccountActivity(accountId);
|
|
6798
6870
|
return true;
|
|
6799
6871
|
}
|
|
6872
|
+
logger.warn(
|
|
6873
|
+
`[SessionKeeper] Failed to silently refresh expiring token for ${accountId}: ${res.error || "unknown error"}`
|
|
6874
|
+
);
|
|
6800
6875
|
}
|
|
6801
6876
|
const lastNavigation = lastKeepAliveNavigation.get(accountId) ?? 0;
|
|
6802
6877
|
const shouldNavigate = !currentUrl.startsWith(qwenOrigin()) || now - lastNavigation > config.sessionKeeper.navigationIntervalMs;
|
|
@@ -7034,6 +7109,7 @@ var init_playwright = __esm({
|
|
|
7034
7109
|
init_qwen_headers();
|
|
7035
7110
|
init_paths();
|
|
7036
7111
|
init_jwt();
|
|
7112
|
+
init_timezone();
|
|
7037
7113
|
requireLocal = createRequire(import.meta.url);
|
|
7038
7114
|
contextInitHooks = [];
|
|
7039
7115
|
ACCOUNT_MUTEX_MAX_HOLD_MS = 18e4;
|
|
@@ -7047,7 +7123,7 @@ var init_playwright = __esm({
|
|
|
7047
7123
|
headerCaches = /* @__PURE__ */ new Map();
|
|
7048
7124
|
HEADER_CACHE_TTL = 24 * 60 * 60 * 1e3;
|
|
7049
7125
|
HEADER_REFRESH_THRESHOLD = 0.8;
|
|
7050
|
-
COOKIE_CACHE_TTL =
|
|
7126
|
+
COOKIE_CACHE_TTL = 60 * 1e3;
|
|
7051
7127
|
cookieCaches = /* @__PURE__ */ new Map();
|
|
7052
7128
|
lastAccountActivity = /* @__PURE__ */ new Map();
|
|
7053
7129
|
lastKeepAliveNavigation = /* @__PURE__ */ new Map();
|
package/dist/reset-cooldowns.js
CHANGED
|
@@ -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);
|
|
@@ -3807,6 +3851,13 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
3807
3851
|
await existingInit;
|
|
3808
3852
|
return;
|
|
3809
3853
|
}
|
|
3854
|
+
let resolveInit;
|
|
3855
|
+
let rejectInit;
|
|
3856
|
+
const inFlightDeferred = new Promise((res, rej) => {
|
|
3857
|
+
resolveInit = res;
|
|
3858
|
+
rejectInit = rej;
|
|
3859
|
+
});
|
|
3860
|
+
inFlightAccountInits.set(account.id, inFlightDeferred);
|
|
3810
3861
|
const initPromise = (async () => {
|
|
3811
3862
|
const release = await acquireAccountMutex(
|
|
3812
3863
|
account.id,
|
|
@@ -4021,6 +4072,22 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4021
4072
|
}
|
|
4022
4073
|
}
|
|
4023
4074
|
}
|
|
4075
|
+
const currentCache = getHeaderCache(account.id);
|
|
4076
|
+
const currentCookie = currentCache?.headers?.cookie || "";
|
|
4077
|
+
if (currentCookie && isTokenExpiringSoon(currentCookie, 5)) {
|
|
4078
|
+
try {
|
|
4079
|
+
const refreshRes = await refreshAccountToken(account.id);
|
|
4080
|
+
if (refreshRes.success) {
|
|
4081
|
+
console.log(
|
|
4082
|
+
`\u26A1 [Playwright] Silently refreshed expired token restored from DB for ${maskEmail(account.email)}`
|
|
4083
|
+
);
|
|
4084
|
+
}
|
|
4085
|
+
} catch (err) {
|
|
4086
|
+
console.warn(
|
|
4087
|
+
`\u26A0\uFE0F [Playwright] Failed to proactively refresh expired token from DB for ${maskEmail(account.email)}: ${err?.message || err}`
|
|
4088
|
+
);
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4024
4091
|
return;
|
|
4025
4092
|
}
|
|
4026
4093
|
const cookies = await acctContext.cookies();
|
|
@@ -4075,9 +4142,12 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4075
4142
|
release();
|
|
4076
4143
|
}
|
|
4077
4144
|
})();
|
|
4078
|
-
inFlightAccountInits.set(account.id, initPromise);
|
|
4079
4145
|
try {
|
|
4080
4146
|
await initPromise;
|
|
4147
|
+
resolveInit();
|
|
4148
|
+
} catch (error) {
|
|
4149
|
+
rejectInit(error);
|
|
4150
|
+
throw error;
|
|
4081
4151
|
} finally {
|
|
4082
4152
|
inFlightAccountInits.delete(account.id);
|
|
4083
4153
|
}
|
|
@@ -4278,7 +4348,7 @@ async function loginViaApi(page, email, password) {
|
|
|
4278
4348
|
const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
|
|
4279
4349
|
const requestId = crypto3.randomUUID();
|
|
4280
4350
|
const evalRes = await page.evaluate(
|
|
4281
|
-
async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2 }) => {
|
|
4351
|
+
async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2, tz }) => {
|
|
4282
4352
|
try {
|
|
4283
4353
|
const response = await fetch(signinUrl2, {
|
|
4284
4354
|
method: "POST",
|
|
@@ -4290,7 +4360,7 @@ async function loginViaApi(page, email, password) {
|
|
|
4290
4360
|
source: "web",
|
|
4291
4361
|
version: "0.3.11",
|
|
4292
4362
|
"x-request-origin": "https://chat.qwen.ai",
|
|
4293
|
-
timezone:
|
|
4363
|
+
timezone: tz,
|
|
4294
4364
|
"x-request-id": requestId2
|
|
4295
4365
|
},
|
|
4296
4366
|
body: JSON.stringify({ email: email2, password: password2 })
|
|
@@ -4301,7 +4371,7 @@ async function loginViaApi(page, email, password) {
|
|
|
4301
4371
|
return { ok: false, error: e.message };
|
|
4302
4372
|
}
|
|
4303
4373
|
},
|
|
4304
|
-
{ email, password: hashedPassword, signinUrl, requestId }
|
|
4374
|
+
{ email, password: hashedPassword, signinUrl, requestId, tz: formatAsciiTimezone() }
|
|
4305
4375
|
).catch(() => null);
|
|
4306
4376
|
let data = evalRes?.data;
|
|
4307
4377
|
let signinSuccess = Boolean(
|
|
@@ -4310,7 +4380,7 @@ async function loginViaApi(page, email, password) {
|
|
|
4310
4380
|
if (!signinSuccess && !data?.data?.code) {
|
|
4311
4381
|
const fallbackUrl = qwenUrl("/api/v2/auths/signin");
|
|
4312
4382
|
const fallbackRes = await page.evaluate(
|
|
4313
|
-
async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2 }) => {
|
|
4383
|
+
async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2, tz }) => {
|
|
4314
4384
|
try {
|
|
4315
4385
|
const response = await fetch(fallbackUrl2, {
|
|
4316
4386
|
method: "POST",
|
|
@@ -4321,7 +4391,7 @@ async function loginViaApi(page, email, password) {
|
|
|
4321
4391
|
"content-type": "application/json",
|
|
4322
4392
|
source: "web",
|
|
4323
4393
|
version: "0.3.11",
|
|
4324
|
-
timezone:
|
|
4394
|
+
timezone: tz,
|
|
4325
4395
|
"x-request-id": requestId2
|
|
4326
4396
|
},
|
|
4327
4397
|
body: JSON.stringify({ email: email2, password: password2 })
|
|
@@ -4332,7 +4402,7 @@ async function loginViaApi(page, email, password) {
|
|
|
4332
4402
|
return { ok: false, error: e.message };
|
|
4333
4403
|
}
|
|
4334
4404
|
},
|
|
4335
|
-
{ email, password: hashedPassword, fallbackUrl, requestId }
|
|
4405
|
+
{ email, password: hashedPassword, fallbackUrl, requestId, tz: formatAsciiTimezone() }
|
|
4336
4406
|
).catch(() => null);
|
|
4337
4407
|
if (fallbackRes?.data) {
|
|
4338
4408
|
data = fallbackRes.data;
|
|
@@ -4664,7 +4734,7 @@ async function refreshAccountToken(accountId) {
|
|
|
4664
4734
|
if (refreshToken) {
|
|
4665
4735
|
try {
|
|
4666
4736
|
const refreshResult = await page.evaluate(
|
|
4667
|
-
async ({ rTok }) => {
|
|
4737
|
+
async ({ rTok, tz }) => {
|
|
4668
4738
|
try {
|
|
4669
4739
|
const response = await fetch("https://auth.qwen.ai/api/v2/auths/refresh", {
|
|
4670
4740
|
method: "GET",
|
|
@@ -4674,7 +4744,7 @@ async function refreshAccountToken(accountId) {
|
|
|
4674
4744
|
source: "web",
|
|
4675
4745
|
version: "0.3.11",
|
|
4676
4746
|
"x-request-origin": "https://chat.qwen.ai",
|
|
4677
|
-
timezone:
|
|
4747
|
+
timezone: tz,
|
|
4678
4748
|
authorization: `Bearer ${rTok}`
|
|
4679
4749
|
}
|
|
4680
4750
|
});
|
|
@@ -4684,7 +4754,7 @@ async function refreshAccountToken(accountId) {
|
|
|
4684
4754
|
return { ok: false, error: e.message };
|
|
4685
4755
|
}
|
|
4686
4756
|
},
|
|
4687
|
-
{ rTok: refreshToken }
|
|
4757
|
+
{ rTok: refreshToken, tz: formatAsciiTimezone() }
|
|
4688
4758
|
);
|
|
4689
4759
|
const json = refreshResult?.data;
|
|
4690
4760
|
if (json && json.success === true && json.data) {
|
|
@@ -4722,6 +4792,7 @@ async function refreshAccountToken(accountId) {
|
|
|
4722
4792
|
const cache = getHeaderCache(accountId);
|
|
4723
4793
|
cache.headers.cookie = cookieStr;
|
|
4724
4794
|
cache.lastRefresh = Date.now();
|
|
4795
|
+
cookieCaches.set(accountId, { cookie: cookieStr, timestamp: Date.now() });
|
|
4725
4796
|
markAccountHeadersReady(accountId);
|
|
4726
4797
|
saveAuthSession2(accountId, {
|
|
4727
4798
|
cookie: cookieStr,
|
|
@@ -4756,6 +4827,7 @@ async function refreshAccountToken(accountId) {
|
|
|
4756
4827
|
const cache = getHeaderCache(accountId);
|
|
4757
4828
|
cache.headers.cookie = cookieStr;
|
|
4758
4829
|
cache.lastRefresh = Date.now();
|
|
4830
|
+
cookieCaches.set(accountId, { cookie: cookieStr, timestamp: Date.now() });
|
|
4759
4831
|
markAccountHeadersReady(accountId);
|
|
4760
4832
|
saveAuthSession2(accountId, {
|
|
4761
4833
|
cookie: cookieStr,
|
|
@@ -5636,6 +5708,7 @@ var init_playwright = __esm({
|
|
|
5636
5708
|
init_qwen_headers();
|
|
5637
5709
|
init_paths();
|
|
5638
5710
|
init_jwt();
|
|
5711
|
+
init_timezone();
|
|
5639
5712
|
requireLocal = createRequire(import.meta.url);
|
|
5640
5713
|
contextInitHooks = [];
|
|
5641
5714
|
ACCOUNT_MUTEX_MAX_HOLD_MS = 18e4;
|
|
@@ -5645,7 +5718,7 @@ var init_playwright = __esm({
|
|
|
5645
5718
|
inFlightAccountInits = /* @__PURE__ */ new Map();
|
|
5646
5719
|
headerCaches = /* @__PURE__ */ new Map();
|
|
5647
5720
|
HEADER_CACHE_TTL = 24 * 60 * 60 * 1e3;
|
|
5648
|
-
COOKIE_CACHE_TTL =
|
|
5721
|
+
COOKIE_CACHE_TTL = 60 * 1e3;
|
|
5649
5722
|
cookieCaches = /* @__PURE__ */ new Map();
|
|
5650
5723
|
lastAccountActivity = /* @__PURE__ */ new Map();
|
|
5651
5724
|
lastKeepAliveNavigation = /* @__PURE__ */ new Map();
|
|
@@ -5927,7 +6000,7 @@ function clearPersonalizationDbCache(accountId) {
|
|
|
5927
6000
|
return 0;
|
|
5928
6001
|
}
|
|
5929
6002
|
}
|
|
5930
|
-
var BROWSER_STREAM_BINDING, browserStreamStates, browserStreamBindingContexts, MODEL_CACHE_TTL_MS, lastSyncedPersonalizationHashes, activePersonalizationByAccount, QWEN_SAFE_SETTINGS_PATCH, QWEN_SAFE_SETTINGS_HASH, UPSTREAM_RESPONSE_PREVIEW_BYTES;
|
|
6003
|
+
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;
|
|
5931
6004
|
var init_qwen = __esm({
|
|
5932
6005
|
"src/services/qwen.ts"() {
|
|
5933
6006
|
"use strict";
|
|
@@ -5945,6 +6018,7 @@ var init_qwen = __esm({
|
|
|
5945
6018
|
init_captcha_coordinator();
|
|
5946
6019
|
init_captcha_solver();
|
|
5947
6020
|
init_account_concurrency();
|
|
6021
|
+
init_timezone();
|
|
5948
6022
|
init_qwen_errors();
|
|
5949
6023
|
init_qwen_thread_state();
|
|
5950
6024
|
init_qwen_chat_pool();
|
|
@@ -5960,6 +6034,7 @@ var init_qwen = __esm({
|
|
|
5960
6034
|
MODEL_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
5961
6035
|
lastSyncedPersonalizationHashes = /* @__PURE__ */ new Map();
|
|
5962
6036
|
activePersonalizationByAccount = /* @__PURE__ */ new Map();
|
|
6037
|
+
STREAM_SLOT_MAX_HOLD_MS = 30 * 60 * 1e3;
|
|
5963
6038
|
QWEN_SAFE_SETTINGS_PATCH = {
|
|
5964
6039
|
ui: {
|
|
5965
6040
|
autoTags: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.6",
|
|
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
|
}
|
package/src/core/version.ts
CHANGED
|
@@ -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 =
|
|
320
|
-
|
|
321
|
-
|
|
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 =
|
|
502
|
-
|
|
503
|
-
: choice.finish_reason === "length"
|
|
506
|
+
const stopReason =
|
|
507
|
+
choice.finish_reason === "length"
|
|
504
508
|
? "max_tokens"
|
|
505
|
-
: "
|
|
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 =
|
|
235
|
-
|
|
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) {
|