qwenproxy-cli 1.0.24 → 1.0.25
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 +1 -1
- package/src/routes/chat/streaming.ts +28 -0
- package/src/services/qwen.ts +26 -14
- package/src/tools/parser.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.25",
|
|
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": {
|
|
@@ -1841,6 +1841,34 @@ export async function processStreamingResponse(
|
|
|
1841
1841
|
// executed, so the model can re-issue them.
|
|
1842
1842
|
setToolCapNotice(logicalSessionId);
|
|
1843
1843
|
await reader.cancel().catch(() => undefined);
|
|
1844
|
+
// Explicitly tell Qwen to stop generating on the backend so the upstream chat
|
|
1845
|
+
// settles immediately instead of remaining in "in progress" state for 30s.
|
|
1846
|
+
const capSessionId = currentUiSessionId || logicalSessionId;
|
|
1847
|
+
const capHeaders = getStream(completionId)?.headers;
|
|
1848
|
+
if (capSessionId && targetResponseId && capHeaders?.cookie && capHeaders["user-agent"]) {
|
|
1849
|
+
const capAccountId = currentAccountId;
|
|
1850
|
+
void requestQwenTextInBrowser(
|
|
1851
|
+
capAccountId,
|
|
1852
|
+
"POST",
|
|
1853
|
+
`/api/v2/chat/completions/stop?chat_id=${encodeURIComponent(capSessionId)}`,
|
|
1854
|
+
buildQwenRequestHeaders({
|
|
1855
|
+
cookie: capHeaders.cookie,
|
|
1856
|
+
userAgent: capHeaders["user-agent"],
|
|
1857
|
+
bxUa: capHeaders["bx-ua"],
|
|
1858
|
+
bxUmidtoken: capHeaders["bx-umidtoken"],
|
|
1859
|
+
bxV: capHeaders["bx-v"],
|
|
1860
|
+
chatSessionId: capSessionId,
|
|
1861
|
+
}),
|
|
1862
|
+
JSON.stringify({
|
|
1863
|
+
chat_id: capSessionId,
|
|
1864
|
+
response_id: targetResponseId,
|
|
1865
|
+
}),
|
|
1866
|
+
{
|
|
1867
|
+
referrer: qwenUrl(`/c/${encodeURIComponent(capSessionId)}`),
|
|
1868
|
+
noMutexRecovery: true,
|
|
1869
|
+
},
|
|
1870
|
+
).catch(() => undefined);
|
|
1871
|
+
}
|
|
1844
1872
|
}
|
|
1845
1873
|
|
|
1846
1874
|
// Post-stream: error check + flush remaining content
|
package/src/services/qwen.ts
CHANGED
|
@@ -261,7 +261,10 @@ export function computeDynamicIdleTimeout(opts: {
|
|
|
261
261
|
}): number {
|
|
262
262
|
const payloadMB = opts.payloadSize / (1024 * 1024);
|
|
263
263
|
const dynamic = opts.baseTimeoutMs + Math.ceil(payloadMB * 30_000);
|
|
264
|
-
|
|
264
|
+
// The tight 15s cap is ONLY for small auxiliary requests (e.g. title generation).
|
|
265
|
+
// Larger parallel requests (such as Zed/OMP context compaction with big history)
|
|
266
|
+
// need the full dynamic timeout so they do not time out at 15s.
|
|
267
|
+
if (opts.parallelEscape && !opts.enableThinking && opts.payloadSize < 16_384) {
|
|
265
268
|
return Math.min(15_000, dynamic);
|
|
266
269
|
}
|
|
267
270
|
return dynamic;
|
|
@@ -857,25 +860,33 @@ export async function requestQwenTextInBrowser(
|
|
|
857
860
|
|
|
858
861
|
const evaluateRequest = (page: Page) =>
|
|
859
862
|
page.evaluate(
|
|
860
|
-
async ({ url, method, headers, body, referrer }: {
|
|
863
|
+
async ({ url, method, headers, body, referrer, timeoutMs }: {
|
|
861
864
|
url: string;
|
|
862
865
|
method: "GET" | "POST" | "DELETE";
|
|
863
866
|
headers: Record<string, string>;
|
|
864
867
|
body?: string;
|
|
865
868
|
referrer?: string;
|
|
869
|
+
timeoutMs: number;
|
|
866
870
|
}): Promise<BrowserTextResponse> => {
|
|
867
|
-
const
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
871
|
+
const controller = new AbortController();
|
|
872
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
873
|
+
try {
|
|
874
|
+
const response = await fetch(url, {
|
|
875
|
+
method,
|
|
876
|
+
credentials: "include",
|
|
877
|
+
headers,
|
|
878
|
+
body,
|
|
879
|
+
signal: controller.signal,
|
|
880
|
+
...(referrer ? { referrer } : {}),
|
|
881
|
+
});
|
|
882
|
+
return {
|
|
883
|
+
status: response.status,
|
|
884
|
+
contentType: response.headers.get("content-type") || "",
|
|
885
|
+
raw: await response.text(),
|
|
886
|
+
};
|
|
887
|
+
} finally {
|
|
888
|
+
clearTimeout(timeoutId);
|
|
889
|
+
}
|
|
879
890
|
},
|
|
880
891
|
{
|
|
881
892
|
url,
|
|
@@ -883,6 +894,7 @@ export async function requestQwenTextInBrowser(
|
|
|
883
894
|
headers: browserHeaders,
|
|
884
895
|
body,
|
|
885
896
|
referrer: options.referrer,
|
|
897
|
+
timeoutMs: options.timeoutMs ?? Math.min(config.timeouts.page, 20_000),
|
|
886
898
|
},
|
|
887
899
|
);
|
|
888
900
|
const recoverOnTimeout = !options.noMutexRecovery;
|
package/src/tools/parser.ts
CHANGED
|
@@ -960,8 +960,8 @@ function repairCommonMalformedToolJson(content: string): string {
|
|
|
960
960
|
'$1"arguments": ',
|
|
961
961
|
)
|
|
962
962
|
.replace(
|
|
963
|
-
/([,{]\s*)
|
|
964
|
-
'$1"
|
|
963
|
+
/([,{]\s*)([A-Za-z_][A-Za-z0-9_]*)"\s*:/g,
|
|
964
|
+
'$1"$2":',
|
|
965
965
|
)
|
|
966
966
|
.replace(
|
|
967
967
|
/([,{]\s*)arguments\s*:\s*(?={|\[|")/g,
|