qwenproxy-cli 1.6.6 → 1.6.8
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 +6 -1
- package/dist/delete-chats.js +82 -5
- package/dist/index.js +84 -7
- package/dist/login.js +6 -1
- package/dist/reset-cooldowns.js +6 -1
- package/package.json +1 -1
- package/src/core/model-alias.ts +6 -0
- package/src/core/process-guard.ts +8 -1
- package/src/core/version.ts +1 -1
- package/src/routes/chat/streaming.ts +1 -1
- package/src/services/captcha-coordinator.ts +7 -0
- package/src/services/playwright.ts +2 -0
- package/src/services/qwen.ts +81 -13
package/dist/clean-cache.js
CHANGED
|
@@ -3594,6 +3594,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
3594
3594
|
resolveInit = res;
|
|
3595
3595
|
rejectInit = rej;
|
|
3596
3596
|
});
|
|
3597
|
+
inFlightDeferred.catch(() => {
|
|
3598
|
+
});
|
|
3597
3599
|
inFlightAccountInits.set(account.id, inFlightDeferred);
|
|
3598
3600
|
const initPromise = (async () => {
|
|
3599
3601
|
const release = await acquireAccountMutex(
|
|
@@ -5491,7 +5493,10 @@ function isPlaywrightAlreadyClosedMessage(message) {
|
|
|
5491
5493
|
return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
|
|
5492
5494
|
}
|
|
5493
5495
|
function isBenignTeardownError(error) {
|
|
5494
|
-
if (!error) return
|
|
5496
|
+
if (!error) return true;
|
|
5497
|
+
if (typeof error === "object" && Object.keys(error).length === 0 && !(error instanceof Error)) {
|
|
5498
|
+
return true;
|
|
5499
|
+
}
|
|
5495
5500
|
const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
|
|
5496
5501
|
return isPlaywrightAlreadyClosedMessage(message);
|
|
5497
5502
|
}
|
package/dist/delete-chats.js
CHANGED
|
@@ -19,7 +19,10 @@ function isPlaywrightAlreadyClosedMessage(message) {
|
|
|
19
19
|
return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
|
|
20
20
|
}
|
|
21
21
|
function isBenignTeardownError(error) {
|
|
22
|
-
if (!error) return
|
|
22
|
+
if (!error) return true;
|
|
23
|
+
if (typeof error === "object" && Object.keys(error).length === 0 && !(error instanceof Error)) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
23
26
|
const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
|
|
24
27
|
return isPlaywrightAlreadyClosedMessage(message);
|
|
25
28
|
}
|
|
@@ -4453,6 +4456,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4453
4456
|
resolveInit = res;
|
|
4454
4457
|
rejectInit = rej;
|
|
4455
4458
|
});
|
|
4459
|
+
inFlightDeferred.catch(() => {
|
|
4460
|
+
});
|
|
4456
4461
|
inFlightAccountInits.set(account.id, inFlightDeferred);
|
|
4457
4462
|
const initPromise = (async () => {
|
|
4458
4463
|
const release = await acquireAccountMutex(
|
|
@@ -7380,6 +7385,10 @@ function stripThinkingSuffix(model) {
|
|
|
7380
7385
|
reasoningMode: "thinking"
|
|
7381
7386
|
};
|
|
7382
7387
|
}
|
|
7388
|
+
const lower = normalizedModel.toLowerCase();
|
|
7389
|
+
if (lower.includes("omni-flash") || lower.includes("turbo")) {
|
|
7390
|
+
return { baseModel: normalizedModel, enableThinking: false, reasoningMode: "fast" };
|
|
7391
|
+
}
|
|
7383
7392
|
return { baseModel: normalizedModel, enableThinking: true, reasoningMode: "auto" };
|
|
7384
7393
|
}
|
|
7385
7394
|
function mapKnownModelAlias(model) {
|
|
@@ -7747,6 +7756,14 @@ async function recoverBaxiaCaptcha(accountId, label, options = {}) {
|
|
|
7747
7756
|
);
|
|
7748
7757
|
const challengeUrl = options.challengeBody ? extractBaxiaChallengeUrl(options.challengeBody, config.qwen.baseUrl) : null;
|
|
7749
7758
|
activeCaptchaRecoveries.add(accountId);
|
|
7759
|
+
try {
|
|
7760
|
+
const { abortActiveBrowserStreamsForAccount: abortActiveBrowserStreamsForAccount2 } = await Promise.resolve().then(() => (init_qwen(), qwen_exports));
|
|
7761
|
+
abortActiveBrowserStreamsForAccount2(
|
|
7762
|
+
accountId,
|
|
7763
|
+
"Account stream interrupted by captcha challenge recovery"
|
|
7764
|
+
);
|
|
7765
|
+
} catch {
|
|
7766
|
+
}
|
|
7750
7767
|
try {
|
|
7751
7768
|
const solved = await withAccountPage(
|
|
7752
7769
|
accountId,
|
|
@@ -8523,6 +8540,7 @@ __export(qwen_exports, {
|
|
|
8523
8540
|
QwenUpstreamUnavailableError: () => QwenUpstreamUnavailableError,
|
|
8524
8541
|
RetryableQwenStreamError: () => RetryableQwenStreamError,
|
|
8525
8542
|
STREAM_SLOT_MAX_HOLD_MS: () => STREAM_SLOT_MAX_HOLD_MS,
|
|
8543
|
+
abortActiveBrowserStreamsForAccount: () => abortActiveBrowserStreamsForAccount,
|
|
8526
8544
|
acquireAccountStreamLock: () => acquireAccountStreamLock,
|
|
8527
8545
|
acquireNewQwenChatSession: () => acquireNewQwenChatSession,
|
|
8528
8546
|
buildCapturedQwenHeaders: () => buildCapturedQwenHeaders,
|
|
@@ -8569,6 +8587,27 @@ __export(qwen_exports, {
|
|
|
8569
8587
|
});
|
|
8570
8588
|
import crypto5 from "crypto";
|
|
8571
8589
|
import { v4 as uuidv43 } from "uuid";
|
|
8590
|
+
function abortActiveBrowserStreamsForAccount(accountId, reason = "Account browser stream interrupted (page context unloaded)") {
|
|
8591
|
+
let count = 0;
|
|
8592
|
+
for (const [requestId, state] of browserStreamStates) {
|
|
8593
|
+
if (state.accountId === accountId && !state.done) {
|
|
8594
|
+
count++;
|
|
8595
|
+
handleBrowserStreamEvent(requestId, {
|
|
8596
|
+
type: "error",
|
|
8597
|
+
message: reason,
|
|
8598
|
+
errorName: "AbortError"
|
|
8599
|
+
});
|
|
8600
|
+
}
|
|
8601
|
+
}
|
|
8602
|
+
if (count > 0) {
|
|
8603
|
+
logger.debug("[Qwen] Aborted active browser streams for account", {
|
|
8604
|
+
accountId,
|
|
8605
|
+
count,
|
|
8606
|
+
reason
|
|
8607
|
+
});
|
|
8608
|
+
}
|
|
8609
|
+
return count;
|
|
8610
|
+
}
|
|
8572
8611
|
async function registerBrowserContextStreamBinding(context) {
|
|
8573
8612
|
if (browserStreamBindingContexts.has(context)) return;
|
|
8574
8613
|
browserStreamBindingContexts.add(context);
|
|
@@ -9217,7 +9256,6 @@ async function requestQwenPersonalizationInBrowser(accountId, method, path6, hea
|
|
|
9217
9256
|
headers,
|
|
9218
9257
|
payload === void 0 ? void 0 : JSON.stringify(payload),
|
|
9219
9258
|
{
|
|
9220
|
-
settingsPage: true,
|
|
9221
9259
|
referrer: qwenUrl("/settings/personalization"),
|
|
9222
9260
|
noMutexRecovery: true
|
|
9223
9261
|
}
|
|
@@ -9263,6 +9301,7 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
|
|
|
9263
9301
|
}
|
|
9264
9302
|
const requestId = uuidv43();
|
|
9265
9303
|
const state = {
|
|
9304
|
+
accountId,
|
|
9266
9305
|
chunks: [],
|
|
9267
9306
|
done: false,
|
|
9268
9307
|
error: null,
|
|
@@ -9279,10 +9318,15 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
|
|
|
9279
9318
|
let settled = false;
|
|
9280
9319
|
let abortListener;
|
|
9281
9320
|
let cancelPromise;
|
|
9321
|
+
let pageCleanup;
|
|
9282
9322
|
const cleanup = () => {
|
|
9283
9323
|
if (settled) return;
|
|
9284
9324
|
settled = true;
|
|
9285
9325
|
if (abortListener) signal.removeEventListener("abort", abortListener);
|
|
9326
|
+
try {
|
|
9327
|
+
pageCleanup?.();
|
|
9328
|
+
} catch {
|
|
9329
|
+
}
|
|
9286
9330
|
browserStreamStates.delete(requestId);
|
|
9287
9331
|
};
|
|
9288
9332
|
const cancel = () => {
|
|
@@ -9316,6 +9360,39 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
|
|
|
9316
9360
|
accountId,
|
|
9317
9361
|
async (page) => {
|
|
9318
9362
|
await ensureBrowserStreamBinding(page);
|
|
9363
|
+
if (typeof page.on === "function") {
|
|
9364
|
+
const onFrameNavigated = (frame) => {
|
|
9365
|
+
if (!settled && typeof page.mainFrame === "function" && frame === page.mainFrame()) {
|
|
9366
|
+
const url2 = typeof frame.url === "function" ? frame.url() : String(frame.url || "");
|
|
9367
|
+
if (url2.startsWith(qwenOrigin()) && !url2.includes("/auth") && !url2.includes("_____tmd_____") && !url2.includes("/punish")) {
|
|
9368
|
+
return;
|
|
9369
|
+
}
|
|
9370
|
+
handleBrowserStreamEvent(requestId, {
|
|
9371
|
+
type: "error",
|
|
9372
|
+
message: "Qwen browser page navigated away during stream",
|
|
9373
|
+
errorName: "AbortError"
|
|
9374
|
+
});
|
|
9375
|
+
}
|
|
9376
|
+
};
|
|
9377
|
+
const onPageClosed = () => {
|
|
9378
|
+
if (!settled) {
|
|
9379
|
+
handleBrowserStreamEvent(requestId, {
|
|
9380
|
+
type: "error",
|
|
9381
|
+
message: "Qwen browser page closed during stream",
|
|
9382
|
+
errorName: "AbortError"
|
|
9383
|
+
});
|
|
9384
|
+
}
|
|
9385
|
+
};
|
|
9386
|
+
page.on("framenavigated", onFrameNavigated);
|
|
9387
|
+
page.on("close", onPageClosed);
|
|
9388
|
+
pageCleanup = () => {
|
|
9389
|
+
try {
|
|
9390
|
+
page.off?.("framenavigated", onFrameNavigated);
|
|
9391
|
+
page.off?.("close", onPageClosed);
|
|
9392
|
+
} catch {
|
|
9393
|
+
}
|
|
9394
|
+
};
|
|
9395
|
+
}
|
|
9319
9396
|
if (config.captcha.enabled) {
|
|
9320
9397
|
captchaWatcher = startBaxiaCaptchaWatcher(
|
|
9321
9398
|
page,
|
|
@@ -9843,7 +9920,6 @@ async function disableNativeTools(accountId) {
|
|
|
9843
9920
|
requestHeaders,
|
|
9844
9921
|
JSON.stringify(payload),
|
|
9845
9922
|
{
|
|
9846
|
-
settingsPage: true,
|
|
9847
9923
|
referrer: qwenUrl("/settings/personalization")
|
|
9848
9924
|
}
|
|
9849
9925
|
);
|
|
@@ -10500,7 +10576,7 @@ async function createQwenStreamInternal(prompt, enableThinking, modelId, forcedP
|
|
|
10500
10576
|
}
|
|
10501
10577
|
});
|
|
10502
10578
|
}
|
|
10503
|
-
const baseTimeoutMs =
|
|
10579
|
+
const baseTimeoutMs = config.timeouts.idleStreamTimeout;
|
|
10504
10580
|
const dynamicIdleTimeoutMs = computeDynamicIdleTimeout({
|
|
10505
10581
|
enableThinking,
|
|
10506
10582
|
parallelEscape: options?.parallelEscape,
|
|
@@ -10516,9 +10592,10 @@ async function createQwenStreamInternal(prompt, enableThinking, modelId, forcedP
|
|
|
10516
10592
|
dynamicTimeout: dynamicIdleTimeoutMs
|
|
10517
10593
|
});
|
|
10518
10594
|
const firstChunkDeadlineMs = enableThinking ? Math.max(
|
|
10595
|
+
config.timeouts.reasoningModelTimeout,
|
|
10519
10596
|
config.timeouts.firstChunkTimeout,
|
|
10520
10597
|
config.timeouts.timeToFirstByte
|
|
10521
|
-
) :
|
|
10598
|
+
) : Math.max(config.timeouts.firstChunkTimeout, config.timeouts.timeToFirstByte);
|
|
10522
10599
|
return addIdleTimeoutToStream(
|
|
10523
10600
|
stream,
|
|
10524
10601
|
controller2,
|
package/dist/index.js
CHANGED
|
@@ -156,7 +156,10 @@ function isPlaywrightAlreadyClosedMessage(message) {
|
|
|
156
156
|
return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
|
|
157
157
|
}
|
|
158
158
|
function isBenignTeardownError(error) {
|
|
159
|
-
if (!error) return
|
|
159
|
+
if (!error) return true;
|
|
160
|
+
if (typeof error === "object" && Object.keys(error).length === 0 && !(error instanceof Error)) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
160
163
|
const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
|
|
161
164
|
return isPlaywrightAlreadyClosedMessage(message);
|
|
162
165
|
}
|
|
@@ -5563,6 +5566,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
5563
5566
|
resolveInit = res;
|
|
5564
5567
|
rejectInit = rej;
|
|
5565
5568
|
});
|
|
5569
|
+
inFlightDeferred.catch(() => {
|
|
5570
|
+
});
|
|
5566
5571
|
inFlightAccountInits.set(account.id, inFlightDeferred);
|
|
5567
5572
|
const initPromise = (async () => {
|
|
5568
5573
|
const release = await acquireAccountMutex(
|
|
@@ -8728,6 +8733,10 @@ function stripThinkingSuffix(model) {
|
|
|
8728
8733
|
reasoningMode: "thinking"
|
|
8729
8734
|
};
|
|
8730
8735
|
}
|
|
8736
|
+
const lower = normalizedModel.toLowerCase();
|
|
8737
|
+
if (lower.includes("omni-flash") || lower.includes("turbo")) {
|
|
8738
|
+
return { baseModel: normalizedModel, enableThinking: false, reasoningMode: "fast" };
|
|
8739
|
+
}
|
|
8731
8740
|
return { baseModel: normalizedModel, enableThinking: true, reasoningMode: "auto" };
|
|
8732
8741
|
}
|
|
8733
8742
|
function mapKnownModelAlias(model) {
|
|
@@ -9137,6 +9146,14 @@ async function recoverBaxiaCaptcha(accountId, label, options = {}) {
|
|
|
9137
9146
|
);
|
|
9138
9147
|
const challengeUrl = options.challengeBody ? extractBaxiaChallengeUrl(options.challengeBody, config.qwen.baseUrl) : null;
|
|
9139
9148
|
activeCaptchaRecoveries.add(accountId);
|
|
9149
|
+
try {
|
|
9150
|
+
const { abortActiveBrowserStreamsForAccount: abortActiveBrowserStreamsForAccount2 } = await Promise.resolve().then(() => (init_qwen(), qwen_exports));
|
|
9151
|
+
abortActiveBrowserStreamsForAccount2(
|
|
9152
|
+
accountId,
|
|
9153
|
+
"Account stream interrupted by captcha challenge recovery"
|
|
9154
|
+
);
|
|
9155
|
+
} catch {
|
|
9156
|
+
}
|
|
9140
9157
|
try {
|
|
9141
9158
|
const solved = await withAccountPage(
|
|
9142
9159
|
accountId,
|
|
@@ -11713,6 +11730,7 @@ __export(qwen_exports, {
|
|
|
11713
11730
|
QwenUpstreamUnavailableError: () => QwenUpstreamUnavailableError,
|
|
11714
11731
|
RetryableQwenStreamError: () => RetryableQwenStreamError,
|
|
11715
11732
|
STREAM_SLOT_MAX_HOLD_MS: () => STREAM_SLOT_MAX_HOLD_MS,
|
|
11733
|
+
abortActiveBrowserStreamsForAccount: () => abortActiveBrowserStreamsForAccount,
|
|
11716
11734
|
acquireAccountStreamLock: () => acquireAccountStreamLock,
|
|
11717
11735
|
acquireNewQwenChatSession: () => acquireNewQwenChatSession,
|
|
11718
11736
|
buildCapturedQwenHeaders: () => buildCapturedQwenHeaders,
|
|
@@ -11759,6 +11777,27 @@ __export(qwen_exports, {
|
|
|
11759
11777
|
});
|
|
11760
11778
|
import crypto7 from "crypto";
|
|
11761
11779
|
import { v4 as uuidv43 } from "uuid";
|
|
11780
|
+
function abortActiveBrowserStreamsForAccount(accountId, reason = "Account browser stream interrupted (page context unloaded)") {
|
|
11781
|
+
let count = 0;
|
|
11782
|
+
for (const [requestId, state] of browserStreamStates) {
|
|
11783
|
+
if (state.accountId === accountId && !state.done) {
|
|
11784
|
+
count++;
|
|
11785
|
+
handleBrowserStreamEvent(requestId, {
|
|
11786
|
+
type: "error",
|
|
11787
|
+
message: reason,
|
|
11788
|
+
errorName: "AbortError"
|
|
11789
|
+
});
|
|
11790
|
+
}
|
|
11791
|
+
}
|
|
11792
|
+
if (count > 0) {
|
|
11793
|
+
logger.debug("[Qwen] Aborted active browser streams for account", {
|
|
11794
|
+
accountId,
|
|
11795
|
+
count,
|
|
11796
|
+
reason
|
|
11797
|
+
});
|
|
11798
|
+
}
|
|
11799
|
+
return count;
|
|
11800
|
+
}
|
|
11762
11801
|
async function registerBrowserContextStreamBinding(context) {
|
|
11763
11802
|
if (browserStreamBindingContexts.has(context)) return;
|
|
11764
11803
|
browserStreamBindingContexts.add(context);
|
|
@@ -12407,7 +12446,6 @@ async function requestQwenPersonalizationInBrowser(accountId, method, path21, he
|
|
|
12407
12446
|
headers,
|
|
12408
12447
|
payload === void 0 ? void 0 : JSON.stringify(payload),
|
|
12409
12448
|
{
|
|
12410
|
-
settingsPage: true,
|
|
12411
12449
|
referrer: qwenUrl("/settings/personalization"),
|
|
12412
12450
|
noMutexRecovery: true
|
|
12413
12451
|
}
|
|
@@ -12453,6 +12491,7 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
|
|
|
12453
12491
|
}
|
|
12454
12492
|
const requestId = uuidv43();
|
|
12455
12493
|
const state = {
|
|
12494
|
+
accountId,
|
|
12456
12495
|
chunks: [],
|
|
12457
12496
|
done: false,
|
|
12458
12497
|
error: null,
|
|
@@ -12469,10 +12508,15 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
|
|
|
12469
12508
|
let settled = false;
|
|
12470
12509
|
let abortListener;
|
|
12471
12510
|
let cancelPromise;
|
|
12511
|
+
let pageCleanup;
|
|
12472
12512
|
const cleanup = () => {
|
|
12473
12513
|
if (settled) return;
|
|
12474
12514
|
settled = true;
|
|
12475
12515
|
if (abortListener) signal.removeEventListener("abort", abortListener);
|
|
12516
|
+
try {
|
|
12517
|
+
pageCleanup?.();
|
|
12518
|
+
} catch {
|
|
12519
|
+
}
|
|
12476
12520
|
browserStreamStates.delete(requestId);
|
|
12477
12521
|
};
|
|
12478
12522
|
const cancel = () => {
|
|
@@ -12506,6 +12550,39 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
|
|
|
12506
12550
|
accountId,
|
|
12507
12551
|
async (page) => {
|
|
12508
12552
|
await ensureBrowserStreamBinding(page);
|
|
12553
|
+
if (typeof page.on === "function") {
|
|
12554
|
+
const onFrameNavigated = (frame) => {
|
|
12555
|
+
if (!settled && typeof page.mainFrame === "function" && frame === page.mainFrame()) {
|
|
12556
|
+
const url2 = typeof frame.url === "function" ? frame.url() : String(frame.url || "");
|
|
12557
|
+
if (url2.startsWith(qwenOrigin()) && !url2.includes("/auth") && !url2.includes("_____tmd_____") && !url2.includes("/punish")) {
|
|
12558
|
+
return;
|
|
12559
|
+
}
|
|
12560
|
+
handleBrowserStreamEvent(requestId, {
|
|
12561
|
+
type: "error",
|
|
12562
|
+
message: "Qwen browser page navigated away during stream",
|
|
12563
|
+
errorName: "AbortError"
|
|
12564
|
+
});
|
|
12565
|
+
}
|
|
12566
|
+
};
|
|
12567
|
+
const onPageClosed = () => {
|
|
12568
|
+
if (!settled) {
|
|
12569
|
+
handleBrowserStreamEvent(requestId, {
|
|
12570
|
+
type: "error",
|
|
12571
|
+
message: "Qwen browser page closed during stream",
|
|
12572
|
+
errorName: "AbortError"
|
|
12573
|
+
});
|
|
12574
|
+
}
|
|
12575
|
+
};
|
|
12576
|
+
page.on("framenavigated", onFrameNavigated);
|
|
12577
|
+
page.on("close", onPageClosed);
|
|
12578
|
+
pageCleanup = () => {
|
|
12579
|
+
try {
|
|
12580
|
+
page.off?.("framenavigated", onFrameNavigated);
|
|
12581
|
+
page.off?.("close", onPageClosed);
|
|
12582
|
+
} catch {
|
|
12583
|
+
}
|
|
12584
|
+
};
|
|
12585
|
+
}
|
|
12509
12586
|
if (config.captcha.enabled) {
|
|
12510
12587
|
captchaWatcher = startBaxiaCaptchaWatcher(
|
|
12511
12588
|
page,
|
|
@@ -13033,7 +13110,6 @@ async function disableNativeTools(accountId) {
|
|
|
13033
13110
|
requestHeaders,
|
|
13034
13111
|
JSON.stringify(payload),
|
|
13035
13112
|
{
|
|
13036
|
-
settingsPage: true,
|
|
13037
13113
|
referrer: qwenUrl("/settings/personalization")
|
|
13038
13114
|
}
|
|
13039
13115
|
);
|
|
@@ -13690,7 +13766,7 @@ async function createQwenStreamInternal(prompt, enableThinking, modelId, forcedP
|
|
|
13690
13766
|
}
|
|
13691
13767
|
});
|
|
13692
13768
|
}
|
|
13693
|
-
const baseTimeoutMs =
|
|
13769
|
+
const baseTimeoutMs = config.timeouts.idleStreamTimeout;
|
|
13694
13770
|
const dynamicIdleTimeoutMs = computeDynamicIdleTimeout({
|
|
13695
13771
|
enableThinking,
|
|
13696
13772
|
parallelEscape: options?.parallelEscape,
|
|
@@ -13706,9 +13782,10 @@ async function createQwenStreamInternal(prompt, enableThinking, modelId, forcedP
|
|
|
13706
13782
|
dynamicTimeout: dynamicIdleTimeoutMs
|
|
13707
13783
|
});
|
|
13708
13784
|
const firstChunkDeadlineMs = enableThinking ? Math.max(
|
|
13785
|
+
config.timeouts.reasoningModelTimeout,
|
|
13709
13786
|
config.timeouts.firstChunkTimeout,
|
|
13710
13787
|
config.timeouts.timeToFirstByte
|
|
13711
|
-
) :
|
|
13788
|
+
) : Math.max(config.timeouts.firstChunkTimeout, config.timeouts.timeToFirstByte);
|
|
13712
13789
|
return addIdleTimeoutToStream(
|
|
13713
13790
|
stream,
|
|
13714
13791
|
controller2,
|
|
@@ -22948,7 +23025,7 @@ Please retry your tool call(s) with correct JSON and valid tool names from the a
|
|
|
22948
23025
|
stack: err.stack?.split("\n").slice(0, 3).join(" | "),
|
|
22949
23026
|
completionId
|
|
22950
23027
|
};
|
|
22951
|
-
if (retryable) {
|
|
23028
|
+
if (retryable || isNetworkLikeError(err) || isAbortError(err)) {
|
|
22952
23029
|
if (activeAccountId && isNetworkLikeError(err)) {
|
|
22953
23030
|
noteMidStreamNetworkFailure(activeAccountId);
|
|
22954
23031
|
}
|
|
@@ -27018,7 +27095,7 @@ function getAppVersion() {
|
|
|
27018
27095
|
}
|
|
27019
27096
|
} catch {
|
|
27020
27097
|
}
|
|
27021
|
-
cachedVersion = "v1.6.
|
|
27098
|
+
cachedVersion = "v1.6.8";
|
|
27022
27099
|
return cachedVersion;
|
|
27023
27100
|
}
|
|
27024
27101
|
var cachedVersion;
|
package/dist/login.js
CHANGED
|
@@ -1108,7 +1108,10 @@ function isPlaywrightAlreadyClosedMessage(message) {
|
|
|
1108
1108
|
return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
|
|
1109
1109
|
}
|
|
1110
1110
|
function isBenignTeardownError(error) {
|
|
1111
|
-
if (!error) return
|
|
1111
|
+
if (!error) return true;
|
|
1112
|
+
if (typeof error === "object" && Object.keys(error).length === 0 && !(error instanceof Error)) {
|
|
1113
|
+
return true;
|
|
1114
|
+
}
|
|
1112
1115
|
const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
|
|
1113
1116
|
return isPlaywrightAlreadyClosedMessage(message);
|
|
1114
1117
|
}
|
|
@@ -4426,6 +4429,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4426
4429
|
resolveInit = res;
|
|
4427
4430
|
rejectInit = rej;
|
|
4428
4431
|
});
|
|
4432
|
+
inFlightDeferred.catch(() => {
|
|
4433
|
+
});
|
|
4429
4434
|
inFlightAccountInits.set(account.id, inFlightDeferred);
|
|
4430
4435
|
const initPromise = (async () => {
|
|
4431
4436
|
const release = await acquireAccountMutex(
|
package/dist/reset-cooldowns.js
CHANGED
|
@@ -2825,7 +2825,10 @@ function isPlaywrightAlreadyClosedMessage(message) {
|
|
|
2825
2825
|
return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
|
|
2826
2826
|
}
|
|
2827
2827
|
function isBenignTeardownError(error) {
|
|
2828
|
-
if (!error) return
|
|
2828
|
+
if (!error) return true;
|
|
2829
|
+
if (typeof error === "object" && Object.keys(error).length === 0 && !(error instanceof Error)) {
|
|
2830
|
+
return true;
|
|
2831
|
+
}
|
|
2829
2832
|
const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
|
|
2830
2833
|
return isPlaywrightAlreadyClosedMessage(message);
|
|
2831
2834
|
}
|
|
@@ -3857,6 +3860,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
3857
3860
|
resolveInit = res;
|
|
3858
3861
|
rejectInit = rej;
|
|
3859
3862
|
});
|
|
3863
|
+
inFlightDeferred.catch(() => {
|
|
3864
|
+
});
|
|
3860
3865
|
inFlightAccountInits.set(account.id, inFlightDeferred);
|
|
3861
3866
|
const initPromise = (async () => {
|
|
3862
3867
|
const release = await acquireAccountMutex(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.8",
|
|
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/model-alias.ts
CHANGED
|
@@ -76,6 +76,12 @@ export function stripThinkingSuffix(model: string): {
|
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
// Default for non-thinking models (omni-flash, turbo)
|
|
80
|
+
const lower = normalizedModel.toLowerCase();
|
|
81
|
+
if (lower.includes("omni-flash") || lower.includes("turbo")) {
|
|
82
|
+
return { baseModel: normalizedModel, enableThinking: false, reasoningMode: "fast" };
|
|
83
|
+
}
|
|
84
|
+
|
|
79
85
|
// Default: auto thinking (Qwen decides)
|
|
80
86
|
return { baseModel: normalizedModel, enableThinking: true, reasoningMode: "auto" };
|
|
81
87
|
}
|
|
@@ -27,7 +27,14 @@ export function isPlaywrightAlreadyClosedMessage(message: string): boolean {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function isBenignTeardownError(error: unknown): boolean {
|
|
30
|
-
if (!error) return
|
|
30
|
+
if (!error) return true;
|
|
31
|
+
if (
|
|
32
|
+
typeof error === "object" &&
|
|
33
|
+
Object.keys(error).length === 0 &&
|
|
34
|
+
!(error instanceof Error)
|
|
35
|
+
) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
31
38
|
const message =
|
|
32
39
|
error instanceof Error
|
|
33
40
|
? error.message
|
package/src/core/version.ts
CHANGED
|
@@ -2819,7 +2819,7 @@ export async function processStreamingResponse(
|
|
|
2819
2819
|
completionId,
|
|
2820
2820
|
};
|
|
2821
2821
|
|
|
2822
|
-
if (retryable) {
|
|
2822
|
+
if (retryable || isNetworkLikeError(err) || isAbortError(err)) {
|
|
2823
2823
|
if (activeAccountId && isNetworkLikeError(err)) {
|
|
2824
2824
|
noteMidStreamNetworkFailure(activeAccountId);
|
|
2825
2825
|
}
|
|
@@ -125,6 +125,13 @@ export async function recoverBaxiaCaptcha(
|
|
|
125
125
|
: null;
|
|
126
126
|
|
|
127
127
|
activeCaptchaRecoveries.add(accountId);
|
|
128
|
+
try {
|
|
129
|
+
const { abortActiveBrowserStreamsForAccount } = await import("./qwen.ts");
|
|
130
|
+
abortActiveBrowserStreamsForAccount(
|
|
131
|
+
accountId,
|
|
132
|
+
"Account stream interrupted by captcha challenge recovery",
|
|
133
|
+
);
|
|
134
|
+
} catch {}
|
|
128
135
|
try {
|
|
129
136
|
// recoverOnTimeout: false ensures that if the captcha solve times out,
|
|
130
137
|
// withAccountPage does NOT destructively kill the browser context, which
|
|
@@ -1535,6 +1535,8 @@ export async function initPlaywrightForAccount(
|
|
|
1535
1535
|
resolveInit = res;
|
|
1536
1536
|
rejectInit = rej;
|
|
1537
1537
|
});
|
|
1538
|
+
// Prevent UnhandledRejection if rejectInit is called when no concurrent callers are awaiting
|
|
1539
|
+
inFlightDeferred.catch(() => {});
|
|
1538
1540
|
inFlightAccountInits.set(account.id, inFlightDeferred);
|
|
1539
1541
|
|
|
1540
1542
|
const initPromise = (async () => {
|
package/src/services/qwen.ts
CHANGED
|
@@ -117,6 +117,7 @@ interface BrowserStreamMetadata {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
interface BrowserStreamState {
|
|
120
|
+
accountId?: string;
|
|
120
121
|
chunks: Uint8Array[];
|
|
121
122
|
done: boolean;
|
|
122
123
|
error: Error | null;
|
|
@@ -128,6 +129,36 @@ const browserStreamStates = new Map<string, BrowserStreamState>();
|
|
|
128
129
|
const browserStreamBindingPages = new WeakSet<object>();
|
|
129
130
|
const browserStreamBindingContexts = new WeakSet<object>();
|
|
130
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Abort all active browser streams for an account immediately.
|
|
134
|
+
* Called when an account page is navigated by captcha recovery, closed, or reset,
|
|
135
|
+
* so in-flight streams fail fast (retryable) instead of stalling for minutes.
|
|
136
|
+
*/
|
|
137
|
+
export function abortActiveBrowserStreamsForAccount(
|
|
138
|
+
accountId: string,
|
|
139
|
+
reason = "Account browser stream interrupted (page context unloaded)",
|
|
140
|
+
): number {
|
|
141
|
+
let count = 0;
|
|
142
|
+
for (const [requestId, state] of browserStreamStates) {
|
|
143
|
+
if (state.accountId === accountId && !state.done) {
|
|
144
|
+
count++;
|
|
145
|
+
handleBrowserStreamEvent(requestId, {
|
|
146
|
+
type: "error",
|
|
147
|
+
message: reason,
|
|
148
|
+
errorName: "AbortError",
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (count > 0) {
|
|
153
|
+
logger.debug("[Qwen] Aborted active browser streams for account", {
|
|
154
|
+
accountId,
|
|
155
|
+
count,
|
|
156
|
+
reason,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return count;
|
|
160
|
+
}
|
|
161
|
+
|
|
131
162
|
export async function registerBrowserContextStreamBinding(
|
|
132
163
|
context: BrowserContext,
|
|
133
164
|
): Promise<void> {
|
|
@@ -1194,7 +1225,6 @@ async function requestQwenPersonalizationInBrowser(
|
|
|
1194
1225
|
headers,
|
|
1195
1226
|
payload === undefined ? undefined : JSON.stringify(payload),
|
|
1196
1227
|
{
|
|
1197
|
-
settingsPage: true,
|
|
1198
1228
|
referrer: qwenUrl("/settings/personalization"),
|
|
1199
1229
|
noMutexRecovery: true,
|
|
1200
1230
|
},
|
|
@@ -1263,6 +1293,7 @@ async function createQwenBrowserResponse(
|
|
|
1263
1293
|
|
|
1264
1294
|
const requestId = uuidv4();
|
|
1265
1295
|
const state: BrowserStreamState = {
|
|
1296
|
+
accountId,
|
|
1266
1297
|
chunks: [],
|
|
1267
1298
|
done: false,
|
|
1268
1299
|
error: null,
|
|
@@ -1283,10 +1314,14 @@ async function createQwenBrowserResponse(
|
|
|
1283
1314
|
let settled = false;
|
|
1284
1315
|
let abortListener: (() => void) | undefined;
|
|
1285
1316
|
let cancelPromise: Promise<void> | undefined;
|
|
1317
|
+
let pageCleanup: (() => void) | undefined;
|
|
1286
1318
|
const cleanup = () => {
|
|
1287
1319
|
if (settled) return;
|
|
1288
1320
|
settled = true;
|
|
1289
1321
|
if (abortListener) signal.removeEventListener("abort", abortListener);
|
|
1322
|
+
try {
|
|
1323
|
+
pageCleanup?.();
|
|
1324
|
+
} catch {}
|
|
1290
1325
|
browserStreamStates.delete(requestId);
|
|
1291
1326
|
};
|
|
1292
1327
|
const cancel = () => {
|
|
@@ -1327,6 +1362,44 @@ async function createQwenBrowserResponse(
|
|
|
1327
1362
|
accountId,
|
|
1328
1363
|
async (page) => {
|
|
1329
1364
|
await ensureBrowserStreamBinding(page);
|
|
1365
|
+
if (typeof (page as any).on === "function") {
|
|
1366
|
+
const onFrameNavigated = (frame: any) => {
|
|
1367
|
+
if (!settled && typeof (page as any).mainFrame === "function" && frame === (page as any).mainFrame()) {
|
|
1368
|
+
const url = typeof frame.url === "function" ? frame.url() : String(frame.url || "");
|
|
1369
|
+
// Safe SPA in-app routing within Qwen Web (e.g. /c/<id>, /#) must NEVER abort active streams
|
|
1370
|
+
if (
|
|
1371
|
+
url.startsWith(qwenOrigin()) &&
|
|
1372
|
+
!url.includes("/auth") &&
|
|
1373
|
+
!url.includes("_____tmd_____") &&
|
|
1374
|
+
!url.includes("/punish")
|
|
1375
|
+
) {
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
handleBrowserStreamEvent(requestId, {
|
|
1379
|
+
type: "error",
|
|
1380
|
+
message: "Qwen browser page navigated away during stream",
|
|
1381
|
+
errorName: "AbortError",
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
const onPageClosed = () => {
|
|
1386
|
+
if (!settled) {
|
|
1387
|
+
handleBrowserStreamEvent(requestId, {
|
|
1388
|
+
type: "error",
|
|
1389
|
+
message: "Qwen browser page closed during stream",
|
|
1390
|
+
errorName: "AbortError",
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1394
|
+
(page as any).on("framenavigated", onFrameNavigated);
|
|
1395
|
+
(page as any).on("close", onPageClosed);
|
|
1396
|
+
pageCleanup = () => {
|
|
1397
|
+
try {
|
|
1398
|
+
(page as any).off?.("framenavigated", onFrameNavigated);
|
|
1399
|
+
(page as any).off?.("close", onPageClosed);
|
|
1400
|
+
} catch {}
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1330
1403
|
if (config.captcha.enabled) {
|
|
1331
1404
|
captchaWatcher = startBaxiaCaptchaWatcher(
|
|
1332
1405
|
page,
|
|
@@ -2049,7 +2122,6 @@ export async function disableNativeTools(accountId?: string): Promise<void> {
|
|
|
2049
2122
|
requestHeaders,
|
|
2050
2123
|
JSON.stringify(payload),
|
|
2051
2124
|
{
|
|
2052
|
-
settingsPage: true,
|
|
2053
2125
|
referrer: qwenUrl("/settings/personalization"),
|
|
2054
2126
|
},
|
|
2055
2127
|
);
|
|
@@ -3011,13 +3083,9 @@ async function createQwenStreamInternal(
|
|
|
3011
3083
|
}
|
|
3012
3084
|
|
|
3013
3085
|
// Dynamic idle timeout based on model type and payload size
|
|
3014
|
-
//
|
|
3015
|
-
//
|
|
3016
|
-
|
|
3017
|
-
// ONLY when non-thinking (see computeDynamicIdleTimeout).
|
|
3018
|
-
const baseTimeoutMs = enableThinking
|
|
3019
|
-
? config.timeouts.reasoningModelTimeout
|
|
3020
|
-
: config.timeouts.idleStreamTimeout;
|
|
3086
|
+
// Base timeout for gaps between chunks is governed by IDLE_STREAM_TIMEOUT (default 60s)
|
|
3087
|
+
// plus dynamic payload scaling (30s per MB).
|
|
3088
|
+
const baseTimeoutMs = config.timeouts.idleStreamTimeout;
|
|
3021
3089
|
const dynamicIdleTimeoutMs = computeDynamicIdleTimeout({
|
|
3022
3090
|
enableThinking,
|
|
3023
3091
|
parallelEscape: options?.parallelEscape,
|
|
@@ -3034,15 +3102,15 @@ async function createQwenStreamInternal(
|
|
|
3034
3102
|
dynamicTimeout: dynamicIdleTimeoutMs,
|
|
3035
3103
|
});
|
|
3036
3104
|
|
|
3037
|
-
// Thinking models
|
|
3038
|
-
//
|
|
3039
|
-
// fast (retryable) so the account slot is not held for 10 minutes.
|
|
3105
|
+
// Thinking models legitimately pause before first chunk (up to reasoningModelTimeout).
|
|
3106
|
+
// After the first chunk, the normal dynamicIdleTimeoutMs (60s+) governs chunk gaps.
|
|
3040
3107
|
const firstChunkDeadlineMs = enableThinking
|
|
3041
3108
|
? Math.max(
|
|
3109
|
+
config.timeouts.reasoningModelTimeout,
|
|
3042
3110
|
config.timeouts.firstChunkTimeout,
|
|
3043
3111
|
config.timeouts.timeToFirstByte,
|
|
3044
3112
|
)
|
|
3045
|
-
:
|
|
3113
|
+
: Math.max(config.timeouts.firstChunkTimeout, config.timeouts.timeToFirstByte);
|
|
3046
3114
|
|
|
3047
3115
|
return addIdleTimeoutToStream(
|
|
3048
3116
|
stream,
|