qwenproxy-cli 1.6.1 → 1.6.3
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 +77 -5
- package/dist/delete-chats.js +415 -12
- package/dist/index.js +721 -107
- package/dist/login.js +428 -13
- package/dist/reset-cooldowns.js +87 -5
- package/package.json +3 -3
- package/src/core/accounts.ts +1 -1
- package/src/core/version.ts +1 -1
- package/src/login.ts +90 -4
- package/src/routes/chat/account.ts +11 -4
- package/src/routes/chat/retry-policy.ts +60 -18
- package/src/routes/chat/streaming.ts +27 -0
- package/src/services/playwright.ts +436 -14
- package/src/services/qwen.ts +22 -6
- package/src/tui/views/accounts-view.ts +313 -96
- package/src/utils/overload-detector.ts +55 -0
package/dist/reset-cooldowns.js
CHANGED
|
@@ -14,6 +14,25 @@ var __export = (target, all) => {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
// src/core/paths.ts
|
|
17
|
+
var paths_exports = {};
|
|
18
|
+
__export(paths_exports, {
|
|
19
|
+
ensureDataDirs: () => ensureDataDirs,
|
|
20
|
+
getAccountPriorityPath: () => getAccountPriorityPath,
|
|
21
|
+
getAccountProfilePath: () => getAccountProfilePath,
|
|
22
|
+
getDataDir: () => getDataDir,
|
|
23
|
+
getDbDir: () => getDbDir,
|
|
24
|
+
getDbPath: () => getDbPath,
|
|
25
|
+
getEncryptionKeyPath: () => getEncryptionKeyPath,
|
|
26
|
+
getEnvFilePath: () => getEnvFilePath,
|
|
27
|
+
getLogsDir: () => getLogsDir,
|
|
28
|
+
getOsGlobalDataDir: () => getOsGlobalDataDir,
|
|
29
|
+
getProfilesDir: () => getProfilesDir,
|
|
30
|
+
getServerLogFilePath: () => getServerLogFilePath,
|
|
31
|
+
getSyncStatePath: () => getSyncStatePath,
|
|
32
|
+
getTuiSettingsPath: () => getTuiSettingsPath,
|
|
33
|
+
isRunningUnderNodeTest: () => isRunningUnderNodeTest,
|
|
34
|
+
resolveDataDir: () => resolveDataDir
|
|
35
|
+
});
|
|
17
36
|
import fs from "node:fs";
|
|
18
37
|
import path from "node:path";
|
|
19
38
|
import os from "node:os";
|
|
@@ -78,6 +97,12 @@ function getAccountProfilePath(accountId, customDataDir) {
|
|
|
78
97
|
function getAccountPriorityPath(customDataDir) {
|
|
79
98
|
return path.join(customDataDir || getDataDir(), "account-priority.json");
|
|
80
99
|
}
|
|
100
|
+
function getSyncStatePath(customDataDir) {
|
|
101
|
+
return path.join(customDataDir || getDataDir(), "sync-state.json");
|
|
102
|
+
}
|
|
103
|
+
function getTuiSettingsPath(customDataDir) {
|
|
104
|
+
return path.join(customDataDir || getDataDir(), "tui-settings.json");
|
|
105
|
+
}
|
|
81
106
|
function getEnvFilePath(customDataDir) {
|
|
82
107
|
if (isRunningUnderNodeTest()) {
|
|
83
108
|
return path.join(customDataDir || getDataDir(), ".env");
|
|
@@ -88,6 +113,26 @@ function getEnvFilePath(customDataDir) {
|
|
|
88
113
|
}
|
|
89
114
|
return path.join(customDataDir || getDataDir(), ".env");
|
|
90
115
|
}
|
|
116
|
+
function getLogsDir(customDataDir) {
|
|
117
|
+
return path.join(customDataDir || getDataDir(), "logs");
|
|
118
|
+
}
|
|
119
|
+
function getServerLogFilePath(customDataDir) {
|
|
120
|
+
return path.join(getLogsDir(customDataDir), "server.log");
|
|
121
|
+
}
|
|
122
|
+
function ensureDataDirs(targetDataDir) {
|
|
123
|
+
const root = targetDataDir || getDataDir();
|
|
124
|
+
const dbDir = getDbDir(root);
|
|
125
|
+
const profilesDir = getProfilesDir(root);
|
|
126
|
+
const logsDir = getLogsDir(root);
|
|
127
|
+
try {
|
|
128
|
+
if (!fs.existsSync(root)) fs.mkdirSync(root, { recursive: true });
|
|
129
|
+
if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
|
|
130
|
+
if (!fs.existsSync(profilesDir)) fs.mkdirSync(profilesDir, { recursive: true });
|
|
131
|
+
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.error(`[Paths] Error ensuring data directories at ${root}:`, err?.message || String(err));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
91
136
|
var init_paths = __esm({
|
|
92
137
|
"src/core/paths.ts"() {
|
|
93
138
|
"use strict";
|
|
@@ -781,7 +826,7 @@ function addAccountsBatch(entries) {
|
|
|
781
826
|
}
|
|
782
827
|
return { added, skipped, invalid };
|
|
783
828
|
}
|
|
784
|
-
function addAccount(email, password, id) {
|
|
829
|
+
function addAccount(email, password = "", id) {
|
|
785
830
|
if (!email || typeof email !== "string" || email.trim().length === 0) {
|
|
786
831
|
throw new Error("Email is required");
|
|
787
832
|
}
|
|
@@ -3226,6 +3271,10 @@ async function clearVisibleChallenge(page) {
|
|
|
3226
3271
|
"button[aria-label*='Close' i]",
|
|
3227
3272
|
"button[aria-label*='Fechar' i]",
|
|
3228
3273
|
".index-module__whats-new-modal-close",
|
|
3274
|
+
".age-confirmation-modal button",
|
|
3275
|
+
".age-confirmation-modal .qwen-chat-v2-btn-black",
|
|
3276
|
+
"button:has-text('Continuar')",
|
|
3277
|
+
"button:has-text('Continue')",
|
|
3229
3278
|
"button:has-text('Cancel')",
|
|
3230
3279
|
"button:has-text('Cancelar')",
|
|
3231
3280
|
"button:has-text('I understand')",
|
|
@@ -4729,7 +4778,24 @@ async function refreshAccountToken(accountId) {
|
|
|
4729
4778
|
return { success: false, error: "Could not refresh or re-authenticate token" };
|
|
4730
4779
|
}
|
|
4731
4780
|
async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.timeouts.headers, triggerGraceMs = HEADER_CAPTURE_TRIGGER_GRACE_MS) {
|
|
4732
|
-
|
|
4781
|
+
let page = pageOverride ?? accountPages.get(accountId);
|
|
4782
|
+
if (!page || page.isClosed()) {
|
|
4783
|
+
const { isRunningUnderNodeTest: isRunningUnderNodeTest3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
4784
|
+
if (!isRunningUnderNodeTest3()) {
|
|
4785
|
+
const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
4786
|
+
const creds = getAccountCredentials2(accountId);
|
|
4787
|
+
if (creds) {
|
|
4788
|
+
await initPlaywrightForAccount(
|
|
4789
|
+
creds,
|
|
4790
|
+
config.playwright.headless,
|
|
4791
|
+
config.playwright.browser,
|
|
4792
|
+
{ skipHeaderCapture: true }
|
|
4793
|
+
).catch(() => {
|
|
4794
|
+
});
|
|
4795
|
+
page = accountPages.get(accountId);
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4733
4799
|
if (!page || page.isClosed()) {
|
|
4734
4800
|
throw new Error(`Playwright page unavailable for header capture: ${accountId}`);
|
|
4735
4801
|
}
|
|
@@ -5430,8 +5496,10 @@ async function evictIdlePlaywrightContextsToLimit() {
|
|
|
5430
5496
|
if (accountPages.size <= max) return 0;
|
|
5431
5497
|
const candidates = priorityOrderForEviction(
|
|
5432
5498
|
Array.from(accountPages.keys()).filter((accountId) => {
|
|
5499
|
+
if (isAccountServingStream(accountId)) return false;
|
|
5500
|
+
if (isAccountRecentlyActive(accountId, 3e4)) return false;
|
|
5433
5501
|
const mutex = accountMutexes.get(accountId);
|
|
5434
|
-
return mutex?.isIdle()
|
|
5502
|
+
return mutex?.isIdle();
|
|
5435
5503
|
})
|
|
5436
5504
|
).map((accountId) => ({
|
|
5437
5505
|
accountId,
|
|
@@ -5544,6 +5612,10 @@ async function closePlaywrightForAccount(accountId) {
|
|
|
5544
5612
|
release();
|
|
5545
5613
|
}
|
|
5546
5614
|
}
|
|
5615
|
+
function isAccountRecentlyActive(accountId, maxIdleMs = 3e5) {
|
|
5616
|
+
const last = lastAccountActivity.get(accountId) ?? 0;
|
|
5617
|
+
return last > 0 && Date.now() - last < maxIdleMs;
|
|
5618
|
+
}
|
|
5547
5619
|
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;
|
|
5548
5620
|
var init_playwright = __esm({
|
|
5549
5621
|
"src/services/playwright.ts"() {
|
|
@@ -5585,8 +5657,8 @@ var init_playwright = __esm({
|
|
|
5585
5657
|
PLAYWRIGHT_MUTEX_WAIT_MS = 6e4;
|
|
5586
5658
|
ACCOUNT_PAGE_OPERATION_TIMEOUT_MS = config.timeouts.page;
|
|
5587
5659
|
SESSION_PROBE_NAVIGATION_TIMEOUT_MS = 15e3;
|
|
5588
|
-
HEADER_CAPTURE_TRIGGER_GRACE_MS =
|
|
5589
|
-
FIRST_TRIGGER_GRACE_MS =
|
|
5660
|
+
HEADER_CAPTURE_TRIGGER_GRACE_MS = 2e4;
|
|
5661
|
+
FIRST_TRIGGER_GRACE_MS = 12e3;
|
|
5590
5662
|
HEADER_CAPTURE_TRIGGER_ATTEMPTS = 3;
|
|
5591
5663
|
CHAT_INPUT_APPEAR_TIMEOUT_MS = 15e3;
|
|
5592
5664
|
CHAT_INPUT_ACTION_TIMEOUT_MS = 1e4;
|
|
@@ -5702,6 +5774,15 @@ var init_helpers = __esm({
|
|
|
5702
5774
|
}
|
|
5703
5775
|
});
|
|
5704
5776
|
|
|
5777
|
+
// src/utils/overload-detector.ts
|
|
5778
|
+
var OVERLOAD_COOLDOWN_MS;
|
|
5779
|
+
var init_overload_detector = __esm({
|
|
5780
|
+
"src/utils/overload-detector.ts"() {
|
|
5781
|
+
"use strict";
|
|
5782
|
+
OVERLOAD_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
5783
|
+
}
|
|
5784
|
+
});
|
|
5785
|
+
|
|
5705
5786
|
// src/routes/chat/retry-policy.ts
|
|
5706
5787
|
var init_retry_policy = __esm({
|
|
5707
5788
|
"src/routes/chat/retry-policy.ts"() {
|
|
@@ -5712,6 +5793,7 @@ var init_retry_policy = __esm({
|
|
|
5712
5793
|
init_qwen();
|
|
5713
5794
|
init_errors();
|
|
5714
5795
|
init_helpers();
|
|
5796
|
+
init_overload_detector();
|
|
5715
5797
|
init_account_manager();
|
|
5716
5798
|
}
|
|
5717
5799
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.3",
|
|
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 src/tests/verify-zero-fake-cooldown.test.ts src/tests/auth-session-refresh-preservation.test.ts src/tests/personalization-in-flight-dedup.test.ts src/tests/daily-usage.test.ts src/tests/resilience-enhancements.test.ts src/tests/pool-quota-failover.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 src/tests/auth-session-refresh-preservation.test.ts src/tests/personalization-in-flight-dedup.test.ts src/tests/daily-usage.test.ts src/tests/resilience-enhancements.test.ts src/tests/pool-quota-failover.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 src/tests/auth-session-refresh-preservation.test.ts src/tests/personalization-in-flight-dedup.test.ts src/tests/daily-usage.test.ts src/tests/resilience-enhancements.test.ts src/tests/pool-quota-failover.test.ts src/tests/process-guard-and-resilience.test.ts src/tests/otp-login.test.ts src/tests/overload-detector.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 src/tests/auth-session-refresh-preservation.test.ts src/tests/personalization-in-flight-dedup.test.ts src/tests/daily-usage.test.ts src/tests/resilience-enhancements.test.ts src/tests/pool-quota-failover.test.ts src/tests/process-guard-and-resilience.test.ts src/tests/otp-login.test.ts src/tests/overload-detector.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",
|
package/src/core/accounts.ts
CHANGED
package/src/core/version.ts
CHANGED
package/src/login.ts
CHANGED
|
@@ -50,7 +50,8 @@ async function showMenu() {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
console.log("\nOptions:");
|
|
53
|
-
console.log(" [A] Add account");
|
|
53
|
+
console.log(" [A] Add account (Password)");
|
|
54
|
+
console.log(" [O] Add account (Passwordless / Email OTP)");
|
|
54
55
|
if (accounts.length > 0) {
|
|
55
56
|
console.log(" [R] Remove an account");
|
|
56
57
|
}
|
|
@@ -64,7 +65,22 @@ async function showMenu() {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
if (choice === "A") {
|
|
67
|
-
|
|
68
|
+
clear();
|
|
69
|
+
console.log("=== Add New Account ===\n");
|
|
70
|
+
console.log("Authentication method:");
|
|
71
|
+
console.log(" [1] Traditional Password (Email + Password)");
|
|
72
|
+
console.log(" [2] Passwordless (Email Verification Code / OTP)\n");
|
|
73
|
+
const method = (await askQuestion("Select method [1/2] (default 1): ")).trim();
|
|
74
|
+
if (method === "2") {
|
|
75
|
+
await addAccountOtpFlow();
|
|
76
|
+
} else {
|
|
77
|
+
await addAccountPasswordFlow();
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (choice === "O") {
|
|
83
|
+
await addAccountOtpFlow();
|
|
68
84
|
continue;
|
|
69
85
|
}
|
|
70
86
|
|
|
@@ -75,9 +91,9 @@ async function showMenu() {
|
|
|
75
91
|
}
|
|
76
92
|
}
|
|
77
93
|
|
|
78
|
-
async function
|
|
94
|
+
async function addAccountPasswordFlow() {
|
|
79
95
|
clear();
|
|
80
|
-
console.log("=== Add New Account ===\n");
|
|
96
|
+
console.log("=== Add New Account (Password Login) ===\n");
|
|
81
97
|
const email = await askQuestion("Email: ");
|
|
82
98
|
if (!email || !email.trim()) {
|
|
83
99
|
console.log("Email is required.");
|
|
@@ -151,6 +167,76 @@ async function addAccountFlow() {
|
|
|
151
167
|
await askQuestion("\nPress Enter to continue...");
|
|
152
168
|
}
|
|
153
169
|
|
|
170
|
+
async function addAccountOtpFlow() {
|
|
171
|
+
clear();
|
|
172
|
+
console.log("=== Add New Account (Passwordless / Email OTP) ===\n");
|
|
173
|
+
const email = await askQuestion("Email: ");
|
|
174
|
+
if (!email || !email.trim()) {
|
|
175
|
+
console.log("Email is required.");
|
|
176
|
+
await askQuestion("Press Enter to continue...");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const trimmedEmail = email.trim();
|
|
181
|
+
const existingAccounts = listAccounts();
|
|
182
|
+
if (
|
|
183
|
+
existingAccounts.some(
|
|
184
|
+
(a) => a.email.toLowerCase() === trimmedEmail.toLowerCase(),
|
|
185
|
+
)
|
|
186
|
+
) {
|
|
187
|
+
console.log(
|
|
188
|
+
`\n❌ Error: An account with email "${trimmedEmail}" already exists.`,
|
|
189
|
+
);
|
|
190
|
+
await askQuestion("Press Enter to continue...");
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log(
|
|
195
|
+
`\n⏳ Requesting OTP verification code for ${maskEmail(trimmedEmail)}...`,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const { startEmailOtpLogin, verifyEmailOtpLogin, cancelEmailOtpLogin } =
|
|
199
|
+
await import("./services/playwright.ts");
|
|
200
|
+
|
|
201
|
+
const startRes = await startEmailOtpLogin(trimmedEmail, { headless: false });
|
|
202
|
+
if (!startRes.success || !startRes.sessionId) {
|
|
203
|
+
console.log(
|
|
204
|
+
`\n❌ Failed to request code: ${startRes.error || "Unknown error"}`,
|
|
205
|
+
);
|
|
206
|
+
await askQuestion("Press Enter to continue...");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log(`\n✉️ Verification code sent to ${trimmedEmail}!`);
|
|
211
|
+
console.log("Check your email inbox (and spam folder).\n");
|
|
212
|
+
|
|
213
|
+
while (true) {
|
|
214
|
+
const code = await askQuestion("Enter 6-digit code (or 'C' to cancel): ");
|
|
215
|
+
if (!code || !code.trim()) continue;
|
|
216
|
+
|
|
217
|
+
if (code.trim().toUpperCase() === "C") {
|
|
218
|
+
await cancelEmailOtpLogin(startRes.sessionId);
|
|
219
|
+
console.log("\nLogin cancelled.");
|
|
220
|
+
await askQuestion("Press Enter to continue...");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
console.log("\n⏳ Verifying code and establishing 30-day session...");
|
|
225
|
+
const verifyRes = await verifyEmailOtpLogin(startRes.sessionId, code.trim());
|
|
226
|
+
if (verifyRes.success && verifyRes.account) {
|
|
227
|
+
console.log(
|
|
228
|
+
`\n✅ Account verified and saved: ${maskEmail(verifyRes.account.email)} (${verifyRes.account.id})`,
|
|
229
|
+
);
|
|
230
|
+
console.log("Passwordless 30-day session active.");
|
|
231
|
+
await askQuestion("\nPress Enter to continue...");
|
|
232
|
+
return;
|
|
233
|
+
} else {
|
|
234
|
+
console.log(`\n❌ ${verifyRes.error || "Invalid code"}`);
|
|
235
|
+
console.log("Please try again or enter 'C' to cancel.\n");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
154
240
|
async function removeAccountFlow() {
|
|
155
241
|
const accounts = listAccounts();
|
|
156
242
|
if (accounts.length === 0) return;
|
|
@@ -82,8 +82,8 @@ const MAX_ANTI_BOT_ROTATIONS = 1;
|
|
|
82
82
|
* a stuck account page (closed context / WAF) can otherwise hold each browser
|
|
83
83
|
* op for 60s and keep the personalization mutex blocked for minutes.
|
|
84
84
|
*/
|
|
85
|
-
export const PERSONALIZATION_SYNC_DEADLINE_MS =
|
|
86
|
-
export const COLD_ACCOUNT_PERSONALIZATION_SYNC_DEADLINE_MS =
|
|
85
|
+
export const PERSONALIZATION_SYNC_DEADLINE_MS = 90_000;
|
|
86
|
+
export const COLD_ACCOUNT_PERSONALIZATION_SYNC_DEADLINE_MS = 120_000;
|
|
87
87
|
|
|
88
88
|
export function computePersonalizationDeadlineMs(
|
|
89
89
|
accountId: string | undefined,
|
|
@@ -1282,8 +1282,15 @@ async function tryCreateStreamWithRetry(
|
|
|
1282
1282
|
const { isCaptchaRecoveryActive } = await import(
|
|
1283
1283
|
"../../services/captcha-coordinator.ts"
|
|
1284
1284
|
);
|
|
1285
|
-
|
|
1286
|
-
|
|
1285
|
+
const { isPlaywrightInitializing } = await import(
|
|
1286
|
+
"../../services/playwright.ts"
|
|
1287
|
+
);
|
|
1288
|
+
if (
|
|
1289
|
+
currentAccountId &&
|
|
1290
|
+
(isCaptchaRecoveryActive(currentAccountId) ||
|
|
1291
|
+
isPlaywrightInitializing(currentAccountId))
|
|
1292
|
+
) {
|
|
1293
|
+
// Captcha solver is actively solving puzzle or browser is launching/initializing; extend deadline by 30s
|
|
1287
1294
|
acquireDeadlineTimer = setTimeout(checkDeadline, 30_000);
|
|
1288
1295
|
acquireDeadlineTimer.unref?.();
|
|
1289
1296
|
return;
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
ValidationError,
|
|
23
23
|
} from "../../core/errors.ts";
|
|
24
24
|
import { isAbortError } from "./helpers.ts";
|
|
25
|
+
import { isOverloadMessage, OVERLOAD_COOLDOWN_MS } from "../../utils/overload-detector.ts";
|
|
25
26
|
|
|
26
27
|
export type RetryAction = {
|
|
27
28
|
/** Outer/create-stream layer should retry this failure */
|
|
@@ -50,6 +51,9 @@ export type RetryableStreamError = RetryableQwenStreamError & {
|
|
|
50
51
|
retryWithFullPrompt?: boolean;
|
|
51
52
|
switchAccount?: boolean;
|
|
52
53
|
dropFiles?: boolean;
|
|
54
|
+
accountCooldownMs?: number;
|
|
55
|
+
accountCooldownReason?: string;
|
|
56
|
+
reason?: string;
|
|
53
57
|
};
|
|
54
58
|
|
|
55
59
|
function errMessage(err: unknown): string {
|
|
@@ -231,10 +235,11 @@ export function isAccountInitializationError(err: unknown): boolean {
|
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
export function isQuotaLikeError(err: unknown): boolean {
|
|
234
|
-
// Chat-not-exist / invalid attachment must never look like quota.
|
|
238
|
+
// Chat-not-exist / invalid attachment / explicit overload must never look like quota.
|
|
235
239
|
if (isChatNotExistError(err) || isInvalidInputError(err)) return false;
|
|
236
|
-
|
|
237
240
|
const code = errCode(err).toLowerCase();
|
|
241
|
+
if (code === "server_overloaded" || code === "overload") return false;
|
|
242
|
+
|
|
238
243
|
const message = errMessage(err).toLowerCase();
|
|
239
244
|
|
|
240
245
|
// Note: RetryableQwenStreamError inherits OpenAI-style code "rate_limit_exceeded".
|
|
@@ -424,6 +429,26 @@ export function isInternalServerError(err: unknown): boolean {
|
|
|
424
429
|
);
|
|
425
430
|
}
|
|
426
431
|
|
|
432
|
+
/**
|
|
433
|
+
* Detects in-band plain text overload messages from Qwen.
|
|
434
|
+
*/
|
|
435
|
+
export function isOverloadError(err: unknown): boolean {
|
|
436
|
+
const code = errCode(err).toLowerCase();
|
|
437
|
+
if (
|
|
438
|
+
code === "quota_limit" ||
|
|
439
|
+
code === "ratelimited" ||
|
|
440
|
+
code === "rate_limit" ||
|
|
441
|
+
code === "rate_limit_exceeded" ||
|
|
442
|
+
code === "membership_limit" ||
|
|
443
|
+
code === "internal_error" ||
|
|
444
|
+
code === "internal_server_error"
|
|
445
|
+
) {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
if (code === "server_overloaded" || code === "overload") return true;
|
|
449
|
+
return !code && isOverloadMessage(errMessage(err));
|
|
450
|
+
}
|
|
451
|
+
|
|
427
452
|
/**
|
|
428
453
|
* Build a RetryAction with sane defaults so each classification branch only
|
|
429
454
|
* spells out the fields it actually changes. Defaults: retryable, no account
|
|
@@ -474,6 +499,21 @@ export function classifyRetryAction(
|
|
|
474
499
|
return makeRetryAction("terminal_local", { retryable: false });
|
|
475
500
|
}
|
|
476
501
|
|
|
502
|
+
// Agent instructions ride ONLY the account-level personalization. An
|
|
503
|
+
// unconfirmed sync means this account cannot serve the request as-is —
|
|
504
|
+
// rotate to another account (each attempt re-syncs on its own account).
|
|
505
|
+
// Do NOT park the account with a 300s cooldown: rotation across accounts
|
|
506
|
+
// is already guarded by triedAccounts, preventing pool collapse.
|
|
507
|
+
if (err instanceof PersonalizationSyncError) {
|
|
508
|
+
return makeRetryAction("personalization_sync_failed", {
|
|
509
|
+
switchAccount: true,
|
|
510
|
+
forceNewChat: true,
|
|
511
|
+
retryAfterMs: baseDelayMs,
|
|
512
|
+
accountCooldownMs: 0,
|
|
513
|
+
accountCooldownReason: "PersonalizationFailed",
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
477
517
|
const message = errMessage(err).toLowerCase();
|
|
478
518
|
const code = errCode(err).toLowerCase();
|
|
479
519
|
if (isAccountInitializationError(err)) {
|
|
@@ -496,21 +536,6 @@ export function classifyRetryAction(
|
|
|
496
536
|
});
|
|
497
537
|
}
|
|
498
538
|
|
|
499
|
-
// Agent instructions ride ONLY the account-level personalization. An
|
|
500
|
-
// unconfirmed sync means this account cannot serve the request as-is —
|
|
501
|
-
// rotate to another account (each attempt re-syncs on its own account).
|
|
502
|
-
// Do NOT park the account with a 300s cooldown: rotation across accounts
|
|
503
|
-
// is already guarded by triedAccounts, preventing pool collapse.
|
|
504
|
-
if (err instanceof PersonalizationSyncError) {
|
|
505
|
-
return makeRetryAction("personalization_sync_failed", {
|
|
506
|
-
switchAccount: true,
|
|
507
|
-
forceNewChat: true,
|
|
508
|
-
retryAfterMs: baseDelayMs,
|
|
509
|
-
accountCooldownMs: 0,
|
|
510
|
-
accountCooldownReason: "PersonalizationFailed",
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
|
|
514
539
|
// Upstream 401 / Unauthorized on chat creation or API requests:
|
|
515
540
|
// Access token expired (15m window). Rotate without locking into a 300s cooldown
|
|
516
541
|
// so the account can immediately be renewed via refresh_token.
|
|
@@ -610,6 +635,18 @@ export function classifyRetryAction(
|
|
|
610
635
|
});
|
|
611
636
|
}
|
|
612
637
|
|
|
638
|
+
if (isOverloadError(err)) {
|
|
639
|
+
const typed = err as RetryableStreamError;
|
|
640
|
+
return makeRetryAction("server_busy", {
|
|
641
|
+
switchAccount: true,
|
|
642
|
+
forceNewChat: true,
|
|
643
|
+
retryWithFullPrompt: true,
|
|
644
|
+
retryAfterMs: typed.retryAfterMs ?? 3_000,
|
|
645
|
+
accountCooldownMs: OVERLOAD_COOLDOWN_MS,
|
|
646
|
+
accountCooldownReason: "ServerOverloaded",
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
613
650
|
if (isInternalServerError(err) && !(err instanceof QwenUpstreamError)) {
|
|
614
651
|
const typed = err as RetryableStreamError;
|
|
615
652
|
return makeRetryAction("upstream_internal_error", {
|
|
@@ -654,11 +691,13 @@ export function classifyRetryAction(
|
|
|
654
691
|
if (err instanceof RetryableQwenStreamError) {
|
|
655
692
|
const typed = err as RetryableStreamError;
|
|
656
693
|
// Default switch unless caller explicitly set switchAccount=false
|
|
657
|
-
return makeRetryAction("explicit_retryable", {
|
|
694
|
+
return makeRetryAction(typed.reason || "explicit_retryable", {
|
|
658
695
|
switchAccount: typed.switchAccount !== false,
|
|
659
696
|
forceNewChat: typed.forceNewChat === true,
|
|
660
697
|
retryWithFullPrompt: typed.retryWithFullPrompt === true,
|
|
661
698
|
retryAfterMs: typed.retryAfterMs ?? baseDelayMs,
|
|
699
|
+
accountCooldownMs: typed.accountCooldownMs,
|
|
700
|
+
accountCooldownReason: typed.accountCooldownReason,
|
|
662
701
|
});
|
|
663
702
|
}
|
|
664
703
|
|
|
@@ -702,6 +741,9 @@ export function toRetryableStreamError(
|
|
|
702
741
|
error.retryWithFullPrompt = merged.retryWithFullPrompt;
|
|
703
742
|
error.switchAccount = merged.switchAccount;
|
|
704
743
|
error.dropFiles = merged.dropFiles;
|
|
744
|
+
error.accountCooldownMs = merged.accountCooldownMs;
|
|
745
|
+
error.accountCooldownReason = merged.accountCooldownReason;
|
|
746
|
+
error.reason = merged.reason;
|
|
705
747
|
return error;
|
|
706
748
|
}
|
|
707
749
|
|
|
@@ -79,6 +79,7 @@ import {
|
|
|
79
79
|
applyUpstreamUsage,
|
|
80
80
|
buildUsage,
|
|
81
81
|
} from "./helpers.ts";
|
|
82
|
+
import { isOverloadMessage, OVERLOAD_COOLDOWN_MS } from "../../utils/overload-detector.ts";
|
|
82
83
|
|
|
83
84
|
function firstString(...values: unknown[]): string | null {
|
|
84
85
|
for (const value of values) {
|
|
@@ -477,6 +478,19 @@ export async function processNonStreamingResponse(
|
|
|
477
478
|
isThinkingChunk = false;
|
|
478
479
|
if (delta.content !== undefined) {
|
|
479
480
|
const newContent = delta.content || "";
|
|
481
|
+
if (isOverloadMessage(newContent)) {
|
|
482
|
+
throw toRetryableStreamError(
|
|
483
|
+
"server_overloaded",
|
|
484
|
+
newContent,
|
|
485
|
+
{
|
|
486
|
+
switchAccount: true,
|
|
487
|
+
forceNewChat: true,
|
|
488
|
+
reason: "server_busy",
|
|
489
|
+
accountCooldownMs: OVERLOAD_COOLDOWN_MS,
|
|
490
|
+
accountCooldownReason: "ServerOverloaded",
|
|
491
|
+
},
|
|
492
|
+
);
|
|
493
|
+
}
|
|
480
494
|
const result = getIncrementalDelta(
|
|
481
495
|
lastRawContent,
|
|
482
496
|
newContent,
|
|
@@ -1875,6 +1889,19 @@ export async function processStreamingResponse(
|
|
|
1875
1889
|
isThinkingChunk = false;
|
|
1876
1890
|
if (delta.content !== undefined) {
|
|
1877
1891
|
const newContent = delta.content || "";
|
|
1892
|
+
if (!emittedModelOutput && isOverloadMessage(newContent)) {
|
|
1893
|
+
throw toRetryableStreamError(
|
|
1894
|
+
"server_overloaded",
|
|
1895
|
+
newContent,
|
|
1896
|
+
{
|
|
1897
|
+
switchAccount: true,
|
|
1898
|
+
forceNewChat: true,
|
|
1899
|
+
reason: "server_busy",
|
|
1900
|
+
accountCooldownMs: OVERLOAD_COOLDOWN_MS,
|
|
1901
|
+
accountCooldownReason: "ServerOverloaded",
|
|
1902
|
+
},
|
|
1903
|
+
);
|
|
1904
|
+
}
|
|
1878
1905
|
const result = getIncrementalDelta(
|
|
1879
1906
|
lastRawContent,
|
|
1880
1907
|
newContent,
|