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/clean-cache.js +76 -12
- package/dist/delete-chats.js +182 -49
- package/dist/index.js +245 -64
- package/dist/login.js +90 -19
- package/dist/reset-cooldowns.js +79 -13
- 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/retry-policy.ts +3 -1
- package/src/routes/chat/streaming.ts +32 -10
- package/src/routes/responses/adapter.ts +4 -1
- package/src/services/context-meter.ts +1 -1
- package/src/services/playwright.ts +55 -21
- package/src/services/qwen-headers.ts +3 -2
- package/src/services/qwen.ts +88 -31
- 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);
|
|
@@ -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
|
-
|
|
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=${
|
|
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: (
|
|
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: (
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
6787
|
-
|
|
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 =
|
|
7116
|
+
COOKIE_CACHE_TTL = 60 * 1e3;
|
|
7046
7117
|
cookieCaches = /* @__PURE__ */ new Map();
|
|
7047
7118
|
lastAccountActivity = /* @__PURE__ */ new Map();
|
|
7048
7119
|
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);
|
|
@@ -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
|
-
|
|
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=${
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
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
|
+
"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
|
}
|
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) {
|
|
@@ -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
|
|