qwenproxy-cli 1.3.6 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwenproxy-cli",
3
- "version": "1.3.6",
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 (recently successful accounts first),
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 (~15-20s).
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 warmOrder = getAccountsByPriority(accounts).filter(
813
- (account) => !getAccountCooldownInfo(account.id),
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.filter(
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("QwenProxy")}|
1048
+ |${center(`QwenProxy ${appVersion}`)}|
1032
1049
  |${center("OpenAI & Anthropic Compatible API")}|
1033
1050
  |${blank()}|
1034
1051
  +${"-".repeat(W)}+
@@ -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
  }
@@ -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("1"),
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"),
@@ -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
 
@@ -356,16 +356,37 @@ async function getSTSToken(
356
356
  body: JSON.stringify({ filename, filesize: String(filesize), filetype }),
357
357
  });
358
358
 
359
- let response = await doFetch(headers);
359
+ let data: any = null;
360
+ const { loadAccounts } = await import("../core/accounts.ts");
361
+ const resolvedId = accountId ?? loadAccounts()[0]?.id;
360
362
 
361
- if (!response.ok) {
362
- const errorText = await response.text().catch(() => "");
363
- throw new Error(
364
- `STS token request failed: ${response.status} ${errorText.substring(0, 200)}`,
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
- let data = await response.json().catch(() => null);
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
- response = await doFetch(headers);
393
- if (response.ok) {
394
- data = await response.json().catch(() => null);
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) {