qwenproxy-cli 1.3.6 → 1.4.0
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/bin/qwenproxy.js +26 -8
- package/dist/clean-cache.js +5076 -0
- package/dist/delete-chats.js +9859 -0
- package/dist/index.js +33946 -0
- package/dist/login.js +6421 -0
- package/dist/reset-cooldowns.js +5462 -0
- package/dist/sync-clients.js +2824 -0
- package/package.json +7 -3
- package/src/api/server.ts +27 -10
- package/src/core/accounts.ts +2 -1
- package/src/core/config.ts +1 -1
- package/src/core/database.ts +139 -0
- package/src/core/paths.ts +0 -6
- package/src/core/version.ts +22 -0
- package/src/routes/chat/retry-policy.ts +4 -0
- package/src/routes/upload.ts +31 -10
- package/src/services/playwright.ts +393 -90
- package/src/services/qwen-chat-pool.ts +26 -0
- package/src/services/qwen-headers.ts +8 -0
- package/src/services/qwen.ts +105 -37
- package/src/tools/parser.ts +7 -1
- package/src/tui/app.ts +2 -12
- package/src/tui/server-manager.ts +3 -1
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { mapClientModelToQwen } from "../core/model-alias.ts";
|
|
10
10
|
import { qwenUrl } from "./qwen-url.ts";
|
|
11
11
|
import { QwenUpstreamError } from "./qwen-errors.ts";
|
|
12
|
+
import { looksLikeAntiBotChallengeText } from "./media-generation.ts";
|
|
12
13
|
import {
|
|
13
14
|
requestQwenTextInBrowser,
|
|
14
15
|
buildCapturedQwenHeaders,
|
|
@@ -57,6 +58,31 @@ async function createQwenChatSession(
|
|
|
57
58
|
json?.data?.chat?.id;
|
|
58
59
|
|
|
59
60
|
if (!chatId || typeof chatId !== "string") {
|
|
61
|
+
const rawLower = raw.toLowerCase();
|
|
62
|
+
if (looksLikeAntiBotChallengeText(raw)) {
|
|
63
|
+
try {
|
|
64
|
+
const { recoverBaxiaCaptcha } = await import("./captcha-coordinator.ts");
|
|
65
|
+
const solved = await recoverBaxiaCaptcha(accountId, "createQwenChatSession", {
|
|
66
|
+
challengeBody: raw,
|
|
67
|
+
});
|
|
68
|
+
if (solved) {
|
|
69
|
+
return createQwenChatSession(headers, model, accountId, chatMode);
|
|
70
|
+
}
|
|
71
|
+
} catch {}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
json?.data?.code === "Unauthorized" ||
|
|
76
|
+
json?.code === "Unauthorized" ||
|
|
77
|
+
rawLower.includes("unauthorized") ||
|
|
78
|
+
rawLower.includes("permission to access")
|
|
79
|
+
) {
|
|
80
|
+
throw new QwenUpstreamError(
|
|
81
|
+
`Qwen create chat unauthorized: ${raw.substring(0, 300)}`,
|
|
82
|
+
"Unauthorized",
|
|
83
|
+
401,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
60
86
|
throw new QwenUpstreamError(
|
|
61
87
|
`Qwen create chat returned unexpected payload: ${raw.substring(0, 300)}`,
|
|
62
88
|
"CreateChatInvalidResponse",
|
|
@@ -89,6 +89,14 @@ export function buildQwenRequestHeaders(
|
|
|
89
89
|
"sec-ch-ua-platform": opts.secChUaPlatform || '"Windows"',
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
+
const tokenMatch = opts.cookie?.match(/(?:^|;\s*)token=([^;]+)/);
|
|
93
|
+
if (tokenMatch && !headers["Authorization"] && !headers["authorization"]) {
|
|
94
|
+
const bearer = decodeURIComponent(tokenMatch[1].trim());
|
|
95
|
+
if (bearer && bearer !== '""' && bearer !== "null") {
|
|
96
|
+
headers["Authorization"] = `Bearer ${bearer}`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
92
100
|
// The real chat.qwen.ai client sends ONLY bx-v on API requests — the WAF
|
|
93
101
|
// carries bx-ua/bx-umidtoken as browser cookies, not headers. Match that
|
|
94
102
|
// unless QWEN_SEND_BX_UA=true restores the legacy injection.
|
package/src/services/qwen.ts
CHANGED
|
@@ -663,8 +663,18 @@ const QWEN_SAFE_SETTINGS_PATCH = {
|
|
|
663
663
|
autoTags: false,
|
|
664
664
|
largeTextAsFile: false,
|
|
665
665
|
splitLargeChunks: false,
|
|
666
|
+
title: {
|
|
667
|
+
auto: false,
|
|
668
|
+
},
|
|
669
|
+
notificationEnabled: false,
|
|
666
670
|
},
|
|
667
671
|
mcp_remind: false,
|
|
672
|
+
mcp: {
|
|
673
|
+
"code-interpreter": false,
|
|
674
|
+
"fire-crawl": false,
|
|
675
|
+
amap: false,
|
|
676
|
+
"image-generation": false,
|
|
677
|
+
},
|
|
668
678
|
memory: {
|
|
669
679
|
enable_memory: false,
|
|
670
680
|
enable_history_memory: false,
|
|
@@ -680,6 +690,10 @@ const QWEN_SAFE_SETTINGS_PATCH = {
|
|
|
680
690
|
image_edit_tool: false,
|
|
681
691
|
bio: false,
|
|
682
692
|
image_zoom_in_tool: false,
|
|
693
|
+
image_search: false,
|
|
694
|
+
},
|
|
695
|
+
extension: {
|
|
696
|
+
show_guide: false,
|
|
683
697
|
},
|
|
684
698
|
} as const;
|
|
685
699
|
|
|
@@ -813,6 +827,7 @@ function getBrowserFetchHeaders(
|
|
|
813
827
|
const browserAllowedHeaders = new Set([
|
|
814
828
|
"accept",
|
|
815
829
|
"content-type",
|
|
830
|
+
"authorization",
|
|
816
831
|
"bx-ua",
|
|
817
832
|
"bx-umidtoken",
|
|
818
833
|
"bx-v",
|
|
@@ -1573,6 +1588,7 @@ export async function syncQwenRequestPersonalization(
|
|
|
1573
1588
|
existingJson?.data?.ui?.largeTextAsFile === false &&
|
|
1574
1589
|
existingJson?.data?.ui?.splitLargeChunks === false &&
|
|
1575
1590
|
existingJson?.data?.ui?.autoTags === false &&
|
|
1591
|
+
existingJson?.data?.ui?.title?.auto === false &&
|
|
1576
1592
|
existingJson?.data?.mcp_remind === false &&
|
|
1577
1593
|
existingJson?.data?.memory?.enable_memory === false &&
|
|
1578
1594
|
existingJson?.data?.memory?.enable_history_memory === false &&
|
|
@@ -1669,6 +1685,7 @@ export async function syncQwenRequestPersonalization(
|
|
|
1669
1685
|
`[Qwen] Personalization 401 — refreshing session with re-auth and retrying | account=${cacheKey}`,
|
|
1670
1686
|
);
|
|
1671
1687
|
try {
|
|
1688
|
+
currentSettings = null;
|
|
1672
1689
|
const { headers: freshHeaders } = await getQwenHeaders(true, accountId, true);
|
|
1673
1690
|
requestHeaders = buildCapturedQwenHeaders(freshHeaders, {
|
|
1674
1691
|
referer: qwenUrl("/settings/personalization"),
|
|
@@ -1977,53 +1994,104 @@ function formatPublicQwenModel(model: Record<string, unknown>): PublicQwenModel
|
|
|
1977
1994
|
}
|
|
1978
1995
|
|
|
1979
1996
|
export async function deleteAllQwenChats(accountId?: string): Promise<boolean> {
|
|
1980
|
-
let requestHeaders: Record<string, string>;
|
|
1981
1997
|
if (isAuthMockEnabled()) {
|
|
1982
1998
|
const { headers } = await getQwenHeaders(false, accountId);
|
|
1983
|
-
requestHeaders = buildCapturedQwenHeaders(headers, {
|
|
1999
|
+
const requestHeaders = buildCapturedQwenHeaders(headers, {
|
|
1984
2000
|
referer: qwenUrl("/settings/chats"),
|
|
1985
2001
|
});
|
|
1986
|
-
} else {
|
|
1987
|
-
// In live mode, requestQwenTextInBrowser executes inside the authenticated
|
|
1988
|
-
// browser page where session cookies are attached automatically.
|
|
1989
|
-
// Bypassing getQwenHeaders avoids triggering captureQwenHeaders (which sends
|
|
1990
|
-
// a dummy chat completion to intercept anti-fraud tokens not needed for deletions).
|
|
1991
|
-
requestHeaders = {
|
|
1992
|
-
source: "web",
|
|
1993
|
-
version: "0.2.89",
|
|
1994
|
-
timezone: new Date().toString().split(" (")[0],
|
|
1995
|
-
"x-request-id": crypto.randomUUID(),
|
|
1996
|
-
Referer: qwenUrl("/settings/chats"),
|
|
1997
|
-
};
|
|
1998
|
-
}
|
|
1999
|
-
|
|
2000
|
-
const response = await requestQwenTextInBrowser(
|
|
2001
|
-
accountId,
|
|
2002
|
-
"DELETE",
|
|
2003
|
-
"/api/v2/chats/",
|
|
2004
|
-
requestHeaders,
|
|
2005
|
-
undefined,
|
|
2006
|
-
{ referrer: qwenUrl("/settings/chats") },
|
|
2007
|
-
);
|
|
2008
2002
|
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2003
|
+
const response = await requestQwenTextInBrowser(
|
|
2004
|
+
accountId,
|
|
2005
|
+
"DELETE",
|
|
2006
|
+
"/api/v2/chats/",
|
|
2007
|
+
requestHeaders,
|
|
2008
|
+
undefined,
|
|
2009
|
+
{ referrer: qwenUrl("/settings/chats") },
|
|
2015
2010
|
);
|
|
2011
|
+
|
|
2012
|
+
const { raw, json: parsed } = await readJsonTextResponse(response, {
|
|
2013
|
+
strict: true,
|
|
2014
|
+
});
|
|
2015
|
+
if (!response.ok) {
|
|
2016
|
+
throw new Error(
|
|
2017
|
+
`Failed to delete chats from Qwen: ${response.status} ${raw.substring(0, 200)}`,
|
|
2018
|
+
);
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
const success = parsed?.success === true && parsed?.data?.status === true;
|
|
2022
|
+
if (!success) {
|
|
2023
|
+
throw new Error(
|
|
2024
|
+
`Qwen delete chats returned unexpected payload: ${raw.substring(0, 200)}`,
|
|
2025
|
+
);
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
clearAllSessionsForAccount(accountId || "global");
|
|
2029
|
+
return true;
|
|
2016
2030
|
}
|
|
2017
2031
|
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2032
|
+
// Live mode: fetch remote chats and delete them by ID in parallel batches
|
|
2033
|
+
// (Alibaba WAF rejects bare DELETE /api/v2/chats/ with 401; deleting by ID succeeds 100%).
|
|
2034
|
+
try {
|
|
2035
|
+
const { withAccountPage } = await import("./playwright.ts");
|
|
2036
|
+
const result = await withAccountPage(
|
|
2037
|
+
accountId || "global",
|
|
2038
|
+
async (page) => {
|
|
2039
|
+
return page.evaluate(async () => {
|
|
2040
|
+
let totalDeleted = 0;
|
|
2041
|
+
let pageNum = 1;
|
|
2042
|
+
while (true) {
|
|
2043
|
+
const listRes = await fetch(`/api/v2/chats/?page=${pageNum}&exclude_project=true`, {
|
|
2044
|
+
credentials: "include",
|
|
2045
|
+
headers: { accept: "application/json, text/plain, */*" },
|
|
2046
|
+
});
|
|
2047
|
+
const listJson = await listRes.json().catch(() => null);
|
|
2048
|
+
const items = listJson?.data || [];
|
|
2049
|
+
if (!Array.isArray(items) || items.length === 0) break;
|
|
2050
|
+
|
|
2051
|
+
for (let i = 0; i < items.length; i += 5) {
|
|
2052
|
+
const batch = items.slice(i, i + 5);
|
|
2053
|
+
await Promise.all(
|
|
2054
|
+
batch.map(async (chat: any) => {
|
|
2055
|
+
try {
|
|
2056
|
+
await fetch(`/api/v2/chats/${chat.id}`, {
|
|
2057
|
+
method: "DELETE",
|
|
2058
|
+
credentials: "include",
|
|
2059
|
+
headers: { accept: "application/json, text/plain, */*" },
|
|
2060
|
+
});
|
|
2061
|
+
totalDeleted++;
|
|
2062
|
+
} catch {}
|
|
2063
|
+
}),
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
if (items.length < 20) break;
|
|
2068
|
+
}
|
|
2069
|
+
return { success: true, count: totalDeleted };
|
|
2070
|
+
});
|
|
2071
|
+
},
|
|
2072
|
+
60_000,
|
|
2073
|
+
30_000,
|
|
2074
|
+
true,
|
|
2022
2075
|
);
|
|
2023
|
-
}
|
|
2024
2076
|
|
|
2025
|
-
|
|
2026
|
-
|
|
2077
|
+
clearAllSessionsForAccount(accountId || "global");
|
|
2078
|
+
return result?.success === true;
|
|
2079
|
+
} catch (err: any) {
|
|
2080
|
+
try {
|
|
2081
|
+
const chats = await fetchRemoteQwenChats(accountId);
|
|
2082
|
+
if (chats.length === 0) {
|
|
2083
|
+
clearAllSessionsForAccount(accountId || "global");
|
|
2084
|
+
return true;
|
|
2085
|
+
}
|
|
2086
|
+
for (const chat of chats) {
|
|
2087
|
+
await deleteSingleQwenChat(accountId, chat.id).catch(() => false);
|
|
2088
|
+
}
|
|
2089
|
+
clearAllSessionsForAccount(accountId || "global");
|
|
2090
|
+
return true;
|
|
2091
|
+
} catch {
|
|
2092
|
+
return false;
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2027
2095
|
}
|
|
2028
2096
|
|
|
2029
2097
|
export interface RemoteQwenChat {
|
package/src/tools/parser.ts
CHANGED
|
@@ -412,7 +412,13 @@ function findPartialToolOpenIndexOutsideMarkdownCode(
|
|
|
412
412
|
if (!tailLower.includes(">")) {
|
|
413
413
|
for (const name of openNames) {
|
|
414
414
|
const full = `<${name.toLowerCase()}`;
|
|
415
|
-
if (
|
|
415
|
+
if (
|
|
416
|
+
full.startsWith(tailLower) ||
|
|
417
|
+
tailLower.startsWith(full + "=") ||
|
|
418
|
+
tailLower.startsWith(full + " ") ||
|
|
419
|
+
tailLower.startsWith(full + "\t") ||
|
|
420
|
+
tailLower.startsWith(full + ":")
|
|
421
|
+
) {
|
|
416
422
|
return i;
|
|
417
423
|
}
|
|
418
424
|
}
|
package/src/tui/app.ts
CHANGED
|
@@ -7,19 +7,9 @@ import type { TuiView, ProxyStatusSnapshot } from "./types.ts";
|
|
|
7
7
|
import { theme, glyphs, drawBox, stringWidth } from "./theme.ts";
|
|
8
8
|
import { fetchProxyStatus, fetchLiveModels, getCachedLiveModels } from "./proxy-client.ts";
|
|
9
9
|
import { ServerManager } from "./server-manager.ts";
|
|
10
|
-
import
|
|
11
|
-
import path from "node:path";
|
|
12
|
-
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { getAppVersion } from "../core/version.ts";
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
-
const pkgPath = path.resolve(currentDir, "../../package.json");
|
|
18
|
-
if (fs.existsSync(pkgPath)) {
|
|
19
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
20
|
-
if (pkg.version) cachedAppVersion = `v${pkg.version}`;
|
|
21
|
-
}
|
|
22
|
-
} catch {}
|
|
12
|
+
const cachedAppVersion = getAppVersion();
|
|
23
13
|
import { StatusView } from "./views/status-view.ts";
|
|
24
14
|
import { ChatView } from "./views/chat-view.ts";
|
|
25
15
|
import { SyncView } from "./views/sync-view.ts";
|
|
@@ -247,9 +247,11 @@ export class ServerManager {
|
|
|
247
247
|
|
|
248
248
|
// 2. Start in-process
|
|
249
249
|
this.state = "warming";
|
|
250
|
+
const { getAppVersion } = await import("../core/version.ts");
|
|
251
|
+
const appVersion = getAppVersion();
|
|
250
252
|
this.appendLog(
|
|
251
253
|
"INFO",
|
|
252
|
-
`🚀 [Server] Iniciando
|
|
254
|
+
`🚀 [Server] Iniciando QwenProxy ${appVersion} na porta ${port}...`,
|
|
253
255
|
);
|
|
254
256
|
|
|
255
257
|
this.interceptLogs();
|