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
package/src/core/accounts.ts
CHANGED
|
@@ -1,186 +1,186 @@
|
|
|
1
|
-
import "dotenv/config";
|
|
2
|
-
import crypto from "crypto";
|
|
3
|
-
import { getDatabase } from "./database.ts";
|
|
4
|
-
import { decrypt, encrypt } from "./crypto-utils.ts";
|
|
5
|
-
|
|
6
|
-
export interface QwenAccount {
|
|
7
|
-
id: string;
|
|
8
|
-
email: string;
|
|
9
|
-
password: string;
|
|
10
|
-
cooldown_until?: number;
|
|
11
|
-
cooldown_reason?: string | null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function generateId(email: string): string {
|
|
15
|
-
return crypto
|
|
16
|
-
.createHash("md5")
|
|
17
|
-
.update(email)
|
|
18
|
-
.digest("hex")
|
|
19
|
-
.replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, "$1-$2-$3-$4-$5");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function parseEnvAccounts(): QwenAccount[] {
|
|
23
|
-
const envAccounts = process.env.QWEN_ACCOUNTS;
|
|
24
|
-
if (!envAccounts) return [];
|
|
25
|
-
|
|
26
|
-
const separator = envAccounts.includes(";") ? ";" : ",";
|
|
27
|
-
|
|
28
|
-
return envAccounts
|
|
29
|
-
.split(separator)
|
|
30
|
-
.map((entry, index) => {
|
|
31
|
-
const trimmed = entry.trim();
|
|
32
|
-
const colonIdx = trimmed.indexOf(":");
|
|
33
|
-
if (colonIdx === -1) {
|
|
34
|
-
console.warn(
|
|
35
|
-
`[Accounts] Invalid QWEN_ACCOUNTS entry at index ${index}: "${trimmed}"`,
|
|
36
|
-
);
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
const email = trimmed.substring(0, colonIdx);
|
|
40
|
-
const password = trimmed.substring(colonIdx + 1);
|
|
41
|
-
if (!email || !password) {
|
|
42
|
-
console.warn(
|
|
43
|
-
`[Accounts] Invalid QWEN_ACCOUNTS entry at index ${index}: "${trimmed}"`,
|
|
44
|
-
);
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
id: generateId(email),
|
|
49
|
-
email: email.trim(),
|
|
50
|
-
password: password.trim(),
|
|
51
|
-
};
|
|
52
|
-
})
|
|
53
|
-
.filter((a): a is QwenAccount => a !== null);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let lastSyncedEnv = "";
|
|
57
|
-
let lastSyncTime = 0;
|
|
58
|
-
const SYNC_INTERVAL = 30_000;
|
|
59
|
-
|
|
60
|
-
function syncEnvAccounts(): void {
|
|
61
|
-
const envAccounts = process.env.QWEN_ACCOUNTS || "";
|
|
62
|
-
const now = Date.now();
|
|
63
|
-
if (envAccounts === lastSyncedEnv && now - lastSyncTime < SYNC_INTERVAL)
|
|
64
|
-
return;
|
|
65
|
-
|
|
66
|
-
lastSyncedEnv = envAccounts;
|
|
67
|
-
lastSyncTime = now;
|
|
68
|
-
|
|
69
|
-
const accounts = parseEnvAccounts();
|
|
70
|
-
if (accounts.length === 0) return;
|
|
71
|
-
|
|
72
|
-
const db = getDatabase();
|
|
73
|
-
const upsert = db.prepare(`
|
|
74
|
-
INSERT INTO accounts (id, email, password) VALUES (?, ?, ?)
|
|
75
|
-
ON CONFLICT(email) DO UPDATE SET password = excluded.password, updated_at = datetime('now')
|
|
76
|
-
`);
|
|
77
|
-
|
|
78
|
-
const sync = db.transaction(() => {
|
|
79
|
-
for (const acc of accounts) {
|
|
80
|
-
upsert.run(acc.id, acc.email, encrypt(acc.password));
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
sync();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
let accountsCache: QwenAccount[] | null = null;
|
|
88
|
-
let accountsCacheTime = 0;
|
|
89
|
-
const ACCOUNTS_CACHE_TTL = 5_000;
|
|
90
|
-
|
|
91
|
-
function getCachedAccounts(): QwenAccount[] {
|
|
92
|
-
syncEnvAccounts();
|
|
93
|
-
|
|
94
|
-
const now = Date.now();
|
|
95
|
-
if (accountsCache && now - accountsCacheTime < ACCOUNTS_CACHE_TTL) {
|
|
96
|
-
return accountsCache;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const db = getDatabase();
|
|
100
|
-
const rows = db
|
|
101
|
-
.prepare(
|
|
102
|
-
"SELECT id, email, password, cooldown_until, cooldown_reason FROM accounts ORDER BY created_at ASC",
|
|
103
|
-
)
|
|
104
|
-
.all() as QwenAccount[];
|
|
105
|
-
|
|
106
|
-
accountsCache = rows.map((row) => ({
|
|
107
|
-
...row,
|
|
108
|
-
password: decrypt(row.password),
|
|
109
|
-
}));
|
|
110
|
-
accountsCacheTime = now;
|
|
111
|
-
return accountsCache;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function loadAccounts(): QwenAccount[] {
|
|
115
|
-
return getCachedAccounts().map((account) => ({
|
|
116
|
-
...account,
|
|
117
|
-
password: "***",
|
|
118
|
-
}));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function invalidateAccountsCache(): void {
|
|
122
|
-
accountsCache = null;
|
|
123
|
-
accountsCacheTime = 0;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function addAccount(
|
|
127
|
-
email: string,
|
|
128
|
-
password: string,
|
|
129
|
-
id?: string,
|
|
130
|
-
): QwenAccount {
|
|
131
|
-
if (!email || typeof email !== "string" || email.trim().length === 0) {
|
|
132
|
-
throw new Error("Email is required");
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const db = getDatabase();
|
|
136
|
-
|
|
137
|
-
const existing = db
|
|
138
|
-
.prepare("SELECT id FROM accounts WHERE email = ?")
|
|
139
|
-
.get(email.trim());
|
|
140
|
-
if (existing) {
|
|
141
|
-
throw new Error("Account with this email already exists");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const newAccount: QwenAccount = {
|
|
145
|
-
id: id || crypto.randomUUID(),
|
|
146
|
-
email: email.trim(),
|
|
147
|
-
password,
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
db.prepare("INSERT INTO accounts (id, email, password) VALUES (?, ?, ?)").run(
|
|
151
|
-
newAccount.id,
|
|
152
|
-
newAccount.email,
|
|
153
|
-
encrypt(newAccount.password),
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
invalidateAccountsCache();
|
|
157
|
-
return newAccount;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export function removeAccount(id: string): boolean {
|
|
161
|
-
const db = getDatabase();
|
|
162
|
-
const result = db.prepare("DELETE FROM accounts WHERE id = ?").run(id);
|
|
163
|
-
invalidateAccountsCache();
|
|
164
|
-
return result.changes > 0;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export function listAccounts(): QwenAccount[] {
|
|
168
|
-
return loadAccounts();
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export function getAccountCredentials(id: string): QwenAccount | undefined {
|
|
172
|
-
const cached = getCachedAccounts();
|
|
173
|
-
return cached.find((a) => a.id === id);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export function updateAccountCooldown(
|
|
177
|
-
id: string,
|
|
178
|
-
cooldownUntil: number,
|
|
179
|
-
reason: string | null,
|
|
180
|
-
): void {
|
|
181
|
-
const db = getDatabase();
|
|
182
|
-
db.prepare(
|
|
183
|
-
"UPDATE accounts SET cooldown_until = ?, cooldown_reason = ? WHERE id = ?",
|
|
184
|
-
).run(cooldownUntil, reason, id);
|
|
185
|
-
invalidateAccountsCache();
|
|
186
|
-
}
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
import { getDatabase } from "./database.ts";
|
|
4
|
+
import { decrypt, encrypt } from "./crypto-utils.ts";
|
|
5
|
+
|
|
6
|
+
export interface QwenAccount {
|
|
7
|
+
id: string;
|
|
8
|
+
email: string;
|
|
9
|
+
password: string;
|
|
10
|
+
cooldown_until?: number;
|
|
11
|
+
cooldown_reason?: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function generateId(email: string): string {
|
|
15
|
+
return crypto
|
|
16
|
+
.createHash("md5")
|
|
17
|
+
.update(email)
|
|
18
|
+
.digest("hex")
|
|
19
|
+
.replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, "$1-$2-$3-$4-$5");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseEnvAccounts(): QwenAccount[] {
|
|
23
|
+
const envAccounts = process.env.QWEN_ACCOUNTS;
|
|
24
|
+
if (!envAccounts) return [];
|
|
25
|
+
|
|
26
|
+
const separator = envAccounts.includes(";") ? ";" : ",";
|
|
27
|
+
|
|
28
|
+
return envAccounts
|
|
29
|
+
.split(separator)
|
|
30
|
+
.map((entry, index) => {
|
|
31
|
+
const trimmed = entry.trim();
|
|
32
|
+
const colonIdx = trimmed.indexOf(":");
|
|
33
|
+
if (colonIdx === -1) {
|
|
34
|
+
console.warn(
|
|
35
|
+
`[Accounts] Invalid QWEN_ACCOUNTS entry at index ${index}: "${trimmed}"`,
|
|
36
|
+
);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const email = trimmed.substring(0, colonIdx);
|
|
40
|
+
const password = trimmed.substring(colonIdx + 1);
|
|
41
|
+
if (!email || !password) {
|
|
42
|
+
console.warn(
|
|
43
|
+
`[Accounts] Invalid QWEN_ACCOUNTS entry at index ${index}: "${trimmed}"`,
|
|
44
|
+
);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
id: generateId(email),
|
|
49
|
+
email: email.trim(),
|
|
50
|
+
password: password.trim(),
|
|
51
|
+
};
|
|
52
|
+
})
|
|
53
|
+
.filter((a): a is QwenAccount => a !== null);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let lastSyncedEnv = "";
|
|
57
|
+
let lastSyncTime = 0;
|
|
58
|
+
const SYNC_INTERVAL = 30_000;
|
|
59
|
+
|
|
60
|
+
function syncEnvAccounts(): void {
|
|
61
|
+
const envAccounts = process.env.QWEN_ACCOUNTS || "";
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
if (envAccounts === lastSyncedEnv && now - lastSyncTime < SYNC_INTERVAL)
|
|
64
|
+
return;
|
|
65
|
+
|
|
66
|
+
lastSyncedEnv = envAccounts;
|
|
67
|
+
lastSyncTime = now;
|
|
68
|
+
|
|
69
|
+
const accounts = parseEnvAccounts();
|
|
70
|
+
if (accounts.length === 0) return;
|
|
71
|
+
|
|
72
|
+
const db = getDatabase();
|
|
73
|
+
const upsert = db.prepare(`
|
|
74
|
+
INSERT INTO accounts (id, email, password) VALUES (?, ?, ?)
|
|
75
|
+
ON CONFLICT(email) DO UPDATE SET password = excluded.password, updated_at = datetime('now')
|
|
76
|
+
`);
|
|
77
|
+
|
|
78
|
+
const sync = db.transaction(() => {
|
|
79
|
+
for (const acc of accounts) {
|
|
80
|
+
upsert.run(acc.id, acc.email, encrypt(acc.password));
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
sync();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let accountsCache: QwenAccount[] | null = null;
|
|
88
|
+
let accountsCacheTime = 0;
|
|
89
|
+
const ACCOUNTS_CACHE_TTL = 5_000;
|
|
90
|
+
|
|
91
|
+
function getCachedAccounts(): QwenAccount[] {
|
|
92
|
+
syncEnvAccounts();
|
|
93
|
+
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
if (accountsCache && now - accountsCacheTime < ACCOUNTS_CACHE_TTL) {
|
|
96
|
+
return accountsCache;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const db = getDatabase();
|
|
100
|
+
const rows = db
|
|
101
|
+
.prepare(
|
|
102
|
+
"SELECT id, email, password, cooldown_until, cooldown_reason FROM accounts ORDER BY created_at ASC",
|
|
103
|
+
)
|
|
104
|
+
.all() as QwenAccount[];
|
|
105
|
+
|
|
106
|
+
accountsCache = rows.map((row) => ({
|
|
107
|
+
...row,
|
|
108
|
+
password: decrypt(row.password),
|
|
109
|
+
}));
|
|
110
|
+
accountsCacheTime = now;
|
|
111
|
+
return accountsCache;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function loadAccounts(): QwenAccount[] {
|
|
115
|
+
return getCachedAccounts().map((account) => ({
|
|
116
|
+
...account,
|
|
117
|
+
password: "***",
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function invalidateAccountsCache(): void {
|
|
122
|
+
accountsCache = null;
|
|
123
|
+
accountsCacheTime = 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function addAccount(
|
|
127
|
+
email: string,
|
|
128
|
+
password: string,
|
|
129
|
+
id?: string,
|
|
130
|
+
): QwenAccount {
|
|
131
|
+
if (!email || typeof email !== "string" || email.trim().length === 0) {
|
|
132
|
+
throw new Error("Email is required");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const db = getDatabase();
|
|
136
|
+
|
|
137
|
+
const existing = db
|
|
138
|
+
.prepare("SELECT id FROM accounts WHERE email = ?")
|
|
139
|
+
.get(email.trim());
|
|
140
|
+
if (existing) {
|
|
141
|
+
throw new Error("Account with this email already exists");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const newAccount: QwenAccount = {
|
|
145
|
+
id: id || crypto.randomUUID(),
|
|
146
|
+
email: email.trim(),
|
|
147
|
+
password,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
db.prepare("INSERT INTO accounts (id, email, password) VALUES (?, ?, ?)").run(
|
|
151
|
+
newAccount.id,
|
|
152
|
+
newAccount.email,
|
|
153
|
+
encrypt(newAccount.password),
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
invalidateAccountsCache();
|
|
157
|
+
return newAccount;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function removeAccount(id: string): boolean {
|
|
161
|
+
const db = getDatabase();
|
|
162
|
+
const result = db.prepare("DELETE FROM accounts WHERE id = ?").run(id);
|
|
163
|
+
invalidateAccountsCache();
|
|
164
|
+
return result.changes > 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function listAccounts(): QwenAccount[] {
|
|
168
|
+
return loadAccounts();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function getAccountCredentials(id: string): QwenAccount | undefined {
|
|
172
|
+
const cached = getCachedAccounts();
|
|
173
|
+
return cached.find((a) => a.id === id);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function updateAccountCooldown(
|
|
177
|
+
id: string,
|
|
178
|
+
cooldownUntil: number,
|
|
179
|
+
reason: string | null,
|
|
180
|
+
): void {
|
|
181
|
+
const db = getDatabase();
|
|
182
|
+
db.prepare(
|
|
183
|
+
"UPDATE accounts SET cooldown_until = ?, cooldown_reason = ? WHERE id = ?",
|
|
184
|
+
).run(cooldownUntil, reason, id);
|
|
185
|
+
invalidateAccountsCache();
|
|
186
|
+
}
|