qwenproxy-cli 1.3.5 → 1.4.0
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/bin/qwenproxy.js +26 -8
- package/dist/clean-cache.js +5076 -0
- package/dist/delete-chats.js +9859 -0
- package/dist/index.js +33946 -0
- package/dist/login.js +6421 -0
- package/dist/reset-cooldowns.js +5462 -0
- package/dist/sync-clients.js +2824 -0
- package/package.json +7 -3
- package/src/api/server.ts +27 -10
- package/src/core/accounts.ts +2 -1
- package/src/core/config.ts +1 -1
- package/src/core/database.ts +139 -0
- package/src/core/paths.ts +0 -6
- package/src/core/version.ts +22 -0
- package/src/routes/chat/retry-policy.ts +4 -0
- package/src/routes/upload.ts +31 -10
- package/src/services/playwright.ts +403 -117
- package/src/services/qwen-chat-pool.ts +26 -0
- package/src/services/qwen-headers.ts +8 -0
- package/src/services/qwen.ts +105 -37
- package/src/tools/parser.ts +7 -1
- package/src/tui/app.ts +2 -12
- package/src/tui/server-manager.ts +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
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": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
|
+
"dist",
|
|
11
12
|
"src",
|
|
12
13
|
"!src/tests",
|
|
13
14
|
"!src/benchmarks",
|
|
@@ -15,6 +16,8 @@
|
|
|
15
16
|
"LICENSE"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
19
|
+
"build": "esbuild src/index.ts src/sync-clients.ts src/clean-cache.ts src/reset-cooldowns.ts src/delete-chats.ts src/login.ts --bundle --platform=node --format=esm --target=node22 --packages=external --outdir=dist",
|
|
20
|
+
"prepack": "npm run build",
|
|
18
21
|
"start": "tsx src/index.ts",
|
|
19
22
|
"tui": "npx tsx src/tui/index.ts",
|
|
20
23
|
"login": "npx tsx src/login.ts",
|
|
@@ -26,8 +29,8 @@
|
|
|
26
29
|
"reset": "npx tsx src/reset-cooldowns.ts",
|
|
27
30
|
"purge": "npx tsx src/delete-chats.ts",
|
|
28
31
|
"test": "npm run test:mock && npm run test:live",
|
|
29
|
-
"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/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",
|
|
30
|
-
"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/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",
|
|
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",
|
|
31
34
|
"test:live": "tsx --test src/tests/concurrentChat.test.ts src/tests/agenticStress.test.ts",
|
|
32
35
|
"benchmark:proxy": "npx tsx src/benchmarks/proxy-baseline.ts",
|
|
33
36
|
"typecheck": "tsc --noEmit"
|
|
@@ -69,6 +72,7 @@
|
|
|
69
72
|
"devDependencies": {
|
|
70
73
|
"@types/better-sqlite3": "^9.6.0",
|
|
71
74
|
"@types/node": "^26.4.1",
|
|
75
|
+
"esbuild": "^0.28.2",
|
|
72
76
|
"typescript": "^7.0.2"
|
|
73
77
|
},
|
|
74
78
|
"type": "module",
|
package/src/api/server.ts
CHANGED
|
@@ -498,6 +498,7 @@ async function warmConfiguredChatPools(
|
|
|
498
498
|
) => Promise<void>,
|
|
499
499
|
accountId?: string,
|
|
500
500
|
): Promise<void> {
|
|
501
|
+
if (config.qwen.chatPoolSize <= 0) return;
|
|
501
502
|
await Promise.all(
|
|
502
503
|
config.qwen.chatPoolModels.map((model) =>
|
|
503
504
|
warmQwenChatPool(accountId, model).catch(() => {}),
|
|
@@ -763,6 +764,12 @@ export async function startServer(options?: {
|
|
|
763
764
|
}
|
|
764
765
|
|
|
765
766
|
startPromise = (async () => {
|
|
767
|
+
const { getAppVersion } = await import("../core/version.ts");
|
|
768
|
+
const appVersion = getAppVersion();
|
|
769
|
+
if (options?.showBanner !== false && !isAuthMockEnabled()) {
|
|
770
|
+
console.log(`🚀 [Server] Iniciando QwenProxy ${appVersion} na porta ${config.server.port}...`);
|
|
771
|
+
}
|
|
772
|
+
|
|
766
773
|
cache = new MemoryCache();
|
|
767
774
|
await cache.connect();
|
|
768
775
|
|
|
@@ -805,13 +812,19 @@ export async function startServer(options?: {
|
|
|
805
812
|
if (accounts.length > 0) {
|
|
806
813
|
const totalAccounts = accounts.length;
|
|
807
814
|
|
|
808
|
-
// Warm accounts in priority order
|
|
815
|
+
// Warm accounts in priority order, giving top precedence to accounts
|
|
816
|
+
// that already have valid, non-expired sessions in SQLite (restores in ~0.5s),
|
|
809
817
|
// skipping accounts still on cooldown. Warm the primary account first so
|
|
810
|
-
// the server binds the port and goes online immediately (~
|
|
818
|
+
// the server binds the port and goes online immediately (~5-10s).
|
|
811
819
|
// Reserve account(s) and standby validations run seamlessly in background.
|
|
812
|
-
const
|
|
813
|
-
|
|
814
|
-
|
|
820
|
+
const { getValidAuthSession } = await import("../core/database.ts");
|
|
821
|
+
const warmOrder = getAccountsByPriority(accounts)
|
|
822
|
+
.filter((account) => !getAccountCooldownInfo(account.id))
|
|
823
|
+
.sort((a, b) => {
|
|
824
|
+
const aHasSession = getValidAuthSession(a.id) !== null ? 1 : 0;
|
|
825
|
+
const bHasSession = getValidAuthSession(b.id) !== null ? 1 : 0;
|
|
826
|
+
return bHasSession - aHasSession;
|
|
827
|
+
});
|
|
815
828
|
const readyAccountIds = new Set<string>();
|
|
816
829
|
|
|
817
830
|
for (let i = 0; i < warmOrder.length; i++) {
|
|
@@ -831,9 +844,13 @@ export async function startServer(options?: {
|
|
|
831
844
|
}
|
|
832
845
|
}
|
|
833
846
|
|
|
834
|
-
const remainingAccounts = accounts
|
|
835
|
-
(account) => !readyAccountIds.has(account.id)
|
|
836
|
-
|
|
847
|
+
const remainingAccounts = accounts
|
|
848
|
+
.filter((account) => !readyAccountIds.has(account.id))
|
|
849
|
+
.sort((a, b) => {
|
|
850
|
+
const aHasSession = getValidAuthSession(a.id) !== null ? 1 : 0;
|
|
851
|
+
const bHasSession = getValidAuthSession(b.id) !== null ? 1 : 0;
|
|
852
|
+
return bHasSession - aHasSession;
|
|
853
|
+
});
|
|
837
854
|
if (readyAccountIds.size === 0) {
|
|
838
855
|
console.warn(
|
|
839
856
|
`⚠️ [Server] No account ready during startup; continuing in background`,
|
|
@@ -1024,11 +1041,11 @@ export async function startServer(options?: {
|
|
|
1024
1041
|
|
|
1025
1042
|
const endpoint = `${started.url}/v1`;
|
|
1026
1043
|
|
|
1027
|
-
if (options?.showBanner !== false) {
|
|
1044
|
+
if (options?.showBanner !== false && !isAuthMockEnabled()) {
|
|
1028
1045
|
console.log(`
|
|
1029
1046
|
+${"-".repeat(W)}+
|
|
1030
1047
|
|${blank()}|
|
|
1031
|
-
|${center(
|
|
1048
|
+
|${center(`QwenProxy ${appVersion}`)}|
|
|
1032
1049
|
|${center("OpenAI & Anthropic Compatible API")}|
|
|
1033
1050
|
|${blank()}|
|
|
1034
1051
|
+${"-".repeat(W)}+
|
package/src/core/accounts.ts
CHANGED
|
@@ -2,7 +2,7 @@ import "dotenv/config";
|
|
|
2
2
|
import crypto from "crypto";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { getDatabase } from "./database.ts";
|
|
5
|
+
import { getDatabase, deleteAuthSession } from "./database.ts";
|
|
6
6
|
import { decrypt, encrypt } from "./crypto-utils.ts";
|
|
7
7
|
import { getAccountProfilePath, getProfilesDir, isRunningUnderNodeTest } from "./paths.ts";
|
|
8
8
|
import { updateEnvVariable } from "./local-auth.ts";
|
|
@@ -430,6 +430,7 @@ export function removeAccount(id: string): boolean {
|
|
|
430
430
|
const result = db.prepare("DELETE FROM accounts WHERE id = ?").run(id);
|
|
431
431
|
if (result.changes > 0) {
|
|
432
432
|
wipeAccountSessionFiles(id);
|
|
433
|
+
deleteAuthSession(id);
|
|
433
434
|
if (account?.email) {
|
|
434
435
|
removeAccountFromEnv(account.email);
|
|
435
436
|
}
|
package/src/core/config.ts
CHANGED
|
@@ -160,7 +160,7 @@ const envSchema = z
|
|
|
160
160
|
|
|
161
161
|
|
|
162
162
|
QWEN_BASE_URL: z.string().default("https://chat.qwen.ai"),
|
|
163
|
-
QWEN_CHAT_POOL_SIZE: z.string().default("
|
|
163
|
+
QWEN_CHAT_POOL_SIZE: z.string().default("0"),
|
|
164
164
|
QWEN_CHAT_POOL_MODELS: z.string().default("qwen3.7-plus"),
|
|
165
165
|
QWEN_PERSONALIZATION_FROM_REQUEST: z.string().default("true"),
|
|
166
166
|
QWEN_PERSONALIZATION_VERIFY_GET: z.string().default("true"),
|
package/src/core/database.ts
CHANGED
|
@@ -141,8 +141,13 @@ function runMigrations(db: Database.Database): void {
|
|
|
141
141
|
bx_v TEXT,
|
|
142
142
|
bx_ua TEXT,
|
|
143
143
|
bx_umidtoken TEXT,
|
|
144
|
+
sec_ch_ua TEXT,
|
|
145
|
+
sec_ch_ua_mobile TEXT,
|
|
146
|
+
sec_ch_ua_platform TEXT,
|
|
147
|
+
version TEXT,
|
|
144
148
|
user_id TEXT,
|
|
145
149
|
token_expires_at INTEGER,
|
|
150
|
+
captured_at INTEGER DEFAULT 0,
|
|
146
151
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
147
152
|
);
|
|
148
153
|
|
|
@@ -181,6 +186,22 @@ function runMigrations(db: Database.Database): void {
|
|
|
181
186
|
} catch (err) {
|
|
182
187
|
if (!isDuplicateColumnError(err)) throw err;
|
|
183
188
|
}
|
|
189
|
+
|
|
190
|
+
// qwen_auth_sessions columns migration
|
|
191
|
+
const authSessionCols = [
|
|
192
|
+
"sec_ch_ua TEXT",
|
|
193
|
+
"sec_ch_ua_mobile TEXT",
|
|
194
|
+
"sec_ch_ua_platform TEXT",
|
|
195
|
+
"version TEXT",
|
|
196
|
+
"captured_at INTEGER DEFAULT 0",
|
|
197
|
+
];
|
|
198
|
+
for (const col of authSessionCols) {
|
|
199
|
+
try {
|
|
200
|
+
db.exec(`ALTER TABLE qwen_auth_sessions ADD COLUMN ${col};`);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
if (!isDuplicateColumnError(err)) throw err;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
184
205
|
}
|
|
185
206
|
|
|
186
207
|
function isDuplicateColumnError(err: unknown): boolean {
|
|
@@ -274,3 +295,121 @@ export function closeDatabase(): void {
|
|
|
274
295
|
db = null;
|
|
275
296
|
}
|
|
276
297
|
}
|
|
298
|
+
|
|
299
|
+
export interface PersistedAuthSession {
|
|
300
|
+
accountId: string;
|
|
301
|
+
cookie: string;
|
|
302
|
+
userAgent: string;
|
|
303
|
+
bxV: string;
|
|
304
|
+
bxUa: string;
|
|
305
|
+
bxUmidtoken: string;
|
|
306
|
+
secChUa?: string;
|
|
307
|
+
secChUaMobile?: string;
|
|
308
|
+
secChUaPlatform?: string;
|
|
309
|
+
version?: string;
|
|
310
|
+
userId?: string;
|
|
311
|
+
tokenExpiresAt?: number;
|
|
312
|
+
capturedAt: number;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function saveAuthSession(
|
|
316
|
+
accountId: string,
|
|
317
|
+
session: {
|
|
318
|
+
cookie: string;
|
|
319
|
+
userAgent: string;
|
|
320
|
+
bxV?: string;
|
|
321
|
+
bxUa?: string;
|
|
322
|
+
bxUmidtoken?: string;
|
|
323
|
+
secChUa?: string;
|
|
324
|
+
secChUaMobile?: string;
|
|
325
|
+
secChUaPlatform?: string;
|
|
326
|
+
version?: string;
|
|
327
|
+
userId?: string;
|
|
328
|
+
tokenExpiresAt?: number;
|
|
329
|
+
capturedAt?: number;
|
|
330
|
+
},
|
|
331
|
+
): void {
|
|
332
|
+
const database = getDatabase();
|
|
333
|
+
const stmt = database.prepare(`
|
|
334
|
+
INSERT OR REPLACE INTO qwen_auth_sessions (
|
|
335
|
+
account_id, cookie, user_agent, bx_v, bx_ua, bx_umidtoken,
|
|
336
|
+
sec_ch_ua, sec_ch_ua_mobile, sec_ch_ua_platform, version,
|
|
337
|
+
user_id, token_expires_at, captured_at, updated_at
|
|
338
|
+
) VALUES (
|
|
339
|
+
?, ?, ?, ?, ?, ?,
|
|
340
|
+
?, ?, ?, ?,
|
|
341
|
+
?, ?, ?, datetime('now')
|
|
342
|
+
)
|
|
343
|
+
`);
|
|
344
|
+
stmt.run(
|
|
345
|
+
accountId,
|
|
346
|
+
session.cookie,
|
|
347
|
+
session.userAgent,
|
|
348
|
+
session.bxV || "2.5.37",
|
|
349
|
+
session.bxUa || "",
|
|
350
|
+
session.bxUmidtoken || "",
|
|
351
|
+
session.secChUa || null,
|
|
352
|
+
session.secChUaMobile || null,
|
|
353
|
+
session.secChUaPlatform || null,
|
|
354
|
+
session.version || null,
|
|
355
|
+
session.userId || null,
|
|
356
|
+
session.tokenExpiresAt || null,
|
|
357
|
+
session.capturedAt ?? Date.now(),
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export function getValidAuthSession(
|
|
362
|
+
accountId: string,
|
|
363
|
+
maxAgeMs = 30 * 24 * 60 * 60 * 1000,
|
|
364
|
+
): PersistedAuthSession | null {
|
|
365
|
+
const database = getDatabase();
|
|
366
|
+
const row = database
|
|
367
|
+
.prepare(
|
|
368
|
+
`SELECT account_id, cookie, user_agent, bx_v, bx_ua, bx_umidtoken,
|
|
369
|
+
sec_ch_ua, sec_ch_ua_mobile, sec_ch_ua_platform, version,
|
|
370
|
+
user_id, token_expires_at, captured_at
|
|
371
|
+
FROM qwen_auth_sessions WHERE account_id = ?`,
|
|
372
|
+
)
|
|
373
|
+
.get(accountId) as any;
|
|
374
|
+
|
|
375
|
+
if (!row) return null;
|
|
376
|
+
|
|
377
|
+
const capturedAt = Number(row.captured_at) || 0;
|
|
378
|
+
if (capturedAt <= 0 || Date.now() - capturedAt > maxAgeMs) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (row.token_expires_at) {
|
|
383
|
+
const tokenExpMs = Number(row.token_expires_at) * 1000;
|
|
384
|
+
// Safety margin of 5 minutes before token expires
|
|
385
|
+
if (tokenExpMs <= Date.now() + 5 * 60 * 1000) {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Ensure critical fields are non-empty
|
|
391
|
+
if (!row.cookie || !row.user_agent || !row.bx_v || !row.bx_ua || !row.bx_umidtoken) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
accountId: row.account_id,
|
|
397
|
+
cookie: row.cookie,
|
|
398
|
+
userAgent: row.user_agent,
|
|
399
|
+
bxV: row.bx_v,
|
|
400
|
+
bxUa: row.bx_ua,
|
|
401
|
+
bxUmidtoken: row.bx_umidtoken,
|
|
402
|
+
secChUa: row.sec_ch_ua || undefined,
|
|
403
|
+
secChUaMobile: row.sec_ch_ua_mobile || undefined,
|
|
404
|
+
secChUaPlatform: row.sec_ch_ua_platform || undefined,
|
|
405
|
+
version: row.version || undefined,
|
|
406
|
+
userId: row.user_id || undefined,
|
|
407
|
+
tokenExpiresAt: row.token_expires_at ? Number(row.token_expires_at) : undefined,
|
|
408
|
+
capturedAt,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function deleteAuthSession(accountId: string): void {
|
|
413
|
+
const database = getDatabase();
|
|
414
|
+
database.prepare("DELETE FROM qwen_auth_sessions WHERE account_id = ?").run(accountId);
|
|
415
|
+
}
|
package/src/core/paths.ts
CHANGED
|
@@ -86,12 +86,6 @@ export function resolveDataDir(options?: ResolveDataDirOptions): string {
|
|
|
86
86
|
// 2. Automated test isolation
|
|
87
87
|
const isTest = options?.isNodeTest ?? isRunningUnderNodeTest();
|
|
88
88
|
if (isTest) {
|
|
89
|
-
if (options?.localDataExists === false) {
|
|
90
|
-
return path.resolve("data-test");
|
|
91
|
-
}
|
|
92
|
-
if (fs.existsSync(path.resolve("data"))) {
|
|
93
|
-
return path.resolve("data");
|
|
94
|
-
}
|
|
95
89
|
return path.resolve("data-test");
|
|
96
90
|
}
|
|
97
91
|
// 3. Local repository checkout (development mode)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
let cachedVersion = "";
|
|
6
|
+
|
|
7
|
+
export function getAppVersion(): string {
|
|
8
|
+
if (cachedVersion) return cachedVersion;
|
|
9
|
+
try {
|
|
10
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const pkgPath = path.resolve(currentDir, "../../package.json");
|
|
12
|
+
if (fs.existsSync(pkgPath)) {
|
|
13
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
14
|
+
if (pkg.version) {
|
|
15
|
+
cachedVersion = `v${pkg.version}`;
|
|
16
|
+
return cachedVersion;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
} catch {}
|
|
20
|
+
cachedVersion = "v1.4.0";
|
|
21
|
+
return cachedVersion;
|
|
22
|
+
}
|
|
@@ -479,11 +479,15 @@ export function classifyRetryAction(
|
|
|
479
479
|
// Agent instructions ride ONLY the account-level personalization. An
|
|
480
480
|
// unconfirmed sync means this account cannot serve the request as-is —
|
|
481
481
|
// rotate to another account (each attempt re-syncs on its own account).
|
|
482
|
+
// Park the failing account with PersonalizationFailed cooldown so it does
|
|
483
|
+
// not enter an infinite ping-pong loop when multiple accounts fail.
|
|
482
484
|
if (err instanceof PersonalizationSyncError) {
|
|
483
485
|
return makeRetryAction("personalization_sync_failed", {
|
|
484
486
|
switchAccount: true,
|
|
485
487
|
forceNewChat: true,
|
|
486
488
|
retryAfterMs: baseDelayMs,
|
|
489
|
+
accountCooldownMs: config.concurrency.initFailureCooldownMs,
|
|
490
|
+
accountCooldownReason: "PersonalizationFailed",
|
|
487
491
|
});
|
|
488
492
|
}
|
|
489
493
|
|
package/src/routes/upload.ts
CHANGED
|
@@ -356,16 +356,37 @@ async function getSTSToken(
|
|
|
356
356
|
body: JSON.stringify({ filename, filesize: String(filesize), filetype }),
|
|
357
357
|
});
|
|
358
358
|
|
|
359
|
-
let
|
|
359
|
+
let data: any = null;
|
|
360
|
+
const { loadAccounts } = await import("../core/accounts.ts");
|
|
361
|
+
const resolvedId = accountId ?? loadAccounts()[0]?.id;
|
|
360
362
|
|
|
361
|
-
if (!
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
363
|
+
if (!isAuthMockEnabled() && resolvedId) {
|
|
364
|
+
try {
|
|
365
|
+
const { requestQwenTextInBrowser, buildCapturedQwenHeaders } =
|
|
366
|
+
await import("../services/qwen.ts");
|
|
367
|
+
const browserRes = await requestQwenTextInBrowser(
|
|
368
|
+
resolvedId,
|
|
369
|
+
"POST",
|
|
370
|
+
"/api/v2/files/getstsToken",
|
|
371
|
+
buildCapturedQwenHeaders(headers, { referer: qwenUrl("/") }),
|
|
372
|
+
JSON.stringify({ filename, filesize: String(filesize), filetype }),
|
|
373
|
+
);
|
|
374
|
+
if (browserRes.ok) {
|
|
375
|
+
data = await browserRes.json().catch(() => null);
|
|
376
|
+
}
|
|
377
|
+
} catch {}
|
|
366
378
|
}
|
|
367
379
|
|
|
368
|
-
|
|
380
|
+
if (!data?.success) {
|
|
381
|
+
const response = await doFetch(headers);
|
|
382
|
+
if (!response.ok) {
|
|
383
|
+
const errorText = await response.text().catch(() => "");
|
|
384
|
+
throw new Error(
|
|
385
|
+
`STS token request failed: ${response.status} ${errorText.substring(0, 200)}`,
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
data = await response.json().catch(() => null);
|
|
389
|
+
}
|
|
369
390
|
const is401 =
|
|
370
391
|
data?.success === false &&
|
|
371
392
|
(data?.data?.code === "Unauthorized" ||
|
|
@@ -389,9 +410,9 @@ async function getSTSToken(
|
|
|
389
410
|
headers["bx-v"] = fresh.bxV;
|
|
390
411
|
if (fresh.bxUa) headers["bx-ua"] = fresh.bxUa;
|
|
391
412
|
if (fresh.bxUmidtoken) headers["bx-umidtoken"] = fresh.bxUmidtoken;
|
|
392
|
-
|
|
393
|
-
if (
|
|
394
|
-
data = await
|
|
413
|
+
const retryRes = await doFetch(headers);
|
|
414
|
+
if (retryRes.ok) {
|
|
415
|
+
data = await retryRes.json().catch(() => null);
|
|
395
416
|
}
|
|
396
417
|
}
|
|
397
418
|
} catch (refreshErr) {
|