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,142 +1,142 @@
1
- /**
2
- * Central tool-call marker module.
3
- *
4
- * The Qwen upstream filters/corrupts the conventional `<tool_call>` token in
5
- * the SSE stream. We deliberately use a private tag (`<qpx_call>`) that does
6
- * NOT contain the `tool_call` substring, so the provider passes it through
7
- * untouched. Every place that *emits* (prompt instructions, history replay) or
8
- * *parses* (response parser) tool calls reads from this module — the two sides
9
- * can never drift apart.
10
- *
11
- * Override with QWEN_TOOL_OPEN / QWEN_TOOL_CLOSE env vars if needed (e.g. to
12
- * dodge a future provider filter that learns this tag).
13
- *
14
- * The parser still accepts the legacy `<tool_call>` token from model output
15
- * for backward compatibility (in-flight sessions, models fine-tuned on it).
16
- */
17
-
18
- const DEFAULT_OPEN = "<qpx_call>";
19
- const DEFAULT_CLOSE = "</qpx_call>";
20
-
21
- export const TOOL_CALL_OPEN: string =
22
- (process.env.QWEN_TOOL_OPEN || DEFAULT_OPEN).trim() || DEFAULT_OPEN;
23
- export const TOOL_CALL_CLOSE: string =
24
- (process.env.QWEN_TOOL_CLOSE || DEFAULT_CLOSE).trim() || DEFAULT_CLOSE;
25
-
26
- function tagName(tag: string): string {
27
- return tag
28
- .replace(/^<\/?/, "")
29
- .replace(/>$/, "")
30
- .trim();
31
- }
32
-
33
- /** All accepted open tag names (custom + legacy). */
34
- export function getOpenNames(): string[] {
35
- return Array.from(new Set([tagName(TOOL_CALL_OPEN), "tool_call", "tool_calls"]));
36
- }
37
-
38
- /** All accepted close tag names (custom + legacy). */
39
- export function getCloseNames(): string[] {
40
- return Array.from(new Set([tagName(TOOL_CALL_CLOSE), "tool_call", "tool_calls", "tool"]));
41
- }
42
-
43
- /** Wrap a JSON payload in the canonical open/close tags. */
44
- export function wrapToolCallPayload(json: string): string {
45
- return `${TOOL_CALL_OPEN}\n${json}\n${TOOL_CALL_CLOSE}`;
46
- }
47
-
48
- function matchesAt(buffer: string, i: number, value: string): boolean {
49
- if (i + value.length > buffer.length) return false;
50
- for (let j = 0; j < value.length; j++) {
51
- const c = buffer.charCodeAt(i + j);
52
- const t = value.charCodeAt(j);
53
- if (c !== t && (c | 0x20) !== (t | 0x20)) return false;
54
- }
55
- return true;
56
- }
57
-
58
- /**
59
- * Find the first open tag in `buffer` (any accepted name, case-insensitive).
60
- * Returns position/length/tag string or null.
61
- */
62
- export function findToolOpen(
63
- buffer: string,
64
- ): { index: number; length: number; tag: string } | null {
65
- let best: { index: number; length: number; tag: string } | null = null;
66
- for (const name of getOpenNames()) {
67
- const re = new RegExp(`<${name}\\b[^>]*>`, "i");
68
- const m = buffer.match(re);
69
- if (m && m.index !== undefined) {
70
- if (!best || m.index < best.index) {
71
- best = { index: m.index, length: m[0].length, tag: m[0] };
72
- }
73
- }
74
- }
75
- return best;
76
- }
77
-
78
- /**
79
- * At buffer position `i`, is there a whitespace-tolerant close tag?
80
- * Accepts `</qpx_call>`, `</tool_call >` (space before `>`), `</tool>`, etc.
81
- * Returns the total length consumed, or null.
82
- */
83
- export function matchToolCloseAt(buffer: string, i: number): number | null {
84
- for (const name of getCloseNames()) {
85
- const prefix = `</${name}`;
86
- if (!matchesAt(buffer, i, prefix)) continue;
87
- let j = i + prefix.length;
88
- // Allow optional whitespace before closing `>`
89
- while (j < buffer.length && /\s/.test(buffer[j])) j++;
90
- if (buffer[j] === ">") return j + 1 - i;
91
- }
92
- return null;
93
- }
94
-
95
- /** Bare name of an open tag actually seen in the stream (e.g. 'qpx_call'). */
96
- export function openTagName(openTag: string): string {
97
- const m = openTag.match(/^<\/?([a-zA-Z_][\w-]*)/);
98
- return m ? m[1] : tagName(TOOL_CALL_OPEN);
99
- }
100
-
101
- /** Matching close tag string for an open tag actually seen in the stream. */
102
- export function closeTagFor(openTag: string): string {
103
- return `</${openTagName(openTag)}>`;
104
- }
105
-
106
- /**
107
- * True when `text` contains the start of any accepted open tag.
108
- * Used in stream-handler to detect tool-call signals in a chunk.
109
- */
110
- export function textContainsToolCallStart(text: string): boolean {
111
- const lower = text.toLowerCase();
112
- for (const name of getOpenNames()) {
113
- if (lower.includes(`<${name.toLowerCase()}`)) return true;
114
- }
115
- return false;
116
- }
117
-
118
- /**
119
- * Strip leading stray close tags (`</qpx_call>`, `</tool_call>`, `</tool>`)
120
- * that the provider sometimes injects before real content.
121
- */
122
- export function stripLeadingStrayCloses(s: string): string {
123
- const re = /^\s*<\/(?:qpx_call|tool_call|tool_calls|tool)\s*>/i;
124
- let m: RegExpExecArray | null;
125
- while ((m = re.exec(s))) s = s.substring(m[0].length);
126
- return s;
127
- }
128
-
129
- /**
130
- * Strip trailing stray close tags that leak after the real payload.
131
- */
132
- export function stripTrailingStrayCloses(s: string): string {
133
- const re = /<\/(?:qpx_call|tool_call|tool_calls|tool)\s*>\s*$/i;
134
- let m: RegExpExecArray | null;
135
- while ((m = re.exec(s))) s = s.substring(0, s.length - m[0].length);
136
- return s;
137
- }
138
-
139
- /** Strip both leading and trailing stray close tags. */
140
- export function sanitizeStrayCloses(s: string): string {
141
- return stripTrailingStrayCloses(stripLeadingStrayCloses(s));
142
- }
1
+ /**
2
+ * Central tool-call marker module.
3
+ *
4
+ * The Qwen upstream filters/corrupts the conventional `<tool_call>` token in
5
+ * the SSE stream. We deliberately use a private tag (`<qpx_call>`) that does
6
+ * NOT contain the `tool_call` substring, so the provider passes it through
7
+ * untouched. Every place that *emits* (prompt instructions, history replay) or
8
+ * *parses* (response parser) tool calls reads from this module — the two sides
9
+ * can never drift apart.
10
+ *
11
+ * Override with QWEN_TOOL_OPEN / QWEN_TOOL_CLOSE env vars if needed (e.g. to
12
+ * dodge a future provider filter that learns this tag).
13
+ *
14
+ * The parser still accepts the legacy `<tool_call>` token from model output
15
+ * for backward compatibility (in-flight sessions, models fine-tuned on it).
16
+ */
17
+
18
+ const DEFAULT_OPEN = "<qpx_call>";
19
+ const DEFAULT_CLOSE = "</qpx_call>";
20
+
21
+ export const TOOL_CALL_OPEN: string =
22
+ (process.env.QWEN_TOOL_OPEN || DEFAULT_OPEN).trim() || DEFAULT_OPEN;
23
+ export const TOOL_CALL_CLOSE: string =
24
+ (process.env.QWEN_TOOL_CLOSE || DEFAULT_CLOSE).trim() || DEFAULT_CLOSE;
25
+
26
+ function tagName(tag: string): string {
27
+ return tag
28
+ .replace(/^<\/?/, "")
29
+ .replace(/>$/, "")
30
+ .trim();
31
+ }
32
+
33
+ /** All accepted open tag names (custom + legacy). */
34
+ export function getOpenNames(): string[] {
35
+ return Array.from(new Set([tagName(TOOL_CALL_OPEN), "tool_call", "tool_calls"]));
36
+ }
37
+
38
+ /** All accepted close tag names (custom + legacy). */
39
+ export function getCloseNames(): string[] {
40
+ return Array.from(new Set([tagName(TOOL_CALL_CLOSE), "tool_call", "tool_calls", "tool"]));
41
+ }
42
+
43
+ /** Wrap a JSON payload in the canonical open/close tags. */
44
+ export function wrapToolCallPayload(json: string): string {
45
+ return `${TOOL_CALL_OPEN}\n${json}\n${TOOL_CALL_CLOSE}`;
46
+ }
47
+
48
+ function matchesAt(buffer: string, i: number, value: string): boolean {
49
+ if (i + value.length > buffer.length) return false;
50
+ for (let j = 0; j < value.length; j++) {
51
+ const c = buffer.charCodeAt(i + j);
52
+ const t = value.charCodeAt(j);
53
+ if (c !== t && (c | 0x20) !== (t | 0x20)) return false;
54
+ }
55
+ return true;
56
+ }
57
+
58
+ /**
59
+ * Find the first open tag in `buffer` (any accepted name, case-insensitive).
60
+ * Returns position/length/tag string or null.
61
+ */
62
+ export function findToolOpen(
63
+ buffer: string,
64
+ ): { index: number; length: number; tag: string } | null {
65
+ let best: { index: number; length: number; tag: string } | null = null;
66
+ for (const name of getOpenNames()) {
67
+ const re = new RegExp(`<${name}\\b[^>]*>`, "i");
68
+ const m = buffer.match(re);
69
+ if (m && m.index !== undefined) {
70
+ if (!best || m.index < best.index) {
71
+ best = { index: m.index, length: m[0].length, tag: m[0] };
72
+ }
73
+ }
74
+ }
75
+ return best;
76
+ }
77
+
78
+ /**
79
+ * At buffer position `i`, is there a whitespace-tolerant close tag?
80
+ * Accepts `</qpx_call>`, `</tool_call >` (space before `>`), `</tool>`, etc.
81
+ * Returns the total length consumed, or null.
82
+ */
83
+ export function matchToolCloseAt(buffer: string, i: number): number | null {
84
+ for (const name of getCloseNames()) {
85
+ const prefix = `</${name}`;
86
+ if (!matchesAt(buffer, i, prefix)) continue;
87
+ let j = i + prefix.length;
88
+ // Allow optional whitespace before closing `>`
89
+ while (j < buffer.length && /\s/.test(buffer[j])) j++;
90
+ if (buffer[j] === ">") return j + 1 - i;
91
+ }
92
+ return null;
93
+ }
94
+
95
+ /** Bare name of an open tag actually seen in the stream (e.g. 'qpx_call'). */
96
+ export function openTagName(openTag: string): string {
97
+ const m = openTag.match(/^<\/?([a-zA-Z_][\w-]*)/);
98
+ return m ? m[1] : tagName(TOOL_CALL_OPEN);
99
+ }
100
+
101
+ /** Matching close tag string for an open tag actually seen in the stream. */
102
+ export function closeTagFor(openTag: string): string {
103
+ return `</${openTagName(openTag)}>`;
104
+ }
105
+
106
+ /**
107
+ * True when `text` contains the start of any accepted open tag.
108
+ * Used in stream-handler to detect tool-call signals in a chunk.
109
+ */
110
+ export function textContainsToolCallStart(text: string): boolean {
111
+ const lower = text.toLowerCase();
112
+ for (const name of getOpenNames()) {
113
+ if (lower.includes(`<${name.toLowerCase()}`)) return true;
114
+ }
115
+ return false;
116
+ }
117
+
118
+ /**
119
+ * Strip leading stray close tags (`</qpx_call>`, `</tool_call>`, `</tool>`)
120
+ * that the provider sometimes injects before real content.
121
+ */
122
+ export function stripLeadingStrayCloses(s: string): string {
123
+ const re = /^\s*<\/(?:qpx_call|tool_call|tool_calls|tool)\s*>/i;
124
+ let m: RegExpExecArray | null;
125
+ while ((m = re.exec(s))) s = s.substring(m[0].length);
126
+ return s;
127
+ }
128
+
129
+ /**
130
+ * Strip trailing stray close tags that leak after the real payload.
131
+ */
132
+ export function stripTrailingStrayCloses(s: string): string {
133
+ const re = /<\/(?:qpx_call|tool_call|tool_calls|tool)\s*>\s*$/i;
134
+ let m: RegExpExecArray | null;
135
+ while ((m = re.exec(s))) s = s.substring(0, s.length - m[0].length);
136
+ return s;
137
+ }
138
+
139
+ /** Strip both leading and trailing stray close tags. */
140
+ export function sanitizeStrayCloses(s: string): string {
141
+ return stripTrailingStrayCloses(stripLeadingStrayCloses(s));
142
+ }