qwenproxy-cli 1.4.8 → 1.5.1
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 +59 -44
- package/dist/delete-chats.js +88 -50
- package/dist/index.js +178 -111
- package/dist/login.js +43 -44
- package/dist/reset-cooldowns.js +46 -44
- package/package.json +3 -3
- package/src/core/account-manager.ts +9 -0
- package/src/core/server-log-buffer.ts +10 -3
- package/src/core/version.ts +1 -1
- package/src/routes/chat/account.ts +22 -10
- package/src/routes/chat/helpers.ts +29 -1
- package/src/routes/chat/retry-policy.ts +5 -5
- package/src/routes/chat/streaming.ts +13 -6
- package/src/routes/upload.ts +19 -14
- package/src/services/playwright.ts +40 -49
- package/src/services/qwen-chat-pool.ts +10 -6
- package/src/services/qwen-headers.ts +2 -1
- package/src/services/qwen.ts +22 -5
- package/src/tools/instructions.ts +38 -25
- package/src/tools/parser.ts +7 -0
package/dist/login.js
CHANGED
|
@@ -2016,7 +2016,15 @@ function defaultCooldownDurationMs() {
|
|
|
2016
2016
|
return computeQuotaCooldownMs(Date.now());
|
|
2017
2017
|
}
|
|
2018
2018
|
function markAccountRateLimited(accountId, cooldownMs, reason, options = {}) {
|
|
2019
|
+
if (cooldownMs !== void 0 && cooldownMs <= 0) {
|
|
2020
|
+
clearAccountCooldown(accountId);
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2019
2023
|
const duration = cooldownMs ?? defaultCooldownDurationMs();
|
|
2024
|
+
if (duration <= 0) {
|
|
2025
|
+
clearAccountCooldown(accountId);
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2020
2028
|
const until = Date.now() + duration;
|
|
2021
2029
|
const cooldownReason = reason ?? "RateLimited";
|
|
2022
2030
|
cooldowns.set(accountId, {
|
|
@@ -3396,26 +3404,6 @@ async function isPageLoggedIn(page, timeoutMs = SESSION_PROBE_NAVIGATION_TIMEOUT
|
|
|
3396
3404
|
}
|
|
3397
3405
|
} catch {
|
|
3398
3406
|
}
|
|
3399
|
-
try {
|
|
3400
|
-
const refreshRes = await fetch("https://auth.qwen.ai/api/v2/auths/refresh", {
|
|
3401
|
-
method: "GET",
|
|
3402
|
-
credentials: "include",
|
|
3403
|
-
signal: AbortSignal.timeout(3e3)
|
|
3404
|
-
});
|
|
3405
|
-
if (refreshRes.status === 200) {
|
|
3406
|
-
const refreshJson = await refreshRes.json().catch(() => null);
|
|
3407
|
-
if (refreshJson && refreshJson.success === false) {
|
|
3408
|
-
const code = refreshJson.data?.code || refreshJson.code;
|
|
3409
|
-
const details = String(refreshJson.data?.details || "");
|
|
3410
|
-
if (code === "Unauthorized" || details.includes("revogado") || details.includes("revoked")) {
|
|
3411
|
-
return false;
|
|
3412
|
-
}
|
|
3413
|
-
}
|
|
3414
|
-
} else if (refreshRes.status === 401 || refreshRes.status === 403) {
|
|
3415
|
-
return false;
|
|
3416
|
-
}
|
|
3417
|
-
} catch {
|
|
3418
|
-
}
|
|
3419
3407
|
return true;
|
|
3420
3408
|
} catch {
|
|
3421
3409
|
return false;
|
|
@@ -4543,7 +4531,7 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4543
4531
|
`\u26A0\uFE0F [Playwright] Initial navigation check for ${maskEmail(account.email)}: ${err.message}`
|
|
4544
4532
|
);
|
|
4545
4533
|
}
|
|
4546
|
-
if (!options.skipHeaderCapture) {
|
|
4534
|
+
if (!options.skipHeaderCapture && !isAccountHeadersReady(account.id)) {
|
|
4547
4535
|
acctPage.__qwenChatHomeLoaded = true;
|
|
4548
4536
|
await captureQwenHeaders(account.id);
|
|
4549
4537
|
}
|
|
@@ -4873,7 +4861,7 @@ async function loginToQwen(accountId, email, password) {
|
|
|
4873
4861
|
);
|
|
4874
4862
|
markAccountRateLimited(
|
|
4875
4863
|
accountId,
|
|
4876
|
-
|
|
4864
|
+
300 * 1e3,
|
|
4877
4865
|
"AuthFailed: All login methods exhausted"
|
|
4878
4866
|
);
|
|
4879
4867
|
return false;
|
|
@@ -5150,38 +5138,49 @@ async function loginViaUi(page, email, password) {
|
|
|
5150
5138
|
try {
|
|
5151
5139
|
const emailEl = document.querySelector('input[name="email"], input[type="email"], input[type="text"]');
|
|
5152
5140
|
const passEl = document.querySelector('input[type="password"], input[name="password"]');
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5141
|
+
const setReactValue = (el, val) => {
|
|
5142
|
+
const proto = Object.getPrototypeOf(el);
|
|
5143
|
+
const setter = Object.getOwnPropertyDescriptor(proto, "value")?.set;
|
|
5144
|
+
if (setter) {
|
|
5145
|
+
setter.call(el, val);
|
|
5146
|
+
} else {
|
|
5147
|
+
el.value = val;
|
|
5148
|
+
}
|
|
5149
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
5150
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
5151
|
+
el.dispatchEvent(new Event("blur", { bubbles: true }));
|
|
5152
|
+
};
|
|
5153
|
+
if (emailEl) setReactValue(emailEl, emailEl.value);
|
|
5154
|
+
if (passEl) setReactValue(passEl, passEl.value);
|
|
5163
5155
|
} catch {
|
|
5164
5156
|
}
|
|
5165
5157
|
}).catch(() => {
|
|
5166
5158
|
});
|
|
5159
|
+
await sleep2(500);
|
|
5167
5160
|
const submitSelector = 'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in"), button:has-text("Fazer login"), button:has-text("Entrar"), button:has-text("Iniciar sesi\xF3n"), button:has-text("\u767B\u5F55")';
|
|
5168
5161
|
const submitButton = page.locator(submitSelector).first();
|
|
5162
|
+
await page.evaluate((sel) => {
|
|
5163
|
+
try {
|
|
5164
|
+
const btn = document.querySelector(sel);
|
|
5165
|
+
if (btn) {
|
|
5166
|
+
btn.removeAttribute("disabled");
|
|
5167
|
+
btn.classList.remove("ant-btn-disabled");
|
|
5168
|
+
btn.click();
|
|
5169
|
+
}
|
|
5170
|
+
} catch {
|
|
5171
|
+
}
|
|
5172
|
+
}, submitSelector).catch(() => {
|
|
5173
|
+
});
|
|
5169
5174
|
try {
|
|
5170
|
-
await
|
|
5171
|
-
timeout:
|
|
5172
|
-
});
|
|
5173
|
-
await submitButton.click();
|
|
5174
|
-
} catch {
|
|
5175
|
-
const isDisabled = await submitButton.isDisabled().catch(() => true);
|
|
5176
|
-
if (!isDisabled) {
|
|
5177
|
-
await submitButton.click({ force: true }).catch(() => {
|
|
5175
|
+
if (await submitButton.isVisible({ timeout: 2e3 }).catch(() => false)) {
|
|
5176
|
+
await submitButton.click({ force: true, timeout: 2e3 }).catch(() => {
|
|
5178
5177
|
});
|
|
5179
|
-
} else {
|
|
5180
|
-
if (!page.url().includes("/auth") && await isPageLoggedIn(page, 2e3)) {
|
|
5181
|
-
return { success: true };
|
|
5182
|
-
}
|
|
5183
5178
|
}
|
|
5179
|
+
} catch {
|
|
5184
5180
|
}
|
|
5181
|
+
const passInput = page.locator(passwordSelector).first();
|
|
5182
|
+
await passInput.press("Enter", { timeout: 2e3 }).catch(() => {
|
|
5183
|
+
});
|
|
5185
5184
|
const postSubmitDeadline = Date.now() + 15e3;
|
|
5186
5185
|
while (Date.now() < postSubmitDeadline) {
|
|
5187
5186
|
if (page.isClosed()) break;
|
package/dist/reset-cooldowns.js
CHANGED
|
@@ -1110,7 +1110,15 @@ function defaultCooldownDurationMs() {
|
|
|
1110
1110
|
return computeQuotaCooldownMs(Date.now());
|
|
1111
1111
|
}
|
|
1112
1112
|
function markAccountRateLimited(accountId, cooldownMs, reason, options = {}) {
|
|
1113
|
+
if (cooldownMs !== void 0 && cooldownMs <= 0) {
|
|
1114
|
+
clearAccountCooldown(accountId);
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1113
1117
|
const duration = cooldownMs ?? defaultCooldownDurationMs();
|
|
1118
|
+
if (duration <= 0) {
|
|
1119
|
+
clearAccountCooldown(accountId);
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1114
1122
|
const until = Date.now() + duration;
|
|
1115
1123
|
const cooldownReason = reason ?? "RateLimited";
|
|
1116
1124
|
cooldowns.set(accountId, {
|
|
@@ -1166,6 +1174,9 @@ function unmarkAccountHeadersReady(accountId) {
|
|
|
1166
1174
|
if (!accountId) return;
|
|
1167
1175
|
headersReadyAccounts.delete(accountId);
|
|
1168
1176
|
}
|
|
1177
|
+
function isAccountHeadersReady(accountId) {
|
|
1178
|
+
return headersReadyAccounts.has(accountId);
|
|
1179
|
+
}
|
|
1169
1180
|
var cooldowns, headersReadyAccounts;
|
|
1170
1181
|
var init_account_manager = __esm({
|
|
1171
1182
|
"src/core/account-manager.ts"() {
|
|
@@ -3113,26 +3124,6 @@ async function isPageLoggedIn(page, timeoutMs = SESSION_PROBE_NAVIGATION_TIMEOUT
|
|
|
3113
3124
|
}
|
|
3114
3125
|
} catch {
|
|
3115
3126
|
}
|
|
3116
|
-
try {
|
|
3117
|
-
const refreshRes = await fetch("https://auth.qwen.ai/api/v2/auths/refresh", {
|
|
3118
|
-
method: "GET",
|
|
3119
|
-
credentials: "include",
|
|
3120
|
-
signal: AbortSignal.timeout(3e3)
|
|
3121
|
-
});
|
|
3122
|
-
if (refreshRes.status === 200) {
|
|
3123
|
-
const refreshJson = await refreshRes.json().catch(() => null);
|
|
3124
|
-
if (refreshJson && refreshJson.success === false) {
|
|
3125
|
-
const code = refreshJson.data?.code || refreshJson.code;
|
|
3126
|
-
const details = String(refreshJson.data?.details || "");
|
|
3127
|
-
if (code === "Unauthorized" || details.includes("revogado") || details.includes("revoked")) {
|
|
3128
|
-
return false;
|
|
3129
|
-
}
|
|
3130
|
-
}
|
|
3131
|
-
} else if (refreshRes.status === 401 || refreshRes.status === 403) {
|
|
3132
|
-
return false;
|
|
3133
|
-
}
|
|
3134
|
-
} catch {
|
|
3135
|
-
}
|
|
3136
3127
|
return true;
|
|
3137
3128
|
} catch {
|
|
3138
3129
|
return false;
|
|
@@ -3938,7 +3929,7 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
3938
3929
|
`\u26A0\uFE0F [Playwright] Initial navigation check for ${maskEmail(account.email)}: ${err.message}`
|
|
3939
3930
|
);
|
|
3940
3931
|
}
|
|
3941
|
-
if (!options.skipHeaderCapture) {
|
|
3932
|
+
if (!options.skipHeaderCapture && !isAccountHeadersReady(account.id)) {
|
|
3942
3933
|
acctPage.__qwenChatHomeLoaded = true;
|
|
3943
3934
|
await captureQwenHeaders(account.id);
|
|
3944
3935
|
}
|
|
@@ -4148,7 +4139,7 @@ async function loginToQwen(accountId, email, password) {
|
|
|
4148
4139
|
);
|
|
4149
4140
|
markAccountRateLimited(
|
|
4150
4141
|
accountId,
|
|
4151
|
-
|
|
4142
|
+
300 * 1e3,
|
|
4152
4143
|
"AuthFailed: All login methods exhausted"
|
|
4153
4144
|
);
|
|
4154
4145
|
return false;
|
|
@@ -4425,38 +4416,49 @@ async function loginViaUi(page, email, password) {
|
|
|
4425
4416
|
try {
|
|
4426
4417
|
const emailEl = document.querySelector('input[name="email"], input[type="email"], input[type="text"]');
|
|
4427
4418
|
const passEl = document.querySelector('input[type="password"], input[name="password"]');
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4419
|
+
const setReactValue = (el, val) => {
|
|
4420
|
+
const proto = Object.getPrototypeOf(el);
|
|
4421
|
+
const setter = Object.getOwnPropertyDescriptor(proto, "value")?.set;
|
|
4422
|
+
if (setter) {
|
|
4423
|
+
setter.call(el, val);
|
|
4424
|
+
} else {
|
|
4425
|
+
el.value = val;
|
|
4426
|
+
}
|
|
4427
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
4428
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
4429
|
+
el.dispatchEvent(new Event("blur", { bubbles: true }));
|
|
4430
|
+
};
|
|
4431
|
+
if (emailEl) setReactValue(emailEl, emailEl.value);
|
|
4432
|
+
if (passEl) setReactValue(passEl, passEl.value);
|
|
4438
4433
|
} catch {
|
|
4439
4434
|
}
|
|
4440
4435
|
}).catch(() => {
|
|
4441
4436
|
});
|
|
4437
|
+
await sleep2(500);
|
|
4442
4438
|
const submitSelector = 'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in"), button:has-text("Fazer login"), button:has-text("Entrar"), button:has-text("Iniciar sesi\xF3n"), button:has-text("\u767B\u5F55")';
|
|
4443
4439
|
const submitButton = page.locator(submitSelector).first();
|
|
4440
|
+
await page.evaluate((sel) => {
|
|
4441
|
+
try {
|
|
4442
|
+
const btn = document.querySelector(sel);
|
|
4443
|
+
if (btn) {
|
|
4444
|
+
btn.removeAttribute("disabled");
|
|
4445
|
+
btn.classList.remove("ant-btn-disabled");
|
|
4446
|
+
btn.click();
|
|
4447
|
+
}
|
|
4448
|
+
} catch {
|
|
4449
|
+
}
|
|
4450
|
+
}, submitSelector).catch(() => {
|
|
4451
|
+
});
|
|
4444
4452
|
try {
|
|
4445
|
-
await
|
|
4446
|
-
timeout:
|
|
4447
|
-
});
|
|
4448
|
-
await submitButton.click();
|
|
4449
|
-
} catch {
|
|
4450
|
-
const isDisabled = await submitButton.isDisabled().catch(() => true);
|
|
4451
|
-
if (!isDisabled) {
|
|
4452
|
-
await submitButton.click({ force: true }).catch(() => {
|
|
4453
|
+
if (await submitButton.isVisible({ timeout: 2e3 }).catch(() => false)) {
|
|
4454
|
+
await submitButton.click({ force: true, timeout: 2e3 }).catch(() => {
|
|
4453
4455
|
});
|
|
4454
|
-
} else {
|
|
4455
|
-
if (!page.url().includes("/auth") && await isPageLoggedIn(page, 2e3)) {
|
|
4456
|
-
return { success: true };
|
|
4457
|
-
}
|
|
4458
4456
|
}
|
|
4457
|
+
} catch {
|
|
4459
4458
|
}
|
|
4459
|
+
const passInput = page.locator(passwordSelector).first();
|
|
4460
|
+
await passInput.press("Enter", { timeout: 2e3 }).catch(() => {
|
|
4461
|
+
});
|
|
4460
4462
|
const postSubmitDeadline = Date.now() + 15e3;
|
|
4461
4463
|
while (Date.now() < postSubmitDeadline) {
|
|
4462
4464
|
if (page.isClosed()) break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
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": {
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"reset": "npx tsx src/reset-cooldowns.ts",
|
|
30
30
|
"purge": "npx tsx src/delete-chats.ts",
|
|
31
31
|
"test": "npm run test:mock && npm run test:live",
|
|
32
|
-
"test:mock": "tsx --test --test-concurrency=1 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/auth-session-persistence.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/local-auth.test.ts src/tests/safe-remote-url.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/sync-top10-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/tui-settings.test.ts src/tests/personalization-deadline.test.ts src/tests/update-cli.test.ts src/tests/batch-accounts.test.ts src/tests/shared-browser.test.ts src/tests/auth-credential-classification.test.ts src/tests/responses-api.test.ts src/tests/media-generation.test.ts src/tests/infer-tool-name.test.ts src/tests/native-session-headers.test.ts src/tests/omni-thinking.test.ts src/tests/protect-stuck-mutex.test.ts src/tests/stream-error-notice.test.ts src/tests/parser-deep-coverage.test.ts src/tests/tui-logs-copy.test.ts src/tests/auth-jwt-and-validation.test.ts src/tests/tui-port-and-key-modal.test.ts",
|
|
33
|
-
"test:mock:fast": "tsx --test --test-concurrency=2 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/auth-session-persistence.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/local-auth.test.ts src/tests/safe-remote-url.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/sync-top10-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/tui-settings.test.ts src/tests/personalization-deadline.test.ts src/tests/update-cli.test.ts src/tests/batch-accounts.test.ts src/tests/shared-browser.test.ts src/tests/auth-credential-classification.test.ts src/tests/responses-api.test.ts src/tests/media-generation.test.ts src/tests/infer-tool-name.test.ts src/tests/native-session-headers.test.ts src/tests/omni-thinking.test.ts src/tests/protect-stuck-mutex.test.ts src/tests/stream-error-notice.test.ts src/tests/parser-deep-coverage.test.ts src/tests/tui-logs-copy.test.ts src/tests/auth-jwt-and-validation.test.ts src/tests/tui-port-and-key-modal.test.ts",
|
|
32
|
+
"test:mock": "tsx --test --test-concurrency=1 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/auth-session-persistence.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/local-auth.test.ts src/tests/safe-remote-url.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/sync-top10-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/tui-settings.test.ts src/tests/personalization-deadline.test.ts src/tests/update-cli.test.ts src/tests/batch-accounts.test.ts src/tests/shared-browser.test.ts src/tests/auth-credential-classification.test.ts src/tests/responses-api.test.ts src/tests/media-generation.test.ts src/tests/infer-tool-name.test.ts src/tests/native-session-headers.test.ts src/tests/omni-thinking.test.ts src/tests/protect-stuck-mutex.test.ts src/tests/stream-error-notice.test.ts src/tests/parser-deep-coverage.test.ts src/tests/tui-logs-copy.test.ts src/tests/auth-jwt-and-validation.test.ts src/tests/tui-port-and-key-modal.test.ts src/tests/verify-zero-fake-cooldown.test.ts",
|
|
33
|
+
"test:mock:fast": "tsx --test --test-concurrency=2 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/auth-session-persistence.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/local-auth.test.ts src/tests/safe-remote-url.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/sync-top10-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/tui-settings.test.ts src/tests/personalization-deadline.test.ts src/tests/update-cli.test.ts src/tests/batch-accounts.test.ts src/tests/shared-browser.test.ts src/tests/auth-credential-classification.test.ts src/tests/responses-api.test.ts src/tests/media-generation.test.ts src/tests/infer-tool-name.test.ts src/tests/native-session-headers.test.ts src/tests/omni-thinking.test.ts src/tests/protect-stuck-mutex.test.ts src/tests/stream-error-notice.test.ts src/tests/parser-deep-coverage.test.ts src/tests/tui-logs-copy.test.ts src/tests/auth-jwt-and-validation.test.ts src/tests/tui-port-and-key-modal.test.ts src/tests/verify-zero-fake-cooldown.test.ts",
|
|
34
34
|
"test:live": "tsx --test src/tests/concurrentChat.test.ts src/tests/agenticStress.test.ts",
|
|
35
35
|
"test:stress": "npx tsx src/benchmarks/stress-test.ts",
|
|
36
36
|
"test:stress:personalization": "npx tsx src/benchmarks/stress-test-personalization.ts",
|
|
@@ -50,7 +50,16 @@ export function markAccountRateLimited(
|
|
|
50
50
|
reason?: string,
|
|
51
51
|
options: { silent?: boolean } = {},
|
|
52
52
|
): void {
|
|
53
|
+
// If cooldown is explicitly 0 or negative, do not park the account.
|
|
54
|
+
if (cooldownMs !== undefined && cooldownMs <= 0) {
|
|
55
|
+
clearAccountCooldown(accountId);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
53
58
|
const duration = cooldownMs ?? defaultCooldownDurationMs();
|
|
59
|
+
if (duration <= 0) {
|
|
60
|
+
clearAccountCooldown(accountId);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
54
63
|
const until = Date.now() + duration;
|
|
55
64
|
const cooldownReason = reason ?? "RateLimited";
|
|
56
65
|
|
|
@@ -11,6 +11,7 @@ export interface ServerLogMessage {
|
|
|
11
11
|
time: string;
|
|
12
12
|
level: "INFO" | "WARN" | "ERROR";
|
|
13
13
|
message: string;
|
|
14
|
+
_ts?: number;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
const MAX_LOG_HISTORY = 2000;
|
|
@@ -80,15 +81,21 @@ export function recordServerLog(level: "INFO" | "WARN" | "ERROR", text: string):
|
|
|
80
81
|
.replace(/([\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{2300}-\u{23FF}]\uFE0F?)\s{2,}/gu, "$1 ");
|
|
81
82
|
if (!line) continue;
|
|
82
83
|
|
|
83
|
-
// Prevent identical duplicate logs within the same second window
|
|
84
|
+
// Prevent identical duplicate logs within the same second or 1.5s window
|
|
85
|
+
const now = Date.now();
|
|
84
86
|
const isDuplicate = logHistory
|
|
85
87
|
.slice(-10)
|
|
86
|
-
.some(
|
|
88
|
+
.some(
|
|
89
|
+
(entry) =>
|
|
90
|
+
(entry.time === time || (entry._ts && now - entry._ts < 1500)) &&
|
|
91
|
+
entry.level === level &&
|
|
92
|
+
entry.message === line,
|
|
93
|
+
);
|
|
87
94
|
if (isDuplicate) {
|
|
88
95
|
continue;
|
|
89
96
|
}
|
|
90
97
|
|
|
91
|
-
const entry: ServerLogMessage = { time, level, message: line };
|
|
98
|
+
const entry: ServerLogMessage = { time, level, message: line, _ts: now };
|
|
92
99
|
logHistory.push(entry);
|
|
93
100
|
if (logHistory.length > MAX_LOG_HISTORY) {
|
|
94
101
|
logHistory.shift();
|
package/src/core/version.ts
CHANGED
|
@@ -1681,15 +1681,27 @@ async function tryCreateStreamWithRetry(
|
|
|
1681
1681
|
}
|
|
1682
1682
|
|
|
1683
1683
|
if (policy.reason === "account_initialization_failed") {
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
currentAccountId
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1684
|
+
const cooldownMs = policy.accountCooldownMs ?? 0;
|
|
1685
|
+
if (cooldownMs > 0) {
|
|
1686
|
+
console.warn(
|
|
1687
|
+
`⚠️ [Chat] Account initialization failed | ${currentAccountEmail} | cooldown=${Math.round(cooldownMs / 1000)}s`,
|
|
1688
|
+
);
|
|
1689
|
+
markAccountFailed(currentAccountId);
|
|
1690
|
+
markAccountRateLimited(
|
|
1691
|
+
currentAccountId,
|
|
1692
|
+
cooldownMs,
|
|
1693
|
+
policy.accountCooldownReason,
|
|
1694
|
+
);
|
|
1695
|
+
} else {
|
|
1696
|
+
console.warn(
|
|
1697
|
+
`⚠️ [Chat] Account auth expired/retryable | ${currentAccountEmail} | rotating without cooldown`,
|
|
1698
|
+
);
|
|
1699
|
+
if (policy.accountCooldownReason === "AuthExpired") {
|
|
1700
|
+
import("../../services/playwright.ts")
|
|
1701
|
+
.then(({ refreshAccountToken }) => refreshAccountToken(currentAccountId))
|
|
1702
|
+
.catch(() => {});
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1693
1705
|
return { success: false, error: err };
|
|
1694
1706
|
}
|
|
1695
1707
|
|
|
@@ -1712,7 +1724,7 @@ async function tryCreateStreamWithRetry(
|
|
|
1712
1724
|
console.warn(
|
|
1713
1725
|
`🔄 [Chat] Switching account after ${policy.reason} | ${currentAccountEmail} -> ${maskEmail(nextAccount.email)}`,
|
|
1714
1726
|
);
|
|
1715
|
-
if (policy.accountCooldownMs
|
|
1727
|
+
if ((policy.accountCooldownMs ?? 0) > 0) {
|
|
1716
1728
|
markAccountRateLimited(
|
|
1717
1729
|
currentAccountId,
|
|
1718
1730
|
policy.accountCooldownMs,
|
|
@@ -113,13 +113,41 @@ export function formatThinkingSummaryContent(delta: any): string {
|
|
|
113
113
|
return sections.join("\n\n");
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Verified answer/content phases from Qwen Web:
|
|
118
|
+
* - "answer": standard text completions
|
|
119
|
+
* - "deep_research_answer", "ReportGeneration", "PdfMdGen": deep research & search modes
|
|
120
|
+
* - "image", "image_generation", "image_gen", "t2i": multimodal/image phases
|
|
121
|
+
*/
|
|
122
|
+
export const ANSWER_PHASES = new Set([
|
|
123
|
+
"answer",
|
|
124
|
+
"deep_research_answer",
|
|
125
|
+
"ReportGeneration",
|
|
126
|
+
"PdfMdGen",
|
|
127
|
+
"image",
|
|
128
|
+
"image_generation",
|
|
129
|
+
"image_gen",
|
|
130
|
+
"t2i",
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
export function isAnswerPhase(phase: unknown): boolean {
|
|
134
|
+
return typeof phase === "string" && ANSWER_PHASES.has(phase);
|
|
135
|
+
}
|
|
136
|
+
|
|
116
137
|
/**
|
|
117
138
|
* Matches thinking phases verified in upstream Qwen Web HAR:
|
|
118
139
|
* - "thinking_summary": structured thinking in Qwen Max / Plus
|
|
119
140
|
* - "think": incremental thinking deltas in Qwen Omni
|
|
141
|
+
* - "DeepThinking": deep thinking models
|
|
120
142
|
*/
|
|
143
|
+
export const THINKING_PHASES = new Set([
|
|
144
|
+
"think",
|
|
145
|
+
"thinking_summary",
|
|
146
|
+
"DeepThinking",
|
|
147
|
+
]);
|
|
148
|
+
|
|
121
149
|
export function isThinkingPhase(phase: unknown): boolean {
|
|
122
|
-
return phase === "
|
|
150
|
+
return typeof phase === "string" && THINKING_PHASES.has(phase);
|
|
123
151
|
}
|
|
124
152
|
|
|
125
153
|
/**
|
|
@@ -486,14 +486,14 @@ export function classifyRetryAction(
|
|
|
486
486
|
switchAccount: true,
|
|
487
487
|
forceNewChat: true,
|
|
488
488
|
retryAfterMs: baseDelayMs,
|
|
489
|
-
accountCooldownMs:
|
|
489
|
+
accountCooldownMs: 0,
|
|
490
490
|
accountCooldownReason: "PersonalizationFailed",
|
|
491
491
|
});
|
|
492
492
|
}
|
|
493
493
|
|
|
494
494
|
// Upstream 401 / Unauthorized on chat creation or API requests:
|
|
495
|
-
//
|
|
496
|
-
//
|
|
495
|
+
// Access token expired (15m window). Rotate without locking into a 300s cooldown
|
|
496
|
+
// so the account can immediately be renewed via refresh_token.
|
|
497
497
|
if (
|
|
498
498
|
code === "createchatinvalidresponse" ||
|
|
499
499
|
code === "createchatfailed" ||
|
|
@@ -510,8 +510,8 @@ export function classifyRetryAction(
|
|
|
510
510
|
forceNewChat: true,
|
|
511
511
|
retryWithFullPrompt: true,
|
|
512
512
|
retryAfterMs: Math.min(baseDelayMs, 1_000),
|
|
513
|
-
accountCooldownMs:
|
|
514
|
-
accountCooldownReason: "
|
|
513
|
+
accountCooldownMs: 0,
|
|
514
|
+
accountCooldownReason: "AuthExpired",
|
|
515
515
|
});
|
|
516
516
|
}
|
|
517
517
|
// Specialized recoveries first (even if wrapped as RetryableQwenStreamError)
|
|
@@ -68,6 +68,7 @@ import {
|
|
|
68
68
|
import {
|
|
69
69
|
getIncrementalDelta,
|
|
70
70
|
isThinkingPhase,
|
|
71
|
+
isAnswerPhase,
|
|
71
72
|
extractThinkingContent,
|
|
72
73
|
formatThinkingSummaryContent,
|
|
73
74
|
shouldSuppressStreamAbort,
|
|
@@ -470,7 +471,7 @@ export async function processNonStreamingResponse(
|
|
|
470
471
|
foundStr = true;
|
|
471
472
|
}
|
|
472
473
|
}
|
|
473
|
-
} else if (delta.phase
|
|
474
|
+
} else if (isAnswerPhase(delta.phase)) {
|
|
474
475
|
isThinkingChunk = false;
|
|
475
476
|
if (delta.content !== undefined) {
|
|
476
477
|
const newContent = delta.content || "";
|
|
@@ -1436,7 +1437,7 @@ export async function processStreamingResponse(
|
|
|
1436
1437
|
|
|
1437
1438
|
if (
|
|
1438
1439
|
switchAccount &&
|
|
1439
|
-
(policy.accountCooldownMs
|
|
1440
|
+
(policy.accountCooldownMs ?? 0) > 0
|
|
1440
1441
|
) {
|
|
1441
1442
|
markAccountRateLimited(
|
|
1442
1443
|
currentAccountId,
|
|
@@ -1445,6 +1446,12 @@ export async function processStreamingResponse(
|
|
|
1445
1446
|
);
|
|
1446
1447
|
}
|
|
1447
1448
|
|
|
1449
|
+
if (policy.accountCooldownReason === "AuthExpired") {
|
|
1450
|
+
import("../../services/playwright.ts")
|
|
1451
|
+
.then(({ refreshAccountToken }) => refreshAccountToken(currentAccountId))
|
|
1452
|
+
.catch(() => {});
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1448
1455
|
retryContext.releaseAccountLease?.();
|
|
1449
1456
|
retryContext.releaseAccountLease = null;
|
|
1450
1457
|
await reader.cancel().catch(() => undefined);
|
|
@@ -1776,7 +1783,7 @@ export async function processStreamingResponse(
|
|
|
1776
1783
|
// "phase":"answer"} delta and NO trailing [DONE]. Treat it as
|
|
1777
1784
|
// the terminal event so we don't wait on the keep-alive
|
|
1778
1785
|
// connection to close (up to the 60s/10min idle timeout).
|
|
1779
|
-
if (delta.phase
|
|
1786
|
+
if (isAnswerPhase(delta.phase) && delta.status === "finished") {
|
|
1780
1787
|
upstreamDone = true;
|
|
1781
1788
|
if (!clientDisconnected) flushWrites();
|
|
1782
1789
|
break; // Exit the for loop; the while check leaves the read loop
|
|
@@ -1800,7 +1807,7 @@ export async function processStreamingResponse(
|
|
|
1800
1807
|
foundStr = true;
|
|
1801
1808
|
}
|
|
1802
1809
|
}
|
|
1803
|
-
} else if (delta.phase
|
|
1810
|
+
} else if (isAnswerPhase(delta.phase)) {
|
|
1804
1811
|
isThinkingChunk = false;
|
|
1805
1812
|
if (delta.content !== undefined) {
|
|
1806
1813
|
const newContent = delta.content || "";
|
|
@@ -2068,7 +2075,7 @@ export async function processStreamingResponse(
|
|
|
2068
2075
|
if (!toolParser) break;
|
|
2069
2076
|
const allToolsFailed =
|
|
2070
2077
|
toolParser.getMalformedToolCalls().length > 0 &&
|
|
2071
|
-
toolParser.
|
|
2078
|
+
toolParser.getSuccessfulToolCallCount() === 0;
|
|
2072
2079
|
if (!allToolsFailed) break;
|
|
2073
2080
|
|
|
2074
2081
|
const malformedCalls = toolParser.getMalformedToolCalls();
|
|
@@ -2290,7 +2297,7 @@ export async function processStreamingResponse(
|
|
|
2290
2297
|
// The retry stream may also terminate with an answer-finished
|
|
2291
2298
|
// delta when upstream sends no [DONE]. Leave the read loop
|
|
2292
2299
|
// immediately so we don't stall on the keep-alive connection.
|
|
2293
|
-
if (delta.phase
|
|
2300
|
+
if (isAnswerPhase(delta.phase) && delta.status === "finished") {
|
|
2294
2301
|
upstreamDone = true;
|
|
2295
2302
|
break retryReadLoop;
|
|
2296
2303
|
}
|
package/src/routes/upload.ts
CHANGED
|
@@ -387,15 +387,18 @@ async function getSTSToken(
|
|
|
387
387
|
}
|
|
388
388
|
data = await response.json().catch(() => null);
|
|
389
389
|
}
|
|
390
|
+
const codeLower = (data?.data?.code || data?.code || "").toLowerCase();
|
|
391
|
+
const detailsLower = (typeof data?.data?.details === "string" ? data.data.details : typeof data?.message === "string" ? data.message : "").toLowerCase();
|
|
390
392
|
const is401 =
|
|
391
393
|
data?.success === false &&
|
|
392
|
-
(
|
|
393
|
-
(
|
|
394
|
-
|
|
394
|
+
(codeLower === "unauthorized" ||
|
|
395
|
+
detailsLower.includes("401") ||
|
|
396
|
+
detailsLower.includes("token has expired") ||
|
|
397
|
+
detailsLower.includes("unauthorized"));
|
|
395
398
|
|
|
396
399
|
if (is401) {
|
|
397
400
|
try {
|
|
398
|
-
const {
|
|
401
|
+
const { refreshAccountToken } = await import("../services/playwright.ts");
|
|
399
402
|
const { getBasicHeaders } = await import("../services/auth-playwright.ts");
|
|
400
403
|
const { loadAccounts } = await import("../core/accounts.ts");
|
|
401
404
|
const resolvedId = accountId ?? loadAccounts()[0]?.id;
|
|
@@ -403,16 +406,18 @@ async function getSTSToken(
|
|
|
403
406
|
console.warn(
|
|
404
407
|
`[Upload] STS token 401 — refreshing session with re-auth and retrying...`,
|
|
405
408
|
);
|
|
406
|
-
await
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
409
|
+
const refreshResult = await refreshAccountToken(resolvedId);
|
|
410
|
+
if (refreshResult.success) {
|
|
411
|
+
const fresh = await getBasicHeaders(resolvedId);
|
|
412
|
+
headers.cookie = fresh.cookie;
|
|
413
|
+
headers["user-agent"] = fresh.userAgent;
|
|
414
|
+
headers["bx-v"] = fresh.bxV;
|
|
415
|
+
if (fresh.bxUa) headers["bx-ua"] = fresh.bxUa;
|
|
416
|
+
if (fresh.bxUmidtoken) headers["bx-umidtoken"] = fresh.bxUmidtoken;
|
|
417
|
+
const retryRes = await doFetch(headers);
|
|
418
|
+
if (retryRes.ok) {
|
|
419
|
+
data = await retryRes.json().catch(() => null);
|
|
420
|
+
}
|
|
416
421
|
}
|
|
417
422
|
}
|
|
418
423
|
} catch (refreshErr) {
|