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.
- package/LICENSE +14 -14
- package/README.md +906 -906
- package/bin/qwenproxy.js +5 -1
- package/package.json +77 -78
- package/src/api/error-classifier.ts +159 -159
- package/src/api/error-helpers.ts +118 -118
- package/src/api/models.ts +261 -261
- package/src/api/server.ts +860 -859
- package/src/cache/memory-cache.ts +385 -385
- package/src/clean-cache.ts +204 -204
- package/src/core/account-concurrency.ts +671 -671
- package/src/core/account-manager.ts +301 -297
- package/src/core/account-priority.ts +163 -163
- package/src/core/accounts.ts +186 -186
- package/src/core/config.ts +383 -383
- package/src/core/crypto-utils.ts +79 -79
- package/src/core/database.ts +276 -276
- package/src/core/errors.ts +118 -118
- package/src/core/logger.ts +269 -269
- package/src/core/memory-usage.ts +84 -84
- package/src/core/metrics.ts +291 -291
- package/src/core/model-alias.ts +77 -77
- package/src/core/model-registry.ts +544 -544
- package/src/core/mutex.ts +119 -119
- package/src/core/paths.ts +199 -199
- package/src/core/prompt-limits.ts +214 -214
- package/src/core/reasoning-effort.ts +102 -102
- package/src/core/stream-registry.ts +96 -96
- package/src/core/waf-isolation.ts +117 -117
- package/src/core/watchdog.ts +195 -195
- package/src/delete-chats.ts +23 -23
- package/src/index.ts +65 -64
- package/src/login.ts +147 -147
- package/src/reset-cooldowns.ts +11 -11
- package/src/routes/anthropic/index.ts +355 -355
- package/src/routes/anthropic/translate.ts +522 -522
- package/src/routes/anthropic/types.ts +154 -154
- package/src/routes/anthropic/validation.ts +144 -144
- package/src/routes/chat/account.ts +1817 -1817
- package/src/routes/chat/context.ts +241 -241
- package/src/routes/chat/errors.ts +85 -85
- package/src/routes/chat/helpers.ts +268 -268
- package/src/routes/chat/index.ts +618 -618
- package/src/routes/chat/media.ts +285 -285
- package/src/routes/chat/retry-policy.ts +754 -754
- package/src/routes/chat/stop.ts +98 -98
- package/src/routes/chat/streaming.ts +2710 -2710
- package/src/routes/chat/validation.ts +526 -526
- package/src/routes/chat.ts +2 -2
- package/src/routes/completions.ts +290 -290
- package/src/routes/images.ts +139 -139
- package/src/routes/responses/adapter.ts +503 -503
- package/src/routes/responses/index.ts +405 -405
- package/src/routes/responses/state.ts +230 -230
- package/src/routes/responses/streaming.ts +528 -528
- package/src/routes/responses/types.ts +285 -285
- package/src/routes/responses/validation.ts +202 -202
- package/src/routes/upload.ts +731 -731
- package/src/routes/videos.ts +214 -214
- package/src/services/auth-playwright.ts +173 -173
- package/src/services/captcha-coordinator.ts +161 -161
- package/src/services/captcha-solver.ts +553 -553
- package/src/services/chat-cleanup.ts +80 -80
- package/src/services/context-meter.ts +317 -317
- package/src/services/fingerprint.ts +242 -242
- package/src/services/human-behavior.ts +173 -173
- package/src/services/media-generation.ts +1748 -1748
- package/src/services/playwright.ts +2878 -2800
- package/src/services/qwen-chat-pool.ts +345 -345
- package/src/services/qwen-errors.ts +133 -133
- package/src/services/qwen-headers.ts +79 -79
- package/src/services/qwen-thread-state.ts +393 -393
- package/src/services/qwen-url.ts +19 -19
- package/src/services/qwen.ts +3126 -3126
- package/src/services/session-keeper.ts +88 -88
- package/src/services/token-estimation-metrics.ts +118 -118
- package/src/sync/claude-code.ts +75 -75
- package/src/sync/codex.ts +123 -123
- package/src/sync/index.ts +362 -362
- package/src/sync/omp.ts +105 -105
- package/src/sync/opencode.ts +214 -214
- package/src/sync/types.ts +53 -53
- package/src/sync/utils.ts +27 -27
- package/src/sync-clients.ts +189 -189
- package/src/tools/instructions.ts +137 -137
- package/src/tools/manifest.ts +81 -81
- package/src/tools/parser.ts +2989 -2989
- package/src/tools/toolcall-tags.ts +142 -142
- package/src/tui/app.ts +259 -264
- package/src/tui/index.ts +61 -61
- package/src/tui/markdown.ts +258 -258
- package/src/tui/proxy-client.ts +331 -326
- package/src/tui/screen.ts +294 -278
- package/src/tui/server-manager.ts +270 -270
- package/src/tui/theme.ts +432 -432
- package/src/tui/types.ts +33 -33
- package/src/tui/views/accounts-view.ts +656 -656
- package/src/tui/views/chat-view.ts +1018 -823
- package/src/tui/views/logs-view.ts +479 -413
- package/src/tui/views/status-view.ts +204 -204
- package/src/tui/views/storage-view.ts +304 -291
- package/src/tui/views/sync-view.ts +409 -409
- package/src/types/ali-oss.d.ts +32 -32
- package/src/update-cli.ts +121 -0
- package/src/utils/context-truncation.ts +84 -84
- package/src/utils/json.ts +380 -380
- package/src/utils/session-id.ts +37 -37
- package/src/utils/tool-call-guard.ts +84 -84
- 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
|
+
}
|