qwenproxy-cli 1.6.10 → 1.6.11
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 +40 -12
- package/dist/delete-chats.js +110 -35
- package/dist/index.js +131 -45
- package/dist/login.js +44 -24
- package/dist/reset-cooldowns.js +40 -12
- package/package.json +2 -2
- package/src/api/server.ts +8 -4
- package/src/core/version.ts +1 -1
- package/src/core/watchdog.ts +4 -3
- package/src/routes/chat/account.ts +12 -0
- package/src/routes/chat/streaming.ts +18 -2
- package/src/services/captcha-coordinator.ts +36 -8
- package/src/services/chat-cleanup.ts +11 -2
- package/src/services/playwright.ts +73 -28
- package/src/services/qwen.ts +29 -3
package/dist/index.js
CHANGED
|
@@ -5447,14 +5447,15 @@ async function getBasicHeaders(accountId) {
|
|
|
5447
5447
|
`\u26A0\uFE0F [Playwright] Header refresh failed for ${accountId}; retaining the previous valid cache: ${getErrorMessage(error)}`
|
|
5448
5448
|
);
|
|
5449
5449
|
}
|
|
5450
|
-
} else if (hadUsableHeaders && headersAge > HEADER_CACHE_TTL * HEADER_REFRESH_THRESHOLD && !cache3.refreshInProgress
|
|
5451
|
-
|
|
5450
|
+
} else if (hadUsableHeaders && headersAge > HEADER_CACHE_TTL * HEADER_REFRESH_THRESHOLD && !cache3.refreshInProgress && // Header capture types into the chat input and sends a probe message on
|
|
5451
|
+
// the account's page. Doing that while the account is serving a stream
|
|
5452
|
+
// would mutate the live conversation (and can navigate the SPA), so the
|
|
5453
|
+
// proactive refresh waits until the account has no active lease.
|
|
5454
|
+
!hasActiveAccountLease(accountId)) {
|
|
5452
5455
|
refreshHeadersInternal(accountId).catch((error) => {
|
|
5453
5456
|
console.warn(
|
|
5454
5457
|
`\u26A0\uFE0F [Playwright] Background header refresh failed for ${accountId}: ${getErrorMessage(error)}`
|
|
5455
5458
|
);
|
|
5456
|
-
}).finally(() => {
|
|
5457
|
-
cache3.refreshInProgress = false;
|
|
5458
5459
|
});
|
|
5459
5460
|
}
|
|
5460
5461
|
if (!hasRequiredQwenHeaders(cache3.headers)) {
|
|
@@ -5592,8 +5593,12 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
5592
5593
|
if (accountPages.has(account.id)) {
|
|
5593
5594
|
return;
|
|
5594
5595
|
}
|
|
5595
|
-
|
|
5596
|
-
|
|
5596
|
+
if (config.playwright.maxActiveContexts > 0) {
|
|
5597
|
+
await evictIdlePlaywrightContextsToLimit(
|
|
5598
|
+
Math.max(1, config.playwright.maxActiveContexts - 1)
|
|
5599
|
+
).catch(() => {
|
|
5600
|
+
});
|
|
5601
|
+
}
|
|
5597
5602
|
const profilePath = getAccountProfilePath(account.id);
|
|
5598
5603
|
const fingerprint = getFingerprintProfile(account.id);
|
|
5599
5604
|
const { engine, channel } = resolveBrowserEngine(browserType);
|
|
@@ -5862,7 +5867,7 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
5862
5867
|
if (!acctPage.isClosed()) {
|
|
5863
5868
|
try {
|
|
5864
5869
|
const currentUrl = new URL(acctPage.url());
|
|
5865
|
-
if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname
|
|
5870
|
+
if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
|
|
5866
5871
|
await acctPage.goto(qwenUrl("/"), {
|
|
5867
5872
|
waitUntil: "domcontentloaded",
|
|
5868
5873
|
timeout: config.timeouts.navigation
|
|
@@ -5880,6 +5885,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
5880
5885
|
} finally {
|
|
5881
5886
|
release();
|
|
5882
5887
|
}
|
|
5888
|
+
await evictIdlePlaywrightContextsToLimit().catch(() => {
|
|
5889
|
+
});
|
|
5883
5890
|
})();
|
|
5884
5891
|
try {
|
|
5885
5892
|
await initPromise;
|
|
@@ -7648,19 +7655,26 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
|
|
|
7648
7655
|
await executeReauth();
|
|
7649
7656
|
} else {
|
|
7650
7657
|
try {
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7658
|
+
const currentUrl = typeof page.url === "function" ? page.url() : "";
|
|
7659
|
+
const needsNavigation = !currentUrl.startsWith(qwenOrigin()) || currentUrl.includes("/auth") || currentUrl.includes("/login") || currentUrl.includes("_____tmd_____");
|
|
7660
|
+
if (needsNavigation) {
|
|
7661
|
+
await page.goto(qwenUrl("/"), {
|
|
7662
|
+
waitUntil: "domcontentloaded",
|
|
7663
|
+
timeout: Math.min(
|
|
7664
|
+
config.timeouts.navigation,
|
|
7665
|
+
boundedTimeoutMs,
|
|
7666
|
+
SESSION_PROBE_NAVIGATION_TIMEOUT_MS
|
|
7667
|
+
)
|
|
7668
|
+
});
|
|
7669
|
+
await sleep2(2e3);
|
|
7670
|
+
}
|
|
7671
|
+
const url = typeof page.url === "function" ? page.url() : "";
|
|
7661
7672
|
const isAuthUrl = url.includes("auth") || url.includes("login");
|
|
7662
7673
|
if (isAuthUrl) {
|
|
7663
|
-
await
|
|
7674
|
+
const refreshed = await refreshAccountToken(accountId);
|
|
7675
|
+
if (!refreshed.success) {
|
|
7676
|
+
await executeReauth();
|
|
7677
|
+
}
|
|
7664
7678
|
}
|
|
7665
7679
|
} catch (navErr) {
|
|
7666
7680
|
console.warn(
|
|
@@ -7717,7 +7731,7 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
|
|
|
7717
7731
|
if (capturedPage && !capturedPage.isClosed()) {
|
|
7718
7732
|
try {
|
|
7719
7733
|
const currentUrl = new URL(capturedPage.url());
|
|
7720
|
-
if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname
|
|
7734
|
+
if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
|
|
7721
7735
|
await capturedPage.goto(qwenUrl("/"), {
|
|
7722
7736
|
waitUntil: "domcontentloaded",
|
|
7723
7737
|
timeout: Math.min(config.timeouts.navigation, boundedTimeoutMs)
|
|
@@ -8033,7 +8047,11 @@ function priorityOrderForEviction(accountIds) {
|
|
|
8033
8047
|
const ranked = getAccountsByPriority(accountIds.map((id) => ({ id })));
|
|
8034
8048
|
const rank = /* @__PURE__ */ new Map();
|
|
8035
8049
|
for (const [index, account] of ranked.entries()) {
|
|
8036
|
-
|
|
8050
|
+
if (getAccountCooldownInfo(account.id)) {
|
|
8051
|
+
rank.set(account.id, Number.MAX_SAFE_INTEGER);
|
|
8052
|
+
} else {
|
|
8053
|
+
rank.set(account.id, index);
|
|
8054
|
+
}
|
|
8037
8055
|
}
|
|
8038
8056
|
return orderContextsForEviction(
|
|
8039
8057
|
accountIds,
|
|
@@ -8070,14 +8088,15 @@ async function closeIdlePlaywrightAccounts(idleMs) {
|
|
|
8070
8088
|
}
|
|
8071
8089
|
return closed;
|
|
8072
8090
|
}
|
|
8073
|
-
async function evictIdlePlaywrightContextsToLimit() {
|
|
8074
|
-
const
|
|
8075
|
-
if (
|
|
8091
|
+
async function evictIdlePlaywrightContextsToLimit(targetLimit) {
|
|
8092
|
+
const configuredMax = config.playwright.maxActiveContexts;
|
|
8093
|
+
if (configuredMax <= 0) return 0;
|
|
8094
|
+
const max = targetLimit !== void 0 ? Math.max(0, targetLimit) : configuredMax;
|
|
8076
8095
|
if (accountPages.size <= max) return 0;
|
|
8077
8096
|
const candidates = priorityOrderForEviction(
|
|
8078
8097
|
Array.from(accountPages.keys()).filter((accountId) => {
|
|
8079
8098
|
if (isAccountServingStream(accountId)) return false;
|
|
8080
|
-
if (
|
|
8099
|
+
if (hasActiveAccountLease(accountId)) return false;
|
|
8081
8100
|
const mutex = accountMutexes.get(accountId);
|
|
8082
8101
|
return mutex?.isIdle();
|
|
8083
8102
|
})
|
|
@@ -8090,6 +8109,7 @@ async function evictIdlePlaywrightContextsToLimit() {
|
|
|
8090
8109
|
for (const candidate of candidates) {
|
|
8091
8110
|
if (accountPages.size <= max) break;
|
|
8092
8111
|
if (isAccountServingStream(candidate.accountId)) continue;
|
|
8112
|
+
if (hasActiveAccountLease(candidate.accountId)) continue;
|
|
8093
8113
|
const mutex = accountMutexes.get(candidate.accountId);
|
|
8094
8114
|
if (!mutex?.isIdle()) continue;
|
|
8095
8115
|
await closePlaywrightForAccount(candidate.accountId).catch((error) => {
|
|
@@ -8520,12 +8540,13 @@ var init_watchdog = __esm({
|
|
|
8520
8540
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
8521
8541
|
this.emit("recovery:ram:freed");
|
|
8522
8542
|
try {
|
|
8523
|
-
const { closeIdlePlaywrightAccounts: closeIdlePlaywrightAccounts2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
|
|
8543
|
+
const { closeIdlePlaywrightAccounts: closeIdlePlaywrightAccounts2, evictIdlePlaywrightContextsToLimit: evictIdlePlaywrightContextsToLimit2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
|
|
8544
|
+
const evicted = await evictIdlePlaywrightContextsToLimit2(1);
|
|
8524
8545
|
const closed = await closeIdlePlaywrightAccounts2(
|
|
8525
8546
|
config.sessionKeeper.idleMs
|
|
8526
8547
|
);
|
|
8527
|
-
if (closed > 0) {
|
|
8528
|
-
this.emit("recovery:ram:contexts-closed", closed);
|
|
8548
|
+
if (evicted + closed > 0) {
|
|
8549
|
+
this.emit("recovery:ram:contexts-closed", evicted + closed);
|
|
8529
8550
|
}
|
|
8530
8551
|
} catch {
|
|
8531
8552
|
}
|
|
@@ -9208,6 +9229,7 @@ var init_model_registry = __esm({
|
|
|
9208
9229
|
var captcha_coordinator_exports = {};
|
|
9209
9230
|
__export(captcha_coordinator_exports, {
|
|
9210
9231
|
isCaptchaRecoveryActive: () => isCaptchaRecoveryActive,
|
|
9232
|
+
isCaptchaRecoveryInBackoff: () => isCaptchaRecoveryInBackoff,
|
|
9211
9233
|
recoverBaxiaCaptcha: () => recoverBaxiaCaptcha,
|
|
9212
9234
|
solveChallengeOnPage: () => solveChallengeOnPage
|
|
9213
9235
|
});
|
|
@@ -9246,6 +9268,11 @@ async function solveChallengeOnPage(page, challengeUrl, waitForMs = config.captc
|
|
|
9246
9268
|
}
|
|
9247
9269
|
}
|
|
9248
9270
|
}
|
|
9271
|
+
function isCaptchaRecoveryInBackoff(accountId) {
|
|
9272
|
+
if (!accountId) return false;
|
|
9273
|
+
const lastFailure = lastFailedRecoveryAt.get(accountId) ?? 0;
|
|
9274
|
+
return Date.now() - lastFailure < FAILED_RECOVERY_BACKOFF_MS;
|
|
9275
|
+
}
|
|
9249
9276
|
async function recoverBaxiaCaptcha(accountId, label, options = {}) {
|
|
9250
9277
|
if (!config.captcha.enabled || !accountId) return false;
|
|
9251
9278
|
const solver = "baxia";
|
|
@@ -9263,17 +9290,34 @@ async function recoverBaxiaCaptcha(accountId, label, options = {}) {
|
|
|
9263
9290
|
const challengeUrl = options.challengeBody ? extractBaxiaChallengeUrl(options.challengeBody, config.qwen.baseUrl) : null;
|
|
9264
9291
|
activeCaptchaRecoveries.add(accountId);
|
|
9265
9292
|
try {
|
|
9266
|
-
const {
|
|
9267
|
-
|
|
9268
|
-
accountId,
|
|
9269
|
-
"Account stream interrupted by captcha challenge recovery"
|
|
9270
|
-
);
|
|
9271
|
-
} catch {
|
|
9272
|
-
}
|
|
9273
|
-
try {
|
|
9293
|
+
const { hasActiveBrowserStreamsForAccount: hasActiveBrowserStreamsForAccount2 } = await Promise.resolve().then(() => (init_qwen(), qwen_exports));
|
|
9294
|
+
const hasConcurrentStream = hasActiveBrowserStreamsForAccount2(accountId);
|
|
9274
9295
|
const solved = await withAccountPage(
|
|
9275
9296
|
accountId,
|
|
9276
|
-
(page) =>
|
|
9297
|
+
async (page) => {
|
|
9298
|
+
if (hasConcurrentStream) {
|
|
9299
|
+
const solverOptions = {
|
|
9300
|
+
maxAttempts: config.captcha.maxAttempts,
|
|
9301
|
+
retryDelayMs: config.captcha.retryDelayMs,
|
|
9302
|
+
settleMs: config.captcha.settleMs
|
|
9303
|
+
};
|
|
9304
|
+
if (await solveBaxiaCaptcha(page, { ...solverOptions, waitForMs: 0 })) {
|
|
9305
|
+
return true;
|
|
9306
|
+
}
|
|
9307
|
+
const ctx = typeof page.context === "function" ? page.context() : null;
|
|
9308
|
+
if (ctx && typeof ctx.newPage === "function") {
|
|
9309
|
+
const tempPage = await ctx.newPage();
|
|
9310
|
+
try {
|
|
9311
|
+
return await solveChallengeOnPage(tempPage, challengeUrl);
|
|
9312
|
+
} finally {
|
|
9313
|
+
await tempPage.close({ runBeforeUnload: false }).catch(() => {
|
|
9314
|
+
});
|
|
9315
|
+
}
|
|
9316
|
+
}
|
|
9317
|
+
return false;
|
|
9318
|
+
}
|
|
9319
|
+
return await solveChallengeOnPage(page, challengeUrl);
|
|
9320
|
+
},
|
|
9277
9321
|
solverOperationTimeoutMs,
|
|
9278
9322
|
solverOperationTimeoutMs,
|
|
9279
9323
|
false
|
|
@@ -11872,6 +11916,7 @@ __export(qwen_exports, {
|
|
|
11872
11916
|
getLogicalThreadState: () => getLogicalThreadState,
|
|
11873
11917
|
getQwenErrorCode: () => getQwenErrorCode,
|
|
11874
11918
|
getSessionParent: () => getSessionParent,
|
|
11919
|
+
hasActiveBrowserStreamsForAccount: () => hasActiveBrowserStreamsForAccount,
|
|
11875
11920
|
invalidateLogicalThreadParent: () => invalidateLogicalThreadParent,
|
|
11876
11921
|
isChatSessionActive: () => isChatSessionActive,
|
|
11877
11922
|
isRetryableFetchErrorMessage: () => isRetryableFetchErrorMessage,
|
|
@@ -11893,6 +11938,14 @@ __export(qwen_exports, {
|
|
|
11893
11938
|
});
|
|
11894
11939
|
import crypto7 from "crypto";
|
|
11895
11940
|
import { v4 as uuidv43 } from "uuid";
|
|
11941
|
+
function hasActiveBrowserStreamsForAccount(accountId) {
|
|
11942
|
+
for (const state of browserStreamStates.values()) {
|
|
11943
|
+
if (state.accountId === accountId && !state.done) {
|
|
11944
|
+
return true;
|
|
11945
|
+
}
|
|
11946
|
+
}
|
|
11947
|
+
return false;
|
|
11948
|
+
}
|
|
11896
11949
|
function abortActiveBrowserStreamsForAccount(accountId, reason = "Account browser stream interrupted (page context unloaded)") {
|
|
11897
11950
|
let count = 0;
|
|
11898
11951
|
for (const [requestId, state] of browserStreamStates) {
|
|
@@ -12670,7 +12723,7 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
|
|
|
12670
12723
|
const onFrameNavigated = (frame) => {
|
|
12671
12724
|
if (!settled && typeof page.mainFrame === "function" && frame === page.mainFrame()) {
|
|
12672
12725
|
const url2 = typeof frame.url === "function" ? frame.url() : String(frame.url || "");
|
|
12673
|
-
if (url2.startsWith(qwenOrigin()) && !url2.includes("/auth") && !url2.includes("_____tmd_____") && !url2.includes("/punish")) {
|
|
12726
|
+
if (url2.startsWith(qwenOrigin()) && !url2.includes("/auth") && !url2.includes("/login") && !url2.includes("_____tmd_____") && !url2.includes("/punish")) {
|
|
12674
12727
|
return;
|
|
12675
12728
|
}
|
|
12676
12729
|
handleBrowserStreamEvent(requestId, {
|
|
@@ -12691,10 +12744,12 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
|
|
|
12691
12744
|
};
|
|
12692
12745
|
page.on("framenavigated", onFrameNavigated);
|
|
12693
12746
|
page.on("close", onPageClosed);
|
|
12747
|
+
page.on("crash", onPageClosed);
|
|
12694
12748
|
pageCleanup = () => {
|
|
12695
12749
|
try {
|
|
12696
12750
|
page.off?.("framenavigated", onFrameNavigated);
|
|
12697
12751
|
page.off?.("close", onPageClosed);
|
|
12752
|
+
page.off?.("crash", onPageClosed);
|
|
12698
12753
|
} catch {
|
|
12699
12754
|
}
|
|
12700
12755
|
};
|
|
@@ -16843,7 +16898,16 @@ async function deleteChatsForAccountId(accountId) {
|
|
|
16843
16898
|
if (!credentials) {
|
|
16844
16899
|
throw new Error(`Credenciais da conta ${account.email} n\xE3o encontradas.`);
|
|
16845
16900
|
}
|
|
16846
|
-
|
|
16901
|
+
const wasInitialized = isPlaywrightInitialized(account.id);
|
|
16902
|
+
try {
|
|
16903
|
+
return await deleteChatsForAccount(credentials);
|
|
16904
|
+
} finally {
|
|
16905
|
+
if (!wasInitialized && !isAuthMockEnabled()) {
|
|
16906
|
+
const { closePlaywrightForAccount: closePlaywrightForAccount2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
|
|
16907
|
+
await closePlaywrightForAccount2(account.id).catch(() => {
|
|
16908
|
+
});
|
|
16909
|
+
}
|
|
16910
|
+
}
|
|
16847
16911
|
}
|
|
16848
16912
|
async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
|
|
16849
16913
|
const accounts = loadAccounts();
|
|
@@ -16858,6 +16922,7 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
|
|
|
16858
16922
|
const account = accounts[i];
|
|
16859
16923
|
const currentIdx = i + 1;
|
|
16860
16924
|
const totalCount = accounts.length;
|
|
16925
|
+
const wasInitialized = isPlaywrightInitialized(account.id);
|
|
16861
16926
|
try {
|
|
16862
16927
|
const ok = await deleteChatsForAccount(account, currentIdx, totalCount);
|
|
16863
16928
|
if (ok) succeeded++;
|
|
@@ -16867,7 +16932,7 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
|
|
|
16867
16932
|
error instanceof Error ? error.message : String(error)
|
|
16868
16933
|
);
|
|
16869
16934
|
} finally {
|
|
16870
|
-
if (!keepBrowserOpen) {
|
|
16935
|
+
if ((!keepBrowserOpen || !wasInitialized) && !isAuthMockEnabled()) {
|
|
16871
16936
|
const { closePlaywrightForAccount: closePlaywrightForAccount2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
|
|
16872
16937
|
await closePlaywrightForAccount2(account.id).catch(() => {
|
|
16873
16938
|
});
|
|
@@ -18154,6 +18219,9 @@ ${loopReminder}` : truncation.prompt;
|
|
|
18154
18219
|
if (policy.reason === "corrupted_chat_history") {
|
|
18155
18220
|
invalidateLogicalThreadParent(params.sessionId);
|
|
18156
18221
|
}
|
|
18222
|
+
if (policy.reason === "anti_bot" && isCaptchaRecoveryInBackoff(currentAccountId)) {
|
|
18223
|
+
return { success: false, error: err };
|
|
18224
|
+
}
|
|
18157
18225
|
const retryInvalidInputOnSameAccount = shouldRetryInvalidInputOnSameAccount(
|
|
18158
18226
|
policy.reason,
|
|
18159
18227
|
invalidInputSameAccountRetried
|
|
@@ -18342,6 +18410,7 @@ var init_account = __esm({
|
|
|
18342
18410
|
init_account_manager();
|
|
18343
18411
|
init_account_priority();
|
|
18344
18412
|
init_waf_isolation();
|
|
18413
|
+
init_captcha_coordinator();
|
|
18345
18414
|
init_accounts();
|
|
18346
18415
|
init_config();
|
|
18347
18416
|
init_errors();
|
|
@@ -21965,8 +22034,8 @@ async function processStreamingResponse(params) {
|
|
|
21965
22034
|
);
|
|
21966
22035
|
};
|
|
21967
22036
|
const emitAnswerText = async (textChunk) => {
|
|
21968
|
-
if (textChunk) emittedModelOutput = true;
|
|
21969
22037
|
if (!toolParser) {
|
|
22038
|
+
if (textChunk) emittedModelOutput = true;
|
|
21970
22039
|
finalContent += textChunk;
|
|
21971
22040
|
writeDeltaEvent({ content: textChunk });
|
|
21972
22041
|
return;
|
|
@@ -22342,6 +22411,23 @@ async function processStreamingResponse(params) {
|
|
|
22342
22411
|
if (fastMatch) {
|
|
22343
22412
|
const unescaped = fastMatch[1].replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
22344
22413
|
if (unescaped) {
|
|
22414
|
+
if (!emittedModelOutput && isOverloadMessage(unescaped)) {
|
|
22415
|
+
const overloadErr = toRetryableStreamError(
|
|
22416
|
+
"server_overloaded",
|
|
22417
|
+
unescaped,
|
|
22418
|
+
{
|
|
22419
|
+
switchAccount: true,
|
|
22420
|
+
forceNewChat: true,
|
|
22421
|
+
reason: "server_busy",
|
|
22422
|
+
accountCooldownMs: OVERLOAD_COOLDOWN_MS,
|
|
22423
|
+
accountCooldownReason: "ServerOverloaded"
|
|
22424
|
+
}
|
|
22425
|
+
);
|
|
22426
|
+
if (await recoverFromStreamError(overloadErr)) {
|
|
22427
|
+
break;
|
|
22428
|
+
}
|
|
22429
|
+
throw overloadErr;
|
|
22430
|
+
}
|
|
22345
22431
|
const result = getIncrementalDelta(
|
|
22346
22432
|
lastRawContent,
|
|
22347
22433
|
unescaped,
|
|
@@ -22476,7 +22562,6 @@ async function processStreamingResponse(params) {
|
|
|
22476
22562
|
if (foundStr && vStr !== "") {
|
|
22477
22563
|
if (vStr === "FINISHED") continue;
|
|
22478
22564
|
if (isThinkingChunk) {
|
|
22479
|
-
emittedModelOutput = true;
|
|
22480
22565
|
reasoningBuffer += vStr;
|
|
22481
22566
|
writeDeltaEvent({ reasoning_content: vStr });
|
|
22482
22567
|
} else {
|
|
@@ -27211,7 +27296,7 @@ function getAppVersion() {
|
|
|
27211
27296
|
}
|
|
27212
27297
|
} catch {
|
|
27213
27298
|
}
|
|
27214
|
-
cachedVersion = "v1.6.
|
|
27299
|
+
cachedVersion = "v1.6.11";
|
|
27215
27300
|
return cachedVersion;
|
|
27216
27301
|
}
|
|
27217
27302
|
var cachedVersion;
|
|
@@ -27617,10 +27702,11 @@ async function startServer(options) {
|
|
|
27617
27702
|
} else if (remainingAccounts.length > 0) {
|
|
27618
27703
|
void (async () => {
|
|
27619
27704
|
const { ensureAccountInPriority: ensureAccountInPriority2 } = await Promise.resolve().then(() => (init_account_priority(), account_priority_exports));
|
|
27620
|
-
const
|
|
27705
|
+
const maxWarmCap = config.playwright.maxActiveContexts > 0 ? Math.min(
|
|
27621
27706
|
config.playwright.startupWarmAccounts,
|
|
27622
|
-
|
|
27623
|
-
);
|
|
27707
|
+
config.playwright.maxActiveContexts
|
|
27708
|
+
) : config.playwright.startupWarmAccounts;
|
|
27709
|
+
const targetWarmTotal = Math.min(maxWarmCap, totalAccounts);
|
|
27624
27710
|
let currentReadyCount = readyAccountIds.size;
|
|
27625
27711
|
if (currentReadyCount < targetWarmTotal && remainingAccounts.length > 0) {
|
|
27626
27712
|
for (const reserveAccount of remainingAccounts) {
|
package/dist/login.js
CHANGED
|
@@ -4310,14 +4310,15 @@ async function getBasicHeaders(accountId) {
|
|
|
4310
4310
|
`\u26A0\uFE0F [Playwright] Header refresh failed for ${accountId}; retaining the previous valid cache: ${getErrorMessage(error)}`
|
|
4311
4311
|
);
|
|
4312
4312
|
}
|
|
4313
|
-
} else if (hadUsableHeaders && headersAge > HEADER_CACHE_TTL * HEADER_REFRESH_THRESHOLD && !cache.refreshInProgress
|
|
4314
|
-
|
|
4313
|
+
} else if (hadUsableHeaders && headersAge > HEADER_CACHE_TTL * HEADER_REFRESH_THRESHOLD && !cache.refreshInProgress && // Header capture types into the chat input and sends a probe message on
|
|
4314
|
+
// the account's page. Doing that while the account is serving a stream
|
|
4315
|
+
// would mutate the live conversation (and can navigate the SPA), so the
|
|
4316
|
+
// proactive refresh waits until the account has no active lease.
|
|
4317
|
+
!hasActiveAccountLease(accountId)) {
|
|
4315
4318
|
refreshHeadersInternal(accountId).catch((error) => {
|
|
4316
4319
|
console.warn(
|
|
4317
4320
|
`\u26A0\uFE0F [Playwright] Background header refresh failed for ${accountId}: ${getErrorMessage(error)}`
|
|
4318
4321
|
);
|
|
4319
|
-
}).finally(() => {
|
|
4320
|
-
cache.refreshInProgress = false;
|
|
4321
4322
|
});
|
|
4322
4323
|
}
|
|
4323
4324
|
if (!hasRequiredQwenHeaders(cache.headers)) {
|
|
@@ -4455,8 +4456,12 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4455
4456
|
if (accountPages.has(account.id)) {
|
|
4456
4457
|
return;
|
|
4457
4458
|
}
|
|
4458
|
-
|
|
4459
|
-
|
|
4459
|
+
if (config.playwright.maxActiveContexts > 0) {
|
|
4460
|
+
await evictIdlePlaywrightContextsToLimit(
|
|
4461
|
+
Math.max(1, config.playwright.maxActiveContexts - 1)
|
|
4462
|
+
).catch(() => {
|
|
4463
|
+
});
|
|
4464
|
+
}
|
|
4460
4465
|
const profilePath = getAccountProfilePath(account.id);
|
|
4461
4466
|
const fingerprint = getFingerprintProfile(account.id);
|
|
4462
4467
|
const { engine, channel } = resolveBrowserEngine(browserType);
|
|
@@ -4725,7 +4730,7 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4725
4730
|
if (!acctPage.isClosed()) {
|
|
4726
4731
|
try {
|
|
4727
4732
|
const currentUrl = new URL(acctPage.url());
|
|
4728
|
-
if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname
|
|
4733
|
+
if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
|
|
4729
4734
|
await acctPage.goto(qwenUrl("/"), {
|
|
4730
4735
|
waitUntil: "domcontentloaded",
|
|
4731
4736
|
timeout: config.timeouts.navigation
|
|
@@ -4743,6 +4748,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4743
4748
|
} finally {
|
|
4744
4749
|
release();
|
|
4745
4750
|
}
|
|
4751
|
+
await evictIdlePlaywrightContextsToLimit().catch(() => {
|
|
4752
|
+
});
|
|
4746
4753
|
})();
|
|
4747
4754
|
try {
|
|
4748
4755
|
await initPromise;
|
|
@@ -6511,19 +6518,26 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
|
|
|
6511
6518
|
await executeReauth();
|
|
6512
6519
|
} else {
|
|
6513
6520
|
try {
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6521
|
+
const currentUrl = typeof page.url === "function" ? page.url() : "";
|
|
6522
|
+
const needsNavigation = !currentUrl.startsWith(qwenOrigin()) || currentUrl.includes("/auth") || currentUrl.includes("/login") || currentUrl.includes("_____tmd_____");
|
|
6523
|
+
if (needsNavigation) {
|
|
6524
|
+
await page.goto(qwenUrl("/"), {
|
|
6525
|
+
waitUntil: "domcontentloaded",
|
|
6526
|
+
timeout: Math.min(
|
|
6527
|
+
config.timeouts.navigation,
|
|
6528
|
+
boundedTimeoutMs,
|
|
6529
|
+
SESSION_PROBE_NAVIGATION_TIMEOUT_MS
|
|
6530
|
+
)
|
|
6531
|
+
});
|
|
6532
|
+
await sleep2(2e3);
|
|
6533
|
+
}
|
|
6534
|
+
const url = typeof page.url === "function" ? page.url() : "";
|
|
6524
6535
|
const isAuthUrl = url.includes("auth") || url.includes("login");
|
|
6525
6536
|
if (isAuthUrl) {
|
|
6526
|
-
await
|
|
6537
|
+
const refreshed = await refreshAccountToken(accountId);
|
|
6538
|
+
if (!refreshed.success) {
|
|
6539
|
+
await executeReauth();
|
|
6540
|
+
}
|
|
6527
6541
|
}
|
|
6528
6542
|
} catch (navErr) {
|
|
6529
6543
|
console.warn(
|
|
@@ -6580,7 +6594,7 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
|
|
|
6580
6594
|
if (capturedPage && !capturedPage.isClosed()) {
|
|
6581
6595
|
try {
|
|
6582
6596
|
const currentUrl = new URL(capturedPage.url());
|
|
6583
|
-
if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname
|
|
6597
|
+
if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
|
|
6584
6598
|
await capturedPage.goto(qwenUrl("/"), {
|
|
6585
6599
|
waitUntil: "domcontentloaded",
|
|
6586
6600
|
timeout: Math.min(config.timeouts.navigation, boundedTimeoutMs)
|
|
@@ -6896,7 +6910,11 @@ function priorityOrderForEviction(accountIds) {
|
|
|
6896
6910
|
const ranked = getAccountsByPriority(accountIds.map((id) => ({ id })));
|
|
6897
6911
|
const rank = /* @__PURE__ */ new Map();
|
|
6898
6912
|
for (const [index, account] of ranked.entries()) {
|
|
6899
|
-
|
|
6913
|
+
if (getAccountCooldownInfo(account.id)) {
|
|
6914
|
+
rank.set(account.id, Number.MAX_SAFE_INTEGER);
|
|
6915
|
+
} else {
|
|
6916
|
+
rank.set(account.id, index);
|
|
6917
|
+
}
|
|
6900
6918
|
}
|
|
6901
6919
|
return orderContextsForEviction(
|
|
6902
6920
|
accountIds,
|
|
@@ -6933,14 +6951,15 @@ async function closeIdlePlaywrightAccounts(idleMs) {
|
|
|
6933
6951
|
}
|
|
6934
6952
|
return closed;
|
|
6935
6953
|
}
|
|
6936
|
-
async function evictIdlePlaywrightContextsToLimit() {
|
|
6937
|
-
const
|
|
6938
|
-
if (
|
|
6954
|
+
async function evictIdlePlaywrightContextsToLimit(targetLimit) {
|
|
6955
|
+
const configuredMax = config.playwright.maxActiveContexts;
|
|
6956
|
+
if (configuredMax <= 0) return 0;
|
|
6957
|
+
const max = targetLimit !== void 0 ? Math.max(0, targetLimit) : configuredMax;
|
|
6939
6958
|
if (accountPages.size <= max) return 0;
|
|
6940
6959
|
const candidates = priorityOrderForEviction(
|
|
6941
6960
|
Array.from(accountPages.keys()).filter((accountId) => {
|
|
6942
6961
|
if (isAccountServingStream(accountId)) return false;
|
|
6943
|
-
if (
|
|
6962
|
+
if (hasActiveAccountLease(accountId)) return false;
|
|
6944
6963
|
const mutex = accountMutexes.get(accountId);
|
|
6945
6964
|
return mutex?.isIdle();
|
|
6946
6965
|
})
|
|
@@ -6953,6 +6972,7 @@ async function evictIdlePlaywrightContextsToLimit() {
|
|
|
6953
6972
|
for (const candidate of candidates) {
|
|
6954
6973
|
if (accountPages.size <= max) break;
|
|
6955
6974
|
if (isAccountServingStream(candidate.accountId)) continue;
|
|
6975
|
+
if (hasActiveAccountLease(candidate.accountId)) continue;
|
|
6956
6976
|
const mutex = accountMutexes.get(candidate.accountId);
|
|
6957
6977
|
if (!mutex?.isIdle()) continue;
|
|
6958
6978
|
await closePlaywrightForAccount(candidate.accountId).catch((error) => {
|
package/dist/reset-cooldowns.js
CHANGED
|
@@ -1236,6 +1236,26 @@ function clearAllAccountCooldowns() {
|
|
|
1236
1236
|
cooldowns.clear();
|
|
1237
1237
|
return count;
|
|
1238
1238
|
}
|
|
1239
|
+
function getAccountCooldownInfo(accountId) {
|
|
1240
|
+
const entry = cooldowns.get(accountId);
|
|
1241
|
+
if (!entry) return null;
|
|
1242
|
+
const remaining = entry.until - Date.now();
|
|
1243
|
+
if (remaining <= 0) {
|
|
1244
|
+
cooldowns.delete(accountId);
|
|
1245
|
+
if (accountId !== "global") {
|
|
1246
|
+
try {
|
|
1247
|
+
updateAccountCooldown(accountId, 0, null);
|
|
1248
|
+
} catch (err) {
|
|
1249
|
+
console.error(
|
|
1250
|
+
`\u274C [AccountManager] Failed to clear expired cooldown in DB:`,
|
|
1251
|
+
err.message
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1257
|
+
return { onCooldown: true, remainingMs: remaining, reason: entry.reason };
|
|
1258
|
+
}
|
|
1239
1259
|
function markAccountHeadersReady(accountId) {
|
|
1240
1260
|
if (!accountId || accountId === "global") return;
|
|
1241
1261
|
headersReadyAccounts.add(accountId);
|
|
@@ -3889,8 +3909,12 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
3889
3909
|
if (accountPages.has(account.id)) {
|
|
3890
3910
|
return;
|
|
3891
3911
|
}
|
|
3892
|
-
|
|
3893
|
-
|
|
3912
|
+
if (config.playwright.maxActiveContexts > 0) {
|
|
3913
|
+
await evictIdlePlaywrightContextsToLimit(
|
|
3914
|
+
Math.max(1, config.playwright.maxActiveContexts - 1)
|
|
3915
|
+
).catch(() => {
|
|
3916
|
+
});
|
|
3917
|
+
}
|
|
3894
3918
|
const profilePath = getAccountProfilePath(account.id);
|
|
3895
3919
|
const fingerprint = getFingerprintProfile(account.id);
|
|
3896
3920
|
const { engine, channel } = resolveBrowserEngine(browserType);
|
|
@@ -4159,7 +4183,7 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4159
4183
|
if (!acctPage.isClosed()) {
|
|
4160
4184
|
try {
|
|
4161
4185
|
const currentUrl = new URL(acctPage.url());
|
|
4162
|
-
if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname
|
|
4186
|
+
if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
|
|
4163
4187
|
await acctPage.goto(qwenUrl("/"), {
|
|
4164
4188
|
waitUntil: "domcontentloaded",
|
|
4165
4189
|
timeout: config.timeouts.navigation
|
|
@@ -4177,6 +4201,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4177
4201
|
} finally {
|
|
4178
4202
|
release();
|
|
4179
4203
|
}
|
|
4204
|
+
await evictIdlePlaywrightContextsToLimit().catch(() => {
|
|
4205
|
+
});
|
|
4180
4206
|
})();
|
|
4181
4207
|
try {
|
|
4182
4208
|
await initPromise;
|
|
@@ -5679,7 +5705,11 @@ function priorityOrderForEviction(accountIds) {
|
|
|
5679
5705
|
const ranked = getAccountsByPriority(accountIds.map((id) => ({ id })));
|
|
5680
5706
|
const rank = /* @__PURE__ */ new Map();
|
|
5681
5707
|
for (const [index, account] of ranked.entries()) {
|
|
5682
|
-
|
|
5708
|
+
if (getAccountCooldownInfo(account.id)) {
|
|
5709
|
+
rank.set(account.id, Number.MAX_SAFE_INTEGER);
|
|
5710
|
+
} else {
|
|
5711
|
+
rank.set(account.id, index);
|
|
5712
|
+
}
|
|
5683
5713
|
}
|
|
5684
5714
|
return orderContextsForEviction(
|
|
5685
5715
|
accountIds,
|
|
@@ -5687,14 +5717,15 @@ function priorityOrderForEviction(accountIds) {
|
|
|
5687
5717
|
(id) => lastAccountActivity.get(id) ?? 0
|
|
5688
5718
|
);
|
|
5689
5719
|
}
|
|
5690
|
-
async function evictIdlePlaywrightContextsToLimit() {
|
|
5691
|
-
const
|
|
5692
|
-
if (
|
|
5720
|
+
async function evictIdlePlaywrightContextsToLimit(targetLimit) {
|
|
5721
|
+
const configuredMax = config.playwright.maxActiveContexts;
|
|
5722
|
+
if (configuredMax <= 0) return 0;
|
|
5723
|
+
const max = targetLimit !== void 0 ? Math.max(0, targetLimit) : configuredMax;
|
|
5693
5724
|
if (accountPages.size <= max) return 0;
|
|
5694
5725
|
const candidates = priorityOrderForEviction(
|
|
5695
5726
|
Array.from(accountPages.keys()).filter((accountId) => {
|
|
5696
5727
|
if (isAccountServingStream(accountId)) return false;
|
|
5697
|
-
if (
|
|
5728
|
+
if (hasActiveAccountLease(accountId)) return false;
|
|
5698
5729
|
const mutex = accountMutexes.get(accountId);
|
|
5699
5730
|
return mutex?.isIdle();
|
|
5700
5731
|
})
|
|
@@ -5707,6 +5738,7 @@ async function evictIdlePlaywrightContextsToLimit() {
|
|
|
5707
5738
|
for (const candidate of candidates) {
|
|
5708
5739
|
if (accountPages.size <= max) break;
|
|
5709
5740
|
if (isAccountServingStream(candidate.accountId)) continue;
|
|
5741
|
+
if (hasActiveAccountLease(candidate.accountId)) continue;
|
|
5710
5742
|
const mutex = accountMutexes.get(candidate.accountId);
|
|
5711
5743
|
if (!mutex?.isIdle()) continue;
|
|
5712
5744
|
await closePlaywrightForAccount(candidate.accountId).catch((error) => {
|
|
@@ -5809,10 +5841,6 @@ async function closePlaywrightForAccount(accountId) {
|
|
|
5809
5841
|
release();
|
|
5810
5842
|
}
|
|
5811
5843
|
}
|
|
5812
|
-
function isAccountRecentlyActive(accountId, maxIdleMs = 3e5) {
|
|
5813
|
-
const last = lastAccountActivity.get(accountId) ?? 0;
|
|
5814
|
-
return last > 0 && Date.now() - last < maxIdleMs;
|
|
5815
|
-
}
|
|
5816
5844
|
var requireLocal, contextInitHooks, ACCOUNT_MUTEX_MAX_HOLD_MS, accountMutexes, accountContexts, accountPages, inFlightAccountInits, headerCaches, HEADER_CACHE_TTL, COOKIE_CACHE_TTL, cookieCaches, lastAccountActivity, lastKeepAliveNavigation, lastProactiveTokenRefresh, profileResetQueue, profileResetChain, closingAllPlaywright, sleep2, HEADER_CAPTURE_SETTLE_MS, PLAYWRIGHT_MUTEX_WAIT_MS, ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, SESSION_PROBE_NAVIGATION_TIMEOUT_MS, HEADER_CAPTURE_TRIGGER_GRACE_MS, FIRST_TRIGGER_GRACE_MS, HEADER_CAPTURE_TRIGGER_ATTEMPTS, CHAT_INPUT_APPEAR_TIMEOUT_MS, CHAT_INPUT_ACTION_TIMEOUT_MS, PROFILE_RESET_TIMEOUT_MS;
|
|
5817
5845
|
var init_playwright = __esm({
|
|
5818
5846
|
"src/services/playwright.ts"() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.11",
|
|
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": {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"bugs": {
|
|
59
59
|
"url": "https://github.com/johngbl/qwenproxy/issues"
|
|
60
60
|
},
|
|
61
|
-
"author": "johngbl
|
|
61
|
+
"author": "johngbl",
|
|
62
62
|
"license": "ISC",
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@hono/node-server": "^2.1.1",
|
package/src/api/server.ts
CHANGED
|
@@ -883,10 +883,14 @@ export async function startServer(options?: {
|
|
|
883
883
|
void (async () => {
|
|
884
884
|
const { ensureAccountInPriority } = await import("../core/account-priority.ts");
|
|
885
885
|
|
|
886
|
-
const
|
|
887
|
-
config.playwright.
|
|
888
|
-
|
|
889
|
-
|
|
886
|
+
const maxWarmCap =
|
|
887
|
+
config.playwright.maxActiveContexts > 0
|
|
888
|
+
? Math.min(
|
|
889
|
+
config.playwright.startupWarmAccounts,
|
|
890
|
+
config.playwright.maxActiveContexts,
|
|
891
|
+
)
|
|
892
|
+
: config.playwright.startupWarmAccounts;
|
|
893
|
+
const targetWarmTotal = Math.min(maxWarmCap, totalAccounts);
|
|
890
894
|
let currentReadyCount = readyAccountIds.size;
|
|
891
895
|
|
|
892
896
|
if (currentReadyCount < targetWarmTotal && remainingAccounts.length > 0) {
|