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/tui/proxy-client.ts
CHANGED
|
@@ -1,326 +1,331 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* QwenProxy TUI - Proxy Data Provider & Live State Client
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { config } from "../core/config.ts";
|
|
6
|
-
import { loadAccounts, type QwenAccount } from "../core/accounts.ts";
|
|
7
|
-
import {
|
|
8
|
-
getAccountCooldownInfo,
|
|
9
|
-
clearAllAccountCooldowns,
|
|
10
|
-
clearAccountCooldown,
|
|
11
|
-
isAccountHeadersReady,
|
|
12
|
-
} from "../core/account-manager.ts";
|
|
13
|
-
import { getAccountConcurrencySnapshot } from "../core/account-concurrency.ts";
|
|
14
|
-
import { getRssUsageSnapshot } from "../core/memory-usage.ts";
|
|
15
|
-
import type { ProxyStatusSnapshot } from "./types.ts";
|
|
16
|
-
|
|
17
|
-
export function maskAccountIdentifier(idOrEmail: string): string {
|
|
18
|
-
if (!idOrEmail) return "unknown";
|
|
19
|
-
if (idOrEmail.includes("@")) {
|
|
20
|
-
const [user, domain] = idOrEmail.split("@");
|
|
21
|
-
const visible = user.slice(0, 2);
|
|
22
|
-
return `${visible}***@${domain}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (idOrEmail.length > 8) {
|
|
26
|
-
return `${idOrEmail.slice(0, 3)}***${idOrEmail.slice(-3)}`;
|
|
27
|
-
}
|
|
28
|
-
return idOrEmail;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function formatUptime(seconds: number): string {
|
|
32
|
-
const hrs = Math.floor(seconds / 3600);
|
|
33
|
-
const mins = Math.floor((seconds % 3600) / 60);
|
|
34
|
-
const secs = Math.floor(seconds % 60);
|
|
35
|
-
const pad2 = (n: number) => n.toString().padStart(2, "0");
|
|
36
|
-
if (hrs > 0) {
|
|
37
|
-
return `${pad2(hrs)}:${pad2(mins)}:${pad2(secs)}`;
|
|
38
|
-
}
|
|
39
|
-
return `${pad2(mins)}:${pad2(secs)}`;
|
|
40
|
-
}
|
|
41
|
-
let cachedAccounts: Array<{
|
|
42
|
-
id: string;
|
|
43
|
-
emailOrName: string;
|
|
44
|
-
priority: number;
|
|
45
|
-
cooldownUntil: number | null;
|
|
46
|
-
onCooldown: boolean;
|
|
47
|
-
remainingCooldownMs: number;
|
|
48
|
-
headersReady: boolean;
|
|
49
|
-
}> = [];
|
|
50
|
-
let lastAccountsFetch = 0;
|
|
51
|
-
let isHealthCheckPending = false;
|
|
52
|
-
let lastOnlineState = false;
|
|
53
|
-
let lastOverallStatus = "offline";
|
|
54
|
-
|
|
55
|
-
export async function fetchProxyStatus(): Promise<ProxyStatusSnapshot> {
|
|
56
|
-
const port = config.server?.port || 7936;
|
|
57
|
-
const configuredHost = config.server?.host;
|
|
58
|
-
const host = configuredHost && configuredHost !== "0.0.0.0" ? configuredHost : "127.0.0.1";
|
|
59
|
-
const uptimeSeconds = Math.floor(process.uptime());
|
|
60
|
-
|
|
61
|
-
// Fast non-blocking health probe
|
|
62
|
-
if (!isHealthCheckPending) {
|
|
63
|
-
isHealthCheckPending = true;
|
|
64
|
-
const controller = new AbortController();
|
|
65
|
-
const timeout = setTimeout(() => controller.abort(), 350);
|
|
66
|
-
fetch(`http://${host}:${port}/health`, { signal: controller.signal })
|
|
67
|
-
.then(async (resp) => {
|
|
68
|
-
clearTimeout(timeout);
|
|
69
|
-
if (resp.ok) {
|
|
70
|
-
lastOnlineState = true;
|
|
71
|
-
const data = (await resp.json()) as any;
|
|
72
|
-
lastOverallStatus = data.status || "healthy";
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
})
|
|
81
|
-
.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (!resp.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (delta
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
"
|
|
320
|
-
"
|
|
321
|
-
"
|
|
322
|
-
"
|
|
323
|
-
"
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
1
|
+
/**
|
|
2
|
+
* QwenProxy TUI - Proxy Data Provider & Live State Client
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { config } from "../core/config.ts";
|
|
6
|
+
import { loadAccounts, type QwenAccount } from "../core/accounts.ts";
|
|
7
|
+
import {
|
|
8
|
+
getAccountCooldownInfo,
|
|
9
|
+
clearAllAccountCooldowns,
|
|
10
|
+
clearAccountCooldown,
|
|
11
|
+
isAccountHeadersReady,
|
|
12
|
+
} from "../core/account-manager.ts";
|
|
13
|
+
import { getAccountConcurrencySnapshot } from "../core/account-concurrency.ts";
|
|
14
|
+
import { getRssUsageSnapshot } from "../core/memory-usage.ts";
|
|
15
|
+
import type { ProxyStatusSnapshot } from "./types.ts";
|
|
16
|
+
|
|
17
|
+
export function maskAccountIdentifier(idOrEmail: string): string {
|
|
18
|
+
if (!idOrEmail) return "unknown";
|
|
19
|
+
if (idOrEmail.includes("@")) {
|
|
20
|
+
const [user, domain] = idOrEmail.split("@");
|
|
21
|
+
const visible = user.slice(0, 2);
|
|
22
|
+
return `${visible}***@${domain}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (idOrEmail.length > 8) {
|
|
26
|
+
return `${idOrEmail.slice(0, 3)}***${idOrEmail.slice(-3)}`;
|
|
27
|
+
}
|
|
28
|
+
return idOrEmail;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function formatUptime(seconds: number): string {
|
|
32
|
+
const hrs = Math.floor(seconds / 3600);
|
|
33
|
+
const mins = Math.floor((seconds % 3600) / 60);
|
|
34
|
+
const secs = Math.floor(seconds % 60);
|
|
35
|
+
const pad2 = (n: number) => n.toString().padStart(2, "0");
|
|
36
|
+
if (hrs > 0) {
|
|
37
|
+
return `${pad2(hrs)}:${pad2(mins)}:${pad2(secs)}`;
|
|
38
|
+
}
|
|
39
|
+
return `${pad2(mins)}:${pad2(secs)}`;
|
|
40
|
+
}
|
|
41
|
+
let cachedAccounts: Array<{
|
|
42
|
+
id: string;
|
|
43
|
+
emailOrName: string;
|
|
44
|
+
priority: number;
|
|
45
|
+
cooldownUntil: number | null;
|
|
46
|
+
onCooldown: boolean;
|
|
47
|
+
remainingCooldownMs: number;
|
|
48
|
+
headersReady: boolean;
|
|
49
|
+
}> = [];
|
|
50
|
+
let lastAccountsFetch = 0;
|
|
51
|
+
let isHealthCheckPending = false;
|
|
52
|
+
let lastOnlineState = false;
|
|
53
|
+
let lastOverallStatus = "offline";
|
|
54
|
+
let lastServerReadyAccounts: Set<string> | null = null;
|
|
55
|
+
export async function fetchProxyStatus(): Promise<ProxyStatusSnapshot> {
|
|
56
|
+
const port = config.server?.port || 7936;
|
|
57
|
+
const configuredHost = config.server?.host;
|
|
58
|
+
const host = configuredHost && configuredHost !== "0.0.0.0" ? configuredHost : "127.0.0.1";
|
|
59
|
+
const uptimeSeconds = Math.floor(process.uptime());
|
|
60
|
+
|
|
61
|
+
// Fast non-blocking health probe
|
|
62
|
+
if (!isHealthCheckPending) {
|
|
63
|
+
isHealthCheckPending = true;
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
const timeout = setTimeout(() => controller.abort(), 350);
|
|
66
|
+
fetch(`http://${host}:${port}/health`, { signal: controller.signal })
|
|
67
|
+
.then(async (resp) => {
|
|
68
|
+
clearTimeout(timeout);
|
|
69
|
+
if (resp.ok) {
|
|
70
|
+
lastOnlineState = true;
|
|
71
|
+
const data = (await resp.json()) as any;
|
|
72
|
+
lastOverallStatus = data.status || "healthy";
|
|
73
|
+
if (Array.isArray(data.readyAccounts)) {
|
|
74
|
+
lastServerReadyAccounts = new Set(data.readyAccounts);
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
lastOnlineState = false;
|
|
78
|
+
lastServerReadyAccounts = null;
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
.catch(() => {
|
|
82
|
+
clearTimeout(timeout);
|
|
83
|
+
lastOnlineState = false;
|
|
84
|
+
})
|
|
85
|
+
.finally(() => {
|
|
86
|
+
isHealthCheckPending = false;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
if (now - lastAccountsFetch > 3000 || cachedAccounts.length === 0) {
|
|
92
|
+
lastAccountsFetch = now;
|
|
93
|
+
let rawAccounts: QwenAccount[] = [];
|
|
94
|
+
try {
|
|
95
|
+
rawAccounts = loadAccounts();
|
|
96
|
+
} catch {
|
|
97
|
+
rawAccounts = [];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
cachedAccounts = rawAccounts.map((acc) => {
|
|
101
|
+
const cooldownInfo = getAccountCooldownInfo(acc.id);
|
|
102
|
+
const onCooldown = Boolean(cooldownInfo?.onCooldown);
|
|
103
|
+
const remainingCooldownMs = cooldownInfo?.remainingMs || 0;
|
|
104
|
+
const headersReady = lastServerReadyAccounts !== null
|
|
105
|
+
? lastServerReadyAccounts.has(acc.id)
|
|
106
|
+
: isAccountHeadersReady(acc.id);
|
|
107
|
+
return {
|
|
108
|
+
id: acc.id,
|
|
109
|
+
emailOrName: maskAccountIdentifier(acc.email || acc.id),
|
|
110
|
+
priority: 1,
|
|
111
|
+
cooldownUntil: acc.cooldown_until || null,
|
|
112
|
+
onCooldown,
|
|
113
|
+
remainingCooldownMs,
|
|
114
|
+
headersReady,
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const accounts = cachedAccounts;
|
|
120
|
+
const online = lastOnlineState;
|
|
121
|
+
const overallStatus = lastOverallStatus;
|
|
122
|
+
|
|
123
|
+
// Concurrency stats
|
|
124
|
+
let activeStreams = 0;
|
|
125
|
+
let waitingStreams = 0;
|
|
126
|
+
try {
|
|
127
|
+
const snapshot = getAccountConcurrencySnapshot();
|
|
128
|
+
for (const item of snapshot) {
|
|
129
|
+
activeStreams += item.active;
|
|
130
|
+
waitingStreams += item.waiting;
|
|
131
|
+
}
|
|
132
|
+
} catch {}
|
|
133
|
+
|
|
134
|
+
// RAM usage
|
|
135
|
+
let rssMb = 0;
|
|
136
|
+
let systemMemoryPct = 0;
|
|
137
|
+
try {
|
|
138
|
+
const rssSnap = getRssUsageSnapshot();
|
|
139
|
+
rssMb = Math.round(rssSnap.rss / (1024 * 1024));
|
|
140
|
+
systemMemoryPct = Math.round(rssSnap.usagePercent * 10) / 10;
|
|
141
|
+
} catch {}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
online,
|
|
145
|
+
port,
|
|
146
|
+
host,
|
|
147
|
+
overallStatus,
|
|
148
|
+
uptimeSeconds,
|
|
149
|
+
rssMb,
|
|
150
|
+
systemMemoryPct,
|
|
151
|
+
activeStreams,
|
|
152
|
+
waitingStreams,
|
|
153
|
+
accounts,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function resetAllCooldowns(): number {
|
|
158
|
+
return clearAllAccountCooldowns();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function resetAccountCooldownById(accountId: string): void {
|
|
162
|
+
clearAccountCooldown(accountId);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface StreamChatOptions {
|
|
166
|
+
model: string;
|
|
167
|
+
reasoning_effort?: "low" | "medium" | "high";
|
|
168
|
+
messages: Array<{ role: "system" | "user" | "assistant"; content: string }>;
|
|
169
|
+
onToken: (text: string) => void;
|
|
170
|
+
onReasoning?: (text: string) => void;
|
|
171
|
+
signal?: AbortSignal;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Streams a chat completion response from the local proxy endpoint.
|
|
176
|
+
*/
|
|
177
|
+
export async function streamChatCompletions(
|
|
178
|
+
options: StreamChatOptions,
|
|
179
|
+
): Promise<{ totalTimeMs: number; ttfbMs: number }> {
|
|
180
|
+
const port = config.server?.port || 7936;
|
|
181
|
+
const configuredHost = config.server?.host;
|
|
182
|
+
const host = configuredHost && configuredHost !== "0.0.0.0" ? configuredHost : "127.0.0.1";
|
|
183
|
+
const apiKey = config.apiKey || "sk-qwenproxy-local";
|
|
184
|
+
|
|
185
|
+
const startTime = Date.now();
|
|
186
|
+
let ttfbMs = 0;
|
|
187
|
+
|
|
188
|
+
let resp: Response;
|
|
189
|
+
try {
|
|
190
|
+
resp = await fetch(`http://${host}:${port}/v1/chat/completions`, {
|
|
191
|
+
method: "POST",
|
|
192
|
+
headers: {
|
|
193
|
+
"Content-Type": "application/json",
|
|
194
|
+
Authorization: `Bearer ${apiKey}`,
|
|
195
|
+
},
|
|
196
|
+
body: JSON.stringify({
|
|
197
|
+
model: options.model,
|
|
198
|
+
reasoning_effort: options.reasoning_effort,
|
|
199
|
+
messages: options.messages,
|
|
200
|
+
stream: true,
|
|
201
|
+
}),
|
|
202
|
+
signal: options.signal,
|
|
203
|
+
});
|
|
204
|
+
} catch (fetchErr: any) {
|
|
205
|
+
if (fetchErr?.name === "AbortError" || options.signal?.aborted) {
|
|
206
|
+
throw fetchErr;
|
|
207
|
+
}
|
|
208
|
+
throw new Error(
|
|
209
|
+
`O servidor QwenProxy está iniciando ou indisponível (:7936). Verifique o status ou a aba [6] Logs.`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!resp.ok) {
|
|
214
|
+
const errText = await resp.text();
|
|
215
|
+
throw new Error(`HTTP ${resp.status}: ${errText}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!resp.body) {
|
|
219
|
+
throw new Error("No response body received from proxy");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const reader = resp.body.getReader();
|
|
223
|
+
const decoder = new TextDecoder();
|
|
224
|
+
let buffer = "";
|
|
225
|
+
|
|
226
|
+
while (true) {
|
|
227
|
+
const { done, value } = await reader.read();
|
|
228
|
+
if (done) break;
|
|
229
|
+
|
|
230
|
+
if (ttfbMs === 0) {
|
|
231
|
+
ttfbMs = Date.now() - startTime;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
buffer += decoder.decode(value, { stream: true });
|
|
235
|
+
const lines = buffer.split("\n");
|
|
236
|
+
buffer = lines.pop() || "";
|
|
237
|
+
|
|
238
|
+
for (const line of lines) {
|
|
239
|
+
const trimmed = line.trim();
|
|
240
|
+
if (!trimmed.startsWith("data:")) continue;
|
|
241
|
+
const dataStr = trimmed.replace(/^data:\s*/, "").trim();
|
|
242
|
+
if (dataStr === "[DONE]") break;
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const parsed = JSON.parse(dataStr);
|
|
246
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
247
|
+
if (!delta) continue;
|
|
248
|
+
|
|
249
|
+
if (delta.reasoning_content && options.onReasoning) {
|
|
250
|
+
options.onReasoning(delta.reasoning_content);
|
|
251
|
+
}
|
|
252
|
+
if (delta.content) {
|
|
253
|
+
options.onToken(delta.content);
|
|
254
|
+
}
|
|
255
|
+
} catch {}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
totalTimeMs: Date.now() - startTime,
|
|
261
|
+
ttfbMs: ttfbMs || Date.now() - startTime,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Fetches all live models dynamically from the running proxy /v1/models catalog.
|
|
267
|
+
*/
|
|
268
|
+
let cachedLiveModels: string[] | null = null;
|
|
269
|
+
let isFetchingLiveModels = false;
|
|
270
|
+
|
|
271
|
+
export async function fetchLiveModels(): Promise<string[]> {
|
|
272
|
+
if (cachedLiveModels && cachedLiveModels.length > 0) {
|
|
273
|
+
return cachedLiveModels;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const port = config.server?.port || 7936;
|
|
277
|
+
const configuredHost = config.server?.host;
|
|
278
|
+
const host = configuredHost && configuredHost !== "0.0.0.0" ? configuredHost : "127.0.0.1";
|
|
279
|
+
const apiKey = config.apiKey || "sk-qwenproxy-local";
|
|
280
|
+
|
|
281
|
+
if (!isFetchingLiveModels) {
|
|
282
|
+
isFetchingLiveModels = true;
|
|
283
|
+
const controller = new AbortController();
|
|
284
|
+
const timeout = setTimeout(() => controller.abort(), 2500);
|
|
285
|
+
fetch(`http://${host}:${port}/v1/models`, {
|
|
286
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
287
|
+
signal: controller.signal,
|
|
288
|
+
})
|
|
289
|
+
.then(async (resp) => {
|
|
290
|
+
clearTimeout(timeout);
|
|
291
|
+
if (resp.ok) {
|
|
292
|
+
const json = (await resp.json()) as any;
|
|
293
|
+
if (Array.isArray(json?.data)) {
|
|
294
|
+
const models = json.data
|
|
295
|
+
.map((m: any) => m.id)
|
|
296
|
+
.filter((id: any): id is string => typeof id === "string" && id.trim().length > 0)
|
|
297
|
+
.filter(
|
|
298
|
+
(id: string) =>
|
|
299
|
+
!id.endsWith("-fast") &&
|
|
300
|
+
!id.endsWith("-thinking") &&
|
|
301
|
+
!id.endsWith("-no-thinking"),
|
|
302
|
+
);
|
|
303
|
+
if (models.length > 0) {
|
|
304
|
+
cachedLiveModels = Array.from(new Set(models));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
.catch(() => {
|
|
310
|
+
clearTimeout(timeout);
|
|
311
|
+
})
|
|
312
|
+
.finally(() => {
|
|
313
|
+
isFetchingLiveModels = false;
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
cachedLiveModels || [
|
|
319
|
+
"qwen3.8-max",
|
|
320
|
+
"qwen3.7-plus",
|
|
321
|
+
"qwen3.7-max",
|
|
322
|
+
"z-image-turbo",
|
|
323
|
+
"qwen-image-3.0-pro",
|
|
324
|
+
"qwen-image-3.0",
|
|
325
|
+
"wan2.7-image-pro",
|
|
326
|
+
"wan2.7-image",
|
|
327
|
+
"wan3.0-video",
|
|
328
|
+
"wan2.7-t2v",
|
|
329
|
+
]
|
|
330
|
+
);
|
|
331
|
+
}
|