qwenproxy-cli 1.6.3 → 1.6.5
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/dist/clean-cache.js +76 -12
- package/dist/delete-chats.js +182 -49
- package/dist/index.js +245 -64
- package/dist/login.js +90 -19
- package/dist/reset-cooldowns.js +79 -13
- package/package.json +1 -1
- package/src/core/mutex.ts +4 -0
- package/src/core/version.ts +1 -1
- package/src/routes/anthropic/translate.ts +15 -7
- package/src/routes/chat/helpers.ts +4 -2
- package/src/routes/chat/retry-policy.ts +3 -1
- package/src/routes/chat/streaming.ts +32 -10
- package/src/routes/responses/adapter.ts +4 -1
- package/src/services/context-meter.ts +1 -1
- package/src/services/playwright.ts +55 -21
- package/src/services/qwen-headers.ts +3 -2
- package/src/services/qwen.ts +88 -31
- package/src/tools/tool-call-heuristics.ts +88 -0
- package/src/utils/timezone.ts +56 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tool-call-heuristics.ts — Heuristics for deciding whether a turn that produced
|
|
3
|
+
* NO (successful) tool call should be auto-retried.
|
|
4
|
+
*
|
|
5
|
+
* The existing retry path only fires when a `<tool_call>` block was emitted
|
|
6
|
+
* but could not be parsed (malformed / undeclared). It does NOT cover the case
|
|
7
|
+
* where the model emits no tool-call block at all and instead answers with
|
|
8
|
+
* prose that references tools as if they were missing, e.g.:
|
|
9
|
+
*
|
|
10
|
+
* "Tool bash does not exists."
|
|
11
|
+
* "Tool read does not exists."
|
|
12
|
+
*
|
|
13
|
+
* In that case the client sees `finish_reason: "stop"` with no `tool_calls`,
|
|
14
|
+
* ends its turn, and the loop stalls until a human nudges it ("continue").
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export type MissingToolCallReason = "malformed" | "no_call" | "json_in_text" | null;
|
|
18
|
+
|
|
19
|
+
export interface MissingToolCallInput {
|
|
20
|
+
/** Number of tool definitions declared on the request. */
|
|
21
|
+
declaredToolCount: number;
|
|
22
|
+
/** Tool calls that parsed AND resolved to a declared tool. */
|
|
23
|
+
successfulToolCallCount: number;
|
|
24
|
+
/** `<tool_call>` blocks captured but rejected (bad JSON / undeclared name). */
|
|
25
|
+
malformedToolCallCount: number;
|
|
26
|
+
/** Total tool-call blocks the parser saw (malformed + capped + valid). */
|
|
27
|
+
emittedToolCallCount: number;
|
|
28
|
+
/** Assistant-visible text for the turn (excluding the tool-call blocks). */
|
|
29
|
+
text: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface MissingToolCallDecision {
|
|
33
|
+
retry: boolean;
|
|
34
|
+
reason: MissingToolCallReason;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Matches a model reply that talks about a tool as if it did not exist, which
|
|
39
|
+
* is the signature of the "lost tool definitions under long context" failure.
|
|
40
|
+
* Kept deliberately narrow to avoid retrying legitimate prose.
|
|
41
|
+
*/
|
|
42
|
+
export function isToolHesitationText(text: string): boolean {
|
|
43
|
+
if (!text) return false;
|
|
44
|
+
return /\btools?\b[^.\n]{0,24}?\b(?:does\s*n[o']?t|do\s*n[o']?t|does\s*not|do\s*not)\b[^.\n]{0,12}?\bexist/i.test(
|
|
45
|
+
text,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Detects when the model dumped a raw tool-call JSON payload as prose instead
|
|
51
|
+
* of emitting it as a structured tool call.
|
|
52
|
+
*/
|
|
53
|
+
export function isRawToolCallJsonInText(text: string): boolean {
|
|
54
|
+
if (!text) return false;
|
|
55
|
+
const hasToolCallJson =
|
|
56
|
+
/\{\s*"name"\s*:\s*"[a-zA-Z_][a-zA-Z0-9_]*"\s*,\s*"arguments"\s*:\s*\{/i.test(text);
|
|
57
|
+
const hasStrayCloseTag = /<\/tool_call>/.test(text);
|
|
58
|
+
return hasToolCallJson || hasStrayCloseTag;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function decideMissingToolCallRetry(
|
|
62
|
+
input: MissingToolCallInput,
|
|
63
|
+
): MissingToolCallDecision {
|
|
64
|
+
const {
|
|
65
|
+
declaredToolCount,
|
|
66
|
+
successfulToolCallCount,
|
|
67
|
+
malformedToolCallCount,
|
|
68
|
+
emittedToolCallCount,
|
|
69
|
+
text,
|
|
70
|
+
} = input;
|
|
71
|
+
|
|
72
|
+
// No tools were declared: a text answer is the expected outcome.
|
|
73
|
+
if (declaredToolCount <= 0) return { retry: false, reason: null };
|
|
74
|
+
|
|
75
|
+
// A usable call was produced: nothing to recover.
|
|
76
|
+
if (successfulToolCallCount > 0) return { retry: false, reason: null };
|
|
77
|
+
|
|
78
|
+
// Existing behaviour: a block was emitted but it was rejected.
|
|
79
|
+
if (malformedToolCallCount > 0) return { retry: true, reason: "malformed" };
|
|
80
|
+
|
|
81
|
+
// New behaviour: no `<tool_call>` block was emitted at all, yet the model
|
|
82
|
+
// talked about tools as missing (the "does not exists" case).
|
|
83
|
+
if (emittedToolCallCount === 0 && isToolHesitationText(text)) {
|
|
84
|
+
return { retry: true, reason: "no_call" };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { retry: false, reason: null };
|
|
88
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* timezone.ts — Locale-safe ASCII timezone header formatting and sanitization.
|
|
3
|
+
*
|
|
4
|
+
* Guarantees that HTTP headers passed into browser `window.fetch()` contain
|
|
5
|
+
* exclusively ISO-8859-1 code points (<= 255), preventing:
|
|
6
|
+
* `TypeError: Failed to read the 'headers' property from 'RequestInit': String contains non ISO-8859-1 code point`
|
|
7
|
+
* on systems configured with non-English locales (e.g. Russian, CJK, Arabic).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
11
|
+
const MONTHS = [
|
|
12
|
+
"Jan",
|
|
13
|
+
"Feb",
|
|
14
|
+
"Mar",
|
|
15
|
+
"Apr",
|
|
16
|
+
"May",
|
|
17
|
+
"Jun",
|
|
18
|
+
"Jul",
|
|
19
|
+
"Aug",
|
|
20
|
+
"Sep",
|
|
21
|
+
"Oct",
|
|
22
|
+
"Nov",
|
|
23
|
+
"Dec",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Format a Date into standard English browser date string matching Chrome,
|
|
28
|
+
* guaranteed pure ASCII (<= 127).
|
|
29
|
+
* Example: "Tue Oct 20 2026 14:35:10 GMT-0300"
|
|
30
|
+
*/
|
|
31
|
+
export function formatAsciiTimezone(date: Date = new Date()): string {
|
|
32
|
+
const dayName = DAYS[date.getDay()];
|
|
33
|
+
const monthName = MONTHS[date.getMonth()];
|
|
34
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
35
|
+
const year = date.getFullYear();
|
|
36
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
37
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
38
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
39
|
+
|
|
40
|
+
const offsetMinutes = -date.getTimezoneOffset();
|
|
41
|
+
const sign = offsetMinutes >= 0 ? "+" : "-";
|
|
42
|
+
const absOffset = Math.abs(offsetMinutes);
|
|
43
|
+
const offsetHours = String(Math.floor(absOffset / 60)).padStart(2, "0");
|
|
44
|
+
const offsetMins = String(absOffset % 60).padStart(2, "0");
|
|
45
|
+
const tzOffset = `GMT${sign}${offsetHours}${offsetMins}`;
|
|
46
|
+
|
|
47
|
+
return `${dayName} ${monthName} ${day} ${year} ${hours}:${minutes}:${seconds} ${tzOffset}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Sanitizes header values for browser fetch, stripping any code points > 255.
|
|
52
|
+
*/
|
|
53
|
+
export function sanitizeHeaderForFetch(val: string): string {
|
|
54
|
+
if (typeof val !== "string") return "";
|
|
55
|
+
return val.replace(/[^\x00-\xFF]/g, "");
|
|
56
|
+
}
|