qwenproxy-cli 1.0.0 → 1.0.2

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.
Files changed (109) hide show
  1. package/LICENSE +14 -14
  2. package/README.md +906 -906
  3. package/bin/qwenproxy.js +5 -1
  4. package/package.json +77 -78
  5. package/src/api/error-classifier.ts +159 -159
  6. package/src/api/error-helpers.ts +118 -118
  7. package/src/api/models.ts +261 -261
  8. package/src/api/server.ts +860 -859
  9. package/src/cache/memory-cache.ts +385 -385
  10. package/src/clean-cache.ts +204 -204
  11. package/src/core/account-concurrency.ts +671 -671
  12. package/src/core/account-manager.ts +301 -297
  13. package/src/core/account-priority.ts +163 -163
  14. package/src/core/accounts.ts +186 -186
  15. package/src/core/config.ts +383 -383
  16. package/src/core/crypto-utils.ts +79 -79
  17. package/src/core/database.ts +276 -276
  18. package/src/core/errors.ts +118 -118
  19. package/src/core/logger.ts +269 -269
  20. package/src/core/memory-usage.ts +84 -84
  21. package/src/core/metrics.ts +291 -291
  22. package/src/core/model-alias.ts +77 -77
  23. package/src/core/model-registry.ts +544 -544
  24. package/src/core/mutex.ts +119 -119
  25. package/src/core/paths.ts +199 -199
  26. package/src/core/prompt-limits.ts +214 -214
  27. package/src/core/reasoning-effort.ts +102 -102
  28. package/src/core/stream-registry.ts +96 -96
  29. package/src/core/waf-isolation.ts +117 -117
  30. package/src/core/watchdog.ts +195 -195
  31. package/src/delete-chats.ts +23 -23
  32. package/src/index.ts +65 -64
  33. package/src/login.ts +147 -147
  34. package/src/reset-cooldowns.ts +11 -11
  35. package/src/routes/anthropic/index.ts +355 -355
  36. package/src/routes/anthropic/translate.ts +522 -522
  37. package/src/routes/anthropic/types.ts +154 -154
  38. package/src/routes/anthropic/validation.ts +144 -144
  39. package/src/routes/chat/account.ts +1817 -1817
  40. package/src/routes/chat/context.ts +241 -241
  41. package/src/routes/chat/errors.ts +85 -85
  42. package/src/routes/chat/helpers.ts +268 -268
  43. package/src/routes/chat/index.ts +618 -618
  44. package/src/routes/chat/media.ts +285 -285
  45. package/src/routes/chat/retry-policy.ts +754 -754
  46. package/src/routes/chat/stop.ts +98 -98
  47. package/src/routes/chat/streaming.ts +2710 -2710
  48. package/src/routes/chat/validation.ts +526 -526
  49. package/src/routes/chat.ts +2 -2
  50. package/src/routes/completions.ts +290 -290
  51. package/src/routes/images.ts +139 -139
  52. package/src/routes/responses/adapter.ts +503 -503
  53. package/src/routes/responses/index.ts +405 -405
  54. package/src/routes/responses/state.ts +230 -230
  55. package/src/routes/responses/streaming.ts +528 -528
  56. package/src/routes/responses/types.ts +285 -285
  57. package/src/routes/responses/validation.ts +202 -202
  58. package/src/routes/upload.ts +731 -731
  59. package/src/routes/videos.ts +214 -214
  60. package/src/services/auth-playwright.ts +173 -173
  61. package/src/services/captcha-coordinator.ts +161 -161
  62. package/src/services/captcha-solver.ts +553 -553
  63. package/src/services/chat-cleanup.ts +80 -80
  64. package/src/services/context-meter.ts +317 -317
  65. package/src/services/fingerprint.ts +242 -242
  66. package/src/services/human-behavior.ts +173 -173
  67. package/src/services/media-generation.ts +1748 -1748
  68. package/src/services/playwright.ts +2878 -2800
  69. package/src/services/qwen-chat-pool.ts +345 -345
  70. package/src/services/qwen-errors.ts +133 -133
  71. package/src/services/qwen-headers.ts +79 -79
  72. package/src/services/qwen-thread-state.ts +393 -393
  73. package/src/services/qwen-url.ts +19 -19
  74. package/src/services/qwen.ts +3126 -3126
  75. package/src/services/session-keeper.ts +88 -88
  76. package/src/services/token-estimation-metrics.ts +118 -118
  77. package/src/sync/claude-code.ts +75 -75
  78. package/src/sync/codex.ts +123 -123
  79. package/src/sync/index.ts +362 -362
  80. package/src/sync/omp.ts +105 -105
  81. package/src/sync/opencode.ts +214 -214
  82. package/src/sync/types.ts +53 -53
  83. package/src/sync/utils.ts +27 -27
  84. package/src/sync-clients.ts +189 -189
  85. package/src/tools/instructions.ts +137 -137
  86. package/src/tools/manifest.ts +81 -81
  87. package/src/tools/parser.ts +2989 -2989
  88. package/src/tools/toolcall-tags.ts +142 -142
  89. package/src/tui/app.ts +259 -264
  90. package/src/tui/index.ts +61 -61
  91. package/src/tui/markdown.ts +258 -258
  92. package/src/tui/proxy-client.ts +331 -326
  93. package/src/tui/screen.ts +294 -278
  94. package/src/tui/server-manager.ts +270 -270
  95. package/src/tui/theme.ts +432 -432
  96. package/src/tui/types.ts +33 -33
  97. package/src/tui/views/accounts-view.ts +656 -656
  98. package/src/tui/views/chat-view.ts +1018 -823
  99. package/src/tui/views/logs-view.ts +479 -413
  100. package/src/tui/views/status-view.ts +204 -204
  101. package/src/tui/views/storage-view.ts +304 -291
  102. package/src/tui/views/sync-view.ts +409 -409
  103. package/src/types/ali-oss.d.ts +32 -32
  104. package/src/update-cli.ts +121 -0
  105. package/src/utils/context-truncation.ts +84 -84
  106. package/src/utils/json.ts +380 -380
  107. package/src/utils/session-id.ts +37 -37
  108. package/src/utils/tool-call-guard.ts +84 -84
  109. package/src/utils/types.ts +109 -109
@@ -1,133 +1,133 @@
1
- import {
2
- QwenProxyError,
3
- UpstreamRateLimit,
4
- UpstreamError,
5
- AuthError,
6
- } from "../core/errors.ts";
7
-
8
- /**
9
- * Messages from the completion fetch that indicate a transient network or
10
- * first-byte stall. These must be treated as retryable so tryCreateStreamWithRetry
11
- * rotates to another account instead of failing with a terminal 500 that the
12
- * client has to retry manually (which looks like an infinite hang).
13
- */
14
- export function isRetryableFetchErrorMessage(message: string): boolean {
15
- return (
16
- message.includes("fetch failed") ||
17
- message.includes("ECONNREFUSED") ||
18
- message.includes("ETIMEDOUT") ||
19
- message.includes("etimedout") ||
20
- message.includes("ENOTFOUND") ||
21
- message.includes("network") ||
22
- message.includes("timed out waiting for response headers")
23
- );
24
- }
25
-
26
- export class RetryableQwenStreamError extends UpstreamRateLimit {
27
- readonly retryAfterMs: number;
28
- /** Original upstream category; avoids exposing the inherited rate-limit code. */
29
- upstreamCode?: string;
30
-
31
- constructor(message: string, retryAfterMs: number) {
32
- super(message);
33
- this.name = "RetryableQwenStreamError";
34
- this.retryAfterMs = retryAfterMs;
35
- }
36
- }
37
-
38
- /**
39
- * The account-level personalization sync could not be confirmed. Agent
40
- * instructions ride ONLY the personalization channel (never inline in the
41
- * prompt), so an unconfirmed sync must fail the attempt — the retry policy
42
- * rotates to another account — instead of sending a request the model would
43
- * answer without any instructions or tools. Surfaces as 503 (service
44
- * degraded) if every account fails to sync.
45
- */
46
- export class PersonalizationSyncError extends QwenProxyError {
47
- readonly statusCode = 503;
48
- readonly type = "service_unavailable";
49
- readonly code = "personalization_unavailable";
50
-
51
- constructor(message: string) {
52
- super(message);
53
- this.name = "PersonalizationSyncError";
54
- }
55
- }
56
-
57
- export class QwenUpstreamError extends UpstreamError {
58
- readonly upstreamCode: string;
59
- readonly upstreamStatus: number;
60
-
61
- constructor(message: string, upstreamCode: string, upstreamStatus: number) {
62
- super(message);
63
- this.name = "QwenUpstreamError";
64
- this.upstreamCode = upstreamCode;
65
- this.upstreamStatus = upstreamStatus;
66
- }
67
- }
68
-
69
- export class QwenSessionExpiredError extends AuthError {
70
- readonly accountId: string;
71
-
72
- constructor(message: string, accountId: string) {
73
- super(message);
74
- this.name = "QwenSessionExpiredError";
75
- this.accountId = accountId;
76
- }
77
- }
78
-
79
- export class QwenUpstreamUnavailableError extends RetryableQwenStreamError {
80
- readonly httpStatusCode: number;
81
-
82
- constructor(message: string, httpStatusCode: number) {
83
- super(message, 5000);
84
- this.name = "QwenUpstreamUnavailableError";
85
- this.upstreamCode = "upstream_unavailable";
86
- this.httpStatusCode = httpStatusCode;
87
- }
88
- }
89
-
90
- export class QwenNetworkError extends RetryableQwenStreamError {
91
- constructor(message: string) {
92
- super(message, 3000);
93
- this.name = "QwenNetworkError";
94
- this.upstreamCode = "network_error";
95
- }
96
- }
97
-
98
- /**
99
- * Return the meaningful upstream category for logs and OpenAI error payloads.
100
- * RetryableQwenStreamError inherits the rate-limit base class for legacy retry
101
- * handling, so its inherited `code` must not be used as the actual category.
102
- */
103
- export function getQwenErrorCode(error: unknown): string | undefined {
104
- if (!error || typeof error !== "object") return undefined;
105
-
106
- const typed = error as {
107
- upstreamCode?: unknown;
108
- code?: unknown;
109
- message?: unknown;
110
- };
111
- if (typeof typed.upstreamCode === "string" && typed.upstreamCode) {
112
- return typed.upstreamCode;
113
- }
114
-
115
- if (error instanceof QwenNetworkError) return "network_error";
116
- if (error instanceof QwenUpstreamUnavailableError) {
117
- return "upstream_unavailable";
118
- }
119
-
120
- if (error instanceof RetryableQwenStreamError) {
121
- const message = typeof typed.message === "string" ? typed.message.toLowerCase() : "";
122
- if (message.includes("chat is in progress")) return "chat_in_progress";
123
- if (message.includes("not exist") || message.includes("does not exist")) {
124
- return "chat_not_exist";
125
- }
126
- if (message.includes("anti-bot") || message.includes("captcha")) {
127
- return "waf_challenge";
128
- }
129
- return "upstream_retryable";
130
- }
131
-
132
- return typeof typed.code === "string" && typed.code ? typed.code : undefined;
133
- }
1
+ import {
2
+ QwenProxyError,
3
+ UpstreamRateLimit,
4
+ UpstreamError,
5
+ AuthError,
6
+ } from "../core/errors.ts";
7
+
8
+ /**
9
+ * Messages from the completion fetch that indicate a transient network or
10
+ * first-byte stall. These must be treated as retryable so tryCreateStreamWithRetry
11
+ * rotates to another account instead of failing with a terminal 500 that the
12
+ * client has to retry manually (which looks like an infinite hang).
13
+ */
14
+ export function isRetryableFetchErrorMessage(message: string): boolean {
15
+ return (
16
+ message.includes("fetch failed") ||
17
+ message.includes("ECONNREFUSED") ||
18
+ message.includes("ETIMEDOUT") ||
19
+ message.includes("etimedout") ||
20
+ message.includes("ENOTFOUND") ||
21
+ message.includes("network") ||
22
+ message.includes("timed out waiting for response headers")
23
+ );
24
+ }
25
+
26
+ export class RetryableQwenStreamError extends UpstreamRateLimit {
27
+ readonly retryAfterMs: number;
28
+ /** Original upstream category; avoids exposing the inherited rate-limit code. */
29
+ upstreamCode?: string;
30
+
31
+ constructor(message: string, retryAfterMs: number) {
32
+ super(message);
33
+ this.name = "RetryableQwenStreamError";
34
+ this.retryAfterMs = retryAfterMs;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * The account-level personalization sync could not be confirmed. Agent
40
+ * instructions ride ONLY the personalization channel (never inline in the
41
+ * prompt), so an unconfirmed sync must fail the attempt — the retry policy
42
+ * rotates to another account — instead of sending a request the model would
43
+ * answer without any instructions or tools. Surfaces as 503 (service
44
+ * degraded) if every account fails to sync.
45
+ */
46
+ export class PersonalizationSyncError extends QwenProxyError {
47
+ readonly statusCode = 503;
48
+ readonly type = "service_unavailable";
49
+ readonly code = "personalization_unavailable";
50
+
51
+ constructor(message: string) {
52
+ super(message);
53
+ this.name = "PersonalizationSyncError";
54
+ }
55
+ }
56
+
57
+ export class QwenUpstreamError extends UpstreamError {
58
+ readonly upstreamCode: string;
59
+ readonly upstreamStatus: number;
60
+
61
+ constructor(message: string, upstreamCode: string, upstreamStatus: number) {
62
+ super(message);
63
+ this.name = "QwenUpstreamError";
64
+ this.upstreamCode = upstreamCode;
65
+ this.upstreamStatus = upstreamStatus;
66
+ }
67
+ }
68
+
69
+ export class QwenSessionExpiredError extends AuthError {
70
+ readonly accountId: string;
71
+
72
+ constructor(message: string, accountId: string) {
73
+ super(message);
74
+ this.name = "QwenSessionExpiredError";
75
+ this.accountId = accountId;
76
+ }
77
+ }
78
+
79
+ export class QwenUpstreamUnavailableError extends RetryableQwenStreamError {
80
+ readonly httpStatusCode: number;
81
+
82
+ constructor(message: string, httpStatusCode: number) {
83
+ super(message, 5000);
84
+ this.name = "QwenUpstreamUnavailableError";
85
+ this.upstreamCode = "upstream_unavailable";
86
+ this.httpStatusCode = httpStatusCode;
87
+ }
88
+ }
89
+
90
+ export class QwenNetworkError extends RetryableQwenStreamError {
91
+ constructor(message: string) {
92
+ super(message, 3000);
93
+ this.name = "QwenNetworkError";
94
+ this.upstreamCode = "network_error";
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Return the meaningful upstream category for logs and OpenAI error payloads.
100
+ * RetryableQwenStreamError inherits the rate-limit base class for legacy retry
101
+ * handling, so its inherited `code` must not be used as the actual category.
102
+ */
103
+ export function getQwenErrorCode(error: unknown): string | undefined {
104
+ if (!error || typeof error !== "object") return undefined;
105
+
106
+ const typed = error as {
107
+ upstreamCode?: unknown;
108
+ code?: unknown;
109
+ message?: unknown;
110
+ };
111
+ if (typeof typed.upstreamCode === "string" && typed.upstreamCode) {
112
+ return typed.upstreamCode;
113
+ }
114
+
115
+ if (error instanceof QwenNetworkError) return "network_error";
116
+ if (error instanceof QwenUpstreamUnavailableError) {
117
+ return "upstream_unavailable";
118
+ }
119
+
120
+ if (error instanceof RetryableQwenStreamError) {
121
+ const message = typeof typed.message === "string" ? typed.message.toLowerCase() : "";
122
+ if (message.includes("chat is in progress")) return "chat_in_progress";
123
+ if (message.includes("not exist") || message.includes("does not exist")) {
124
+ return "chat_not_exist";
125
+ }
126
+ if (message.includes("anti-bot") || message.includes("captcha")) {
127
+ return "waf_challenge";
128
+ }
129
+ return "upstream_retryable";
130
+ }
131
+
132
+ return typeof typed.code === "string" && typed.code ? typed.code : undefined;
133
+ }
@@ -1,79 +1,79 @@
1
- import { v4 as uuidv4 } from "uuid";
2
- import { qwenUrl, qwenOrigin } from "./qwen-url.ts";
3
- import { config } from "../core/config.js";
4
- let dynamicWebVersion: string | null = null;
5
-
6
- export function updateQwenWebVersion(version?: string | null): void {
7
- if (version && typeof version === "string" && /^\d+\.\d+\.\d+/.test(version)) {
8
- dynamicWebVersion = version.trim();
9
- }
10
- }
11
-
12
- export function getQwenWebVersion(): string {
13
- return dynamicWebVersion || config.qwen.webVersion;
14
- }
15
-
16
- export const QWEN_WEB_VERSION = config.qwen.webVersion;
17
- export const DEFAULT_QWEN_USER_AGENT =
18
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36";
19
- const QWEN_TIMEZONE_HEADER = new Date().toString().split(" (")[0];
20
-
21
- export interface BuildQwenHeadersOptions {
22
- cookie: string;
23
- userAgent?: string;
24
- bxUa?: string;
25
- bxUmidtoken?: string;
26
- bxV?: string;
27
- chatSessionId?: string | null;
28
- /** Real browser client-hints captured by getBasicHeaders (anti-hardcoded). */
29
- secChUa?: string;
30
- secChUaMobile?: string;
31
- secChUaPlatform?: string;
32
- /** Real web bundle version captured from the browser request (anti-hardcoded). */
33
- version?: string;
34
- extra?: Record<string, string>;
35
- }
36
-
37
- export function buildQwenRequestHeaders(
38
- opts: BuildQwenHeadersOptions,
39
- ): Record<string, string> {
40
- const headers: Record<string, string> = {
41
- ...(opts.extra ?? {}),
42
- Accept: "application/json",
43
- "Accept-Language": "pt-BR,pt;q=0.9",
44
- "Accept-Encoding": "gzip, deflate, br, zstd",
45
- "Content-Type": "application/json",
46
- Cookie: opts.cookie,
47
- Origin: qwenOrigin(),
48
- Referer:
49
- opts.extra?.Referer ??
50
- (opts.chatSessionId
51
- ? qwenUrl(`/c/${encodeURIComponent(opts.chatSessionId)}`)
52
- : qwenUrl("/")),
53
- "Sec-Fetch-Dest": "empty",
54
- "Sec-Fetch-Mode": "cors",
55
- "Sec-Fetch-Site": "same-origin",
56
- Connection: "keep-alive",
57
- "User-Agent": opts.userAgent || DEFAULT_QWEN_USER_AGENT,
58
- "X-Request-Id": uuidv4(),
59
- "bx-v": opts.bxV || "2.5.37",
60
- source: "web",
61
- version: opts.version || getQwenWebVersion(),
62
- timezone: opts.extra?.timezone || new Date().toString().split(" (")[0],
63
- // Use the real browser client-hints when captured (anti-hardcoded); fall
64
- // back to the static fingerprint otherwise.
65
- "sec-ch-ua": opts.secChUa || '"Google Chrome";v="150", "Chromium";v="150", "Not.A/Brand";v="99"',
66
- "sec-ch-ua-mobile": opts.secChUaMobile || "?0",
67
- "sec-ch-ua-platform": opts.secChUaPlatform || '"Windows"',
68
- };
69
-
70
- // The real chat.qwen.ai client sends ONLY bx-v on API requests — the WAF
71
- // carries bx-ua/bx-umidtoken as browser cookies, not headers. Match that
72
- // unless QWEN_SEND_BX_UA=true restores the legacy injection.
73
- if (config.qwen.sendBxUa) {
74
- if (opts.bxUa) headers["bx-ua"] = opts.bxUa;
75
- if (opts.bxUmidtoken) headers["bx-umidtoken"] = opts.bxUmidtoken;
76
- }
77
-
78
- return headers;
79
- }
1
+ import { v4 as uuidv4 } from "uuid";
2
+ import { qwenUrl, qwenOrigin } from "./qwen-url.ts";
3
+ import { config } from "../core/config.js";
4
+ let dynamicWebVersion: string | null = null;
5
+
6
+ export function updateQwenWebVersion(version?: string | null): void {
7
+ if (version && typeof version === "string" && /^\d+\.\d+\.\d+/.test(version)) {
8
+ dynamicWebVersion = version.trim();
9
+ }
10
+ }
11
+
12
+ export function getQwenWebVersion(): string {
13
+ return dynamicWebVersion || config.qwen.webVersion;
14
+ }
15
+
16
+ export const QWEN_WEB_VERSION = config.qwen.webVersion;
17
+ export const DEFAULT_QWEN_USER_AGENT =
18
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36";
19
+ const QWEN_TIMEZONE_HEADER = new Date().toString().split(" (")[0];
20
+
21
+ export interface BuildQwenHeadersOptions {
22
+ cookie: string;
23
+ userAgent?: string;
24
+ bxUa?: string;
25
+ bxUmidtoken?: string;
26
+ bxV?: string;
27
+ chatSessionId?: string | null;
28
+ /** Real browser client-hints captured by getBasicHeaders (anti-hardcoded). */
29
+ secChUa?: string;
30
+ secChUaMobile?: string;
31
+ secChUaPlatform?: string;
32
+ /** Real web bundle version captured from the browser request (anti-hardcoded). */
33
+ version?: string;
34
+ extra?: Record<string, string>;
35
+ }
36
+
37
+ export function buildQwenRequestHeaders(
38
+ opts: BuildQwenHeadersOptions,
39
+ ): Record<string, string> {
40
+ const headers: Record<string, string> = {
41
+ ...(opts.extra ?? {}),
42
+ Accept: "application/json",
43
+ "Accept-Language": "pt-BR,pt;q=0.9",
44
+ "Accept-Encoding": "gzip, deflate, br, zstd",
45
+ "Content-Type": "application/json",
46
+ Cookie: opts.cookie,
47
+ Origin: qwenOrigin(),
48
+ Referer:
49
+ opts.extra?.Referer ??
50
+ (opts.chatSessionId
51
+ ? qwenUrl(`/c/${encodeURIComponent(opts.chatSessionId)}`)
52
+ : qwenUrl("/")),
53
+ "Sec-Fetch-Dest": "empty",
54
+ "Sec-Fetch-Mode": "cors",
55
+ "Sec-Fetch-Site": "same-origin",
56
+ Connection: "keep-alive",
57
+ "User-Agent": opts.userAgent || DEFAULT_QWEN_USER_AGENT,
58
+ "X-Request-Id": uuidv4(),
59
+ "bx-v": opts.bxV || "2.5.37",
60
+ source: "web",
61
+ version: opts.version || getQwenWebVersion(),
62
+ timezone: opts.extra?.timezone || new Date().toString().split(" (")[0],
63
+ // Use the real browser client-hints when captured (anti-hardcoded); fall
64
+ // back to the static fingerprint otherwise.
65
+ "sec-ch-ua": opts.secChUa || '"Google Chrome";v="150", "Chromium";v="150", "Not.A/Brand";v="99"',
66
+ "sec-ch-ua-mobile": opts.secChUaMobile || "?0",
67
+ "sec-ch-ua-platform": opts.secChUaPlatform || '"Windows"',
68
+ };
69
+
70
+ // The real chat.qwen.ai client sends ONLY bx-v on API requests — the WAF
71
+ // carries bx-ua/bx-umidtoken as browser cookies, not headers. Match that
72
+ // unless QWEN_SEND_BX_UA=true restores the legacy injection.
73
+ if (config.qwen.sendBxUa) {
74
+ if (opts.bxUa) headers["bx-ua"] = opts.bxUa;
75
+ if (opts.bxUmidtoken) headers["bx-umidtoken"] = opts.bxUmidtoken;
76
+ }
77
+
78
+ return headers;
79
+ }