qwenproxy-cli 1.0.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.
Files changed (109) hide show
  1. package/LICENSE +14 -0
  2. package/README.md +907 -0
  3. package/bin/qwenproxy.js +141 -0
  4. package/package.json +78 -0
  5. package/src/api/error-classifier.ts +159 -0
  6. package/src/api/error-helpers.ts +118 -0
  7. package/src/api/models.ts +261 -0
  8. package/src/api/server.ts +859 -0
  9. package/src/cache/memory-cache.ts +385 -0
  10. package/src/clean-cache.ts +204 -0
  11. package/src/core/account-concurrency.ts +671 -0
  12. package/src/core/account-manager.ts +297 -0
  13. package/src/core/account-priority.ts +163 -0
  14. package/src/core/accounts.ts +186 -0
  15. package/src/core/config.ts +383 -0
  16. package/src/core/crypto-utils.ts +79 -0
  17. package/src/core/database.ts +276 -0
  18. package/src/core/errors.ts +118 -0
  19. package/src/core/logger.ts +269 -0
  20. package/src/core/memory-usage.ts +84 -0
  21. package/src/core/metrics.ts +291 -0
  22. package/src/core/model-alias.ts +77 -0
  23. package/src/core/model-registry.ts +544 -0
  24. package/src/core/mutex.ts +119 -0
  25. package/src/core/paths.ts +199 -0
  26. package/src/core/prompt-limits.ts +214 -0
  27. package/src/core/reasoning-effort.ts +102 -0
  28. package/src/core/stream-registry.ts +96 -0
  29. package/src/core/waf-isolation.ts +117 -0
  30. package/src/core/watchdog.ts +195 -0
  31. package/src/delete-chats.ts +23 -0
  32. package/src/index.ts +64 -0
  33. package/src/login.ts +147 -0
  34. package/src/reset-cooldowns.ts +11 -0
  35. package/src/routes/anthropic/index.ts +355 -0
  36. package/src/routes/anthropic/translate.ts +522 -0
  37. package/src/routes/anthropic/types.ts +154 -0
  38. package/src/routes/anthropic/validation.ts +144 -0
  39. package/src/routes/chat/account.ts +1817 -0
  40. package/src/routes/chat/context.ts +241 -0
  41. package/src/routes/chat/errors.ts +85 -0
  42. package/src/routes/chat/helpers.ts +268 -0
  43. package/src/routes/chat/index.ts +618 -0
  44. package/src/routes/chat/media.ts +285 -0
  45. package/src/routes/chat/retry-policy.ts +754 -0
  46. package/src/routes/chat/stop.ts +98 -0
  47. package/src/routes/chat/streaming.ts +2710 -0
  48. package/src/routes/chat/validation.ts +526 -0
  49. package/src/routes/chat.ts +2 -0
  50. package/src/routes/completions.ts +290 -0
  51. package/src/routes/images.ts +139 -0
  52. package/src/routes/responses/adapter.ts +503 -0
  53. package/src/routes/responses/index.ts +405 -0
  54. package/src/routes/responses/state.ts +230 -0
  55. package/src/routes/responses/streaming.ts +528 -0
  56. package/src/routes/responses/types.ts +285 -0
  57. package/src/routes/responses/validation.ts +202 -0
  58. package/src/routes/upload.ts +731 -0
  59. package/src/routes/videos.ts +214 -0
  60. package/src/services/auth-playwright.ts +173 -0
  61. package/src/services/captcha-coordinator.ts +161 -0
  62. package/src/services/captcha-solver.ts +553 -0
  63. package/src/services/chat-cleanup.ts +80 -0
  64. package/src/services/context-meter.ts +317 -0
  65. package/src/services/fingerprint.ts +242 -0
  66. package/src/services/human-behavior.ts +173 -0
  67. package/src/services/media-generation.ts +1748 -0
  68. package/src/services/playwright.ts +2800 -0
  69. package/src/services/qwen-chat-pool.ts +345 -0
  70. package/src/services/qwen-errors.ts +133 -0
  71. package/src/services/qwen-headers.ts +79 -0
  72. package/src/services/qwen-thread-state.ts +393 -0
  73. package/src/services/qwen-url.ts +19 -0
  74. package/src/services/qwen.ts +3126 -0
  75. package/src/services/session-keeper.ts +88 -0
  76. package/src/services/token-estimation-metrics.ts +118 -0
  77. package/src/sync/claude-code.ts +75 -0
  78. package/src/sync/codex.ts +123 -0
  79. package/src/sync/index.ts +362 -0
  80. package/src/sync/omp.ts +105 -0
  81. package/src/sync/opencode.ts +214 -0
  82. package/src/sync/types.ts +53 -0
  83. package/src/sync/utils.ts +27 -0
  84. package/src/sync-clients.ts +189 -0
  85. package/src/tools/instructions.ts +137 -0
  86. package/src/tools/manifest.ts +81 -0
  87. package/src/tools/parser.ts +2989 -0
  88. package/src/tools/toolcall-tags.ts +142 -0
  89. package/src/tools/types.ts +53 -0
  90. package/src/tui/app.ts +264 -0
  91. package/src/tui/index.ts +61 -0
  92. package/src/tui/markdown.ts +258 -0
  93. package/src/tui/proxy-client.ts +326 -0
  94. package/src/tui/screen.ts +278 -0
  95. package/src/tui/server-manager.ts +270 -0
  96. package/src/tui/theme.ts +432 -0
  97. package/src/tui/types.ts +33 -0
  98. package/src/tui/views/accounts-view.ts +656 -0
  99. package/src/tui/views/chat-view.ts +823 -0
  100. package/src/tui/views/logs-view.ts +413 -0
  101. package/src/tui/views/status-view.ts +204 -0
  102. package/src/tui/views/storage-view.ts +291 -0
  103. package/src/tui/views/sync-view.ts +409 -0
  104. package/src/types/ali-oss.d.ts +32 -0
  105. package/src/utils/context-truncation.ts +84 -0
  106. package/src/utils/json.ts +380 -0
  107. package/src/utils/session-id.ts +37 -0
  108. package/src/utils/tool-call-guard.ts +85 -0
  109. package/src/utils/types.ts +109 -0
@@ -0,0 +1,528 @@
1
+ import crypto from "crypto";
2
+ import type {
3
+ ResponsesOutputMessage,
4
+ ResponsesOutputFunctionCall,
5
+ ResponsesOutputReasoning,
6
+ ResponsesOutputContentPart,
7
+ ResponsesUsage,
8
+ ResponsesStreamEvent,
9
+ } from "./types.ts";
10
+
11
+ /**
12
+ * Convert OpenAI Chat Completions streaming chunks to Responses API streaming events.
13
+ *
14
+ * The Responses API has more granular streaming events than Chat Completions:
15
+ * - response.created
16
+ * - response.in_progress
17
+ * - response.output_item.added
18
+ * - response.content_part.added
19
+ * - response.output_text.delta (multiple)
20
+ * - response.output_text.done
21
+ * - response.content_part.done
22
+ * - response.output_item.done
23
+ * - response.function_call_arguments.delta (for tool calls)
24
+ * - response.function_call_arguments.done
25
+ * - response.completed
26
+ */
27
+
28
+ export interface ResponsesStreamState {
29
+ responseId: string;
30
+ messageId: string;
31
+ reasoningId: string;
32
+ requestModel: string;
33
+ outputIndex: number;
34
+ contentIndex: number;
35
+ currentBlockType: "text" | "function_call" | "reasoning" | null;
36
+ accumulatedText: string;
37
+ accumulatedReasoning: string;
38
+ accumulatedToolCalls: Map<
39
+ number,
40
+ {
41
+ id: string;
42
+ callId: string;
43
+ name: string;
44
+ arguments: string;
45
+ }
46
+ >;
47
+ completedOutput: (
48
+ | ResponsesOutputMessage
49
+ | ResponsesOutputFunctionCall
50
+ | ResponsesOutputReasoning
51
+ )[];
52
+ inputTokens: number;
53
+ /** Real usage from upstream (overwrites estimates) */
54
+ upstreamUsage: {
55
+ inputTokens: number | null;
56
+ outputTokens: number | null;
57
+ cachedTokens: number | null;
58
+ reasoningTokens: number | null;
59
+ };
60
+ contextMeter: ResponsesUsage["context_meter"];
61
+ }
62
+
63
+ export function createStreamState(
64
+ responseId: string,
65
+ requestModel: string,
66
+ ): ResponsesStreamState {
67
+ return {
68
+ responseId,
69
+ messageId: `msg_${crypto.randomBytes(16).toString("hex")}`,
70
+ reasoningId: `rs_${crypto.randomBytes(16).toString("hex")}`,
71
+ requestModel,
72
+ outputIndex: 0,
73
+ contentIndex: 0,
74
+ currentBlockType: null,
75
+ accumulatedText: "",
76
+ accumulatedReasoning: "",
77
+ accumulatedToolCalls: new Map(),
78
+ completedOutput: [],
79
+ inputTokens: 0,
80
+ upstreamUsage: {
81
+ inputTokens: null,
82
+ outputTokens: null,
83
+ cachedTokens: null,
84
+ reasoningTokens: null,
85
+ },
86
+ contextMeter: undefined,
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Process a Chat Completions streaming chunk and emit Responses API events.
92
+ */
93
+ export function processChatChunk(
94
+ chunk: any,
95
+ state: ResponsesStreamState,
96
+ ): ResponsesStreamEvent[] {
97
+ const events: ResponsesStreamEvent[] = [];
98
+
99
+ // Update token count if available (real upstream usage overwrites)
100
+ if (chunk.usage) {
101
+ if (chunk.usage.context_meter) {
102
+ state.contextMeter = chunk.usage.context_meter;
103
+ }
104
+ if (chunk.usage.prompt_tokens !== undefined) {
105
+ state.inputTokens = chunk.usage.prompt_tokens;
106
+ state.upstreamUsage.inputTokens = chunk.usage.prompt_tokens;
107
+ }
108
+ if (chunk.usage.input_tokens !== undefined) {
109
+ state.inputTokens = chunk.usage.input_tokens;
110
+ state.upstreamUsage.inputTokens = chunk.usage.input_tokens;
111
+ }
112
+ if (chunk.usage.completion_tokens !== undefined) {
113
+ state.upstreamUsage.outputTokens = chunk.usage.completion_tokens;
114
+ }
115
+ if (chunk.usage.output_tokens !== undefined) {
116
+ state.upstreamUsage.outputTokens = chunk.usage.output_tokens;
117
+ }
118
+ const cached = chunk.usage.prompt_tokens_details?.cached_tokens
119
+ ?? chunk.usage.input_tokens_details?.cached_tokens;
120
+ if (cached !== undefined) {
121
+ state.upstreamUsage.cachedTokens = cached;
122
+ }
123
+ const reasoning = chunk.usage.completion_tokens_details?.reasoning_tokens
124
+ ?? chunk.usage.output_tokens_details?.reasoning_tokens;
125
+ if (reasoning !== undefined) {
126
+ state.upstreamUsage.reasoningTokens = reasoning;
127
+ }
128
+ }
129
+
130
+ const choice = chunk.choices?.[0];
131
+ if (!choice) return events;
132
+
133
+ const delta = choice.delta ?? {};
134
+
135
+ // Reasoning content (thinking/reasoning from Qwen models)
136
+ if (delta.reasoning_content) {
137
+ // Close non-reasoning block if active
138
+ if (
139
+ state.currentBlockType === "text" ||
140
+ state.currentBlockType === "function_call"
141
+ ) {
142
+ if (state.currentBlockType === "text") {
143
+ events.push(...closeCurrentText(state));
144
+ } else if (state.currentBlockType === "function_call") {
145
+ events.push(...closeCurrentFunctionCall(state));
146
+ }
147
+ }
148
+
149
+ // Start reasoning block if needed
150
+ if (state.currentBlockType !== "reasoning") {
151
+ const reasoningItem: ResponsesOutputReasoning = {
152
+ type: "reasoning",
153
+ id: state.reasoningId,
154
+ summary: [],
155
+ };
156
+ events.push({
157
+ type: "response.output_item.added",
158
+ output_index: state.outputIndex,
159
+ item: reasoningItem,
160
+ });
161
+ // Emit reasoning_summary_part.added
162
+ events.push({
163
+ type: "response.reasoning_summary_part.added",
164
+ item_id: state.reasoningId,
165
+ output_index: state.outputIndex,
166
+ part: { type: "summary_text", text: "" },
167
+ });
168
+ state.currentBlockType = "reasoning";
169
+ }
170
+
171
+ state.accumulatedReasoning += delta.reasoning_content;
172
+ events.push({
173
+ type: "response.reasoning_summary_text.delta",
174
+ item_id: state.reasoningId,
175
+ output_index: state.outputIndex,
176
+ delta: delta.reasoning_content,
177
+ });
178
+ }
179
+
180
+ // Text content
181
+ if (delta.content) {
182
+ // Start text block if needed
183
+ if (state.currentBlockType !== "text") {
184
+ // Close previous block if exists
185
+ if (state.currentBlockType === "function_call") {
186
+ events.push(...closeCurrentFunctionCall(state));
187
+ } else if (state.currentBlockType === "reasoning") {
188
+ events.push(...closeCurrentReasoning(state));
189
+ }
190
+
191
+ // Emit output_item.added for message
192
+ const messageItem: ResponsesOutputMessage = {
193
+ type: "message",
194
+ id: state.messageId,
195
+ role: "assistant",
196
+ status: "in_progress",
197
+ content: [],
198
+ };
199
+ events.push({
200
+ type: "response.output_item.added",
201
+ output_index: state.outputIndex,
202
+ item: messageItem,
203
+ });
204
+
205
+ // Emit content_part.added
206
+ const contentPart: ResponsesOutputContentPart = {
207
+ type: "output_text",
208
+ text: "",
209
+ annotations: [],
210
+ };
211
+ events.push({
212
+ type: "response.content_part.added",
213
+ item_id: state.messageId,
214
+ output_index: state.outputIndex,
215
+ content_index: state.contentIndex,
216
+ part: contentPart,
217
+ });
218
+
219
+ state.currentBlockType = "text";
220
+ }
221
+
222
+ // Emit text delta
223
+ state.accumulatedText += delta.content;
224
+ events.push({
225
+ type: "response.output_text.delta",
226
+ item_id: state.messageId,
227
+ output_index: state.outputIndex,
228
+ content_index: state.contentIndex,
229
+ delta: delta.content,
230
+ });
231
+ }
232
+
233
+ // Tool calls
234
+ if (delta.tool_calls) {
235
+ for (const tc of delta.tool_calls) {
236
+ const index = tc.index;
237
+
238
+ // New tool call
239
+ if (tc.function?.name) {
240
+ // Close previous text block if exists
241
+ if (state.currentBlockType === "text") {
242
+ events.push(...closeCurrentText(state));
243
+ }
244
+
245
+ const callId =
246
+ tc.id || `call_${crypto.randomBytes(12).toString("hex")}`;
247
+ const fcId = `fc_${crypto.randomBytes(12).toString("hex")}`;
248
+
249
+ state.accumulatedToolCalls.set(index, {
250
+ id: fcId,
251
+ callId,
252
+ name: tc.function.name,
253
+ arguments: "",
254
+ });
255
+
256
+ // Emit output_item.added for function_call
257
+ const fcItem: ResponsesOutputFunctionCall = {
258
+ type: "function_call",
259
+ id: fcId,
260
+ call_id: callId,
261
+ name: tc.function.name,
262
+ arguments: "",
263
+ status: "in_progress",
264
+ };
265
+ events.push({
266
+ type: "response.output_item.added",
267
+ output_index: state.outputIndex,
268
+ item: fcItem,
269
+ });
270
+
271
+ state.currentBlockType = "function_call";
272
+ }
273
+
274
+ // Tool call arguments
275
+ if (tc.function?.arguments) {
276
+ const stored = state.accumulatedToolCalls.get(index);
277
+ if (stored) {
278
+ stored.arguments += tc.function.arguments;
279
+ events.push({
280
+ type: "response.function_call_arguments.delta",
281
+ item_id: stored.id,
282
+ output_index: state.outputIndex,
283
+ delta: tc.function.arguments,
284
+ });
285
+ }
286
+ }
287
+ }
288
+ }
289
+
290
+ // Finish reason
291
+ if (choice.finish_reason) {
292
+ // Close current block
293
+ if (state.currentBlockType === "reasoning") {
294
+ events.push(...closeCurrentReasoning(state));
295
+ }
296
+ if (state.currentBlockType === "text") {
297
+ events.push(...closeCurrentText(state));
298
+ } else if (state.currentBlockType === "function_call") {
299
+ events.push(...closeCurrentFunctionCall(state));
300
+ }
301
+ }
302
+
303
+ return events;
304
+ }
305
+
306
+ /**
307
+ * Close the current text content block.
308
+ */
309
+ function closeCurrentText(state: ResponsesStreamState): ResponsesStreamEvent[] {
310
+ const events: ResponsesStreamEvent[] = [];
311
+
312
+ if (state.currentBlockType !== "text") return events;
313
+
314
+ // Emit output_text.done
315
+ events.push({
316
+ type: "response.output_text.done",
317
+ item_id: state.messageId,
318
+ output_index: state.outputIndex,
319
+ content_index: state.contentIndex,
320
+ text: state.accumulatedText,
321
+ });
322
+
323
+ // Emit content_part.done
324
+ const contentPart: ResponsesOutputContentPart = {
325
+ type: "output_text",
326
+ text: state.accumulatedText,
327
+ annotations: [],
328
+ };
329
+ events.push({
330
+ type: "response.content_part.done",
331
+ item_id: state.messageId,
332
+ output_index: state.outputIndex,
333
+ content_index: state.contentIndex,
334
+ part: contentPart,
335
+ });
336
+
337
+ // Emit output_item.done for message
338
+ const messageItem: ResponsesOutputMessage = {
339
+ type: "message",
340
+ id: state.messageId,
341
+ role: "assistant",
342
+ status: "completed",
343
+ content: [contentPart],
344
+ };
345
+ events.push({
346
+ type: "response.output_item.done",
347
+ output_index: state.outputIndex,
348
+ item: messageItem,
349
+ });
350
+ state.completedOutput.push(messageItem);
351
+
352
+ state.currentBlockType = null;
353
+ state.contentIndex++;
354
+ state.outputIndex++;
355
+
356
+ return events;
357
+ }
358
+
359
+ /**
360
+ * Close all current function call blocks.
361
+ */
362
+ function closeCurrentFunctionCall(
363
+ state: ResponsesStreamState,
364
+ ): ResponsesStreamEvent[] {
365
+ const events: ResponsesStreamEvent[] = [];
366
+
367
+ if (state.currentBlockType !== "function_call") return events;
368
+
369
+ // Close all accumulated tool calls
370
+ for (const [_, fc] of state.accumulatedToolCalls) {
371
+ // Emit function_call_arguments.done
372
+ events.push({
373
+ type: "response.function_call_arguments.done",
374
+ item_id: fc.id,
375
+ output_index: state.outputIndex,
376
+ arguments: fc.arguments,
377
+ });
378
+
379
+ // Emit output_item.done for function_call
380
+ const fcItem: ResponsesOutputFunctionCall = {
381
+ type: "function_call",
382
+ id: fc.id,
383
+ call_id: fc.callId,
384
+ name: fc.name,
385
+ arguments: fc.arguments,
386
+ status: "completed",
387
+ };
388
+ events.push({
389
+ type: "response.output_item.done",
390
+ output_index: state.outputIndex,
391
+ item: fcItem,
392
+ });
393
+ state.completedOutput.push(fcItem);
394
+
395
+ state.outputIndex++;
396
+ }
397
+
398
+ state.currentBlockType = null;
399
+ state.accumulatedToolCalls.clear();
400
+
401
+ return events;
402
+ }
403
+
404
+ /**
405
+ * Close the current reasoning block.
406
+ */
407
+ function closeCurrentReasoning(
408
+ state: ResponsesStreamState,
409
+ ): ResponsesStreamEvent[] {
410
+ const events: ResponsesStreamEvent[] = [];
411
+
412
+ if (state.currentBlockType !== "reasoning") return events;
413
+
414
+ // Emit reasoning_summary_text.done
415
+ events.push({
416
+ type: "response.reasoning_summary_text.done",
417
+ item_id: state.reasoningId,
418
+ output_index: state.outputIndex,
419
+ text: state.accumulatedReasoning,
420
+ });
421
+
422
+ // Emit reasoning_summary_part.done
423
+ events.push({
424
+ type: "response.reasoning_summary_part.done",
425
+ item_id: state.reasoningId,
426
+ output_index: state.outputIndex,
427
+ part: { type: "summary_text", text: state.accumulatedReasoning },
428
+ });
429
+
430
+ const reasoningItem: ResponsesOutputReasoning = {
431
+ type: "reasoning",
432
+ id: state.reasoningId,
433
+ summary: [{ type: "summary_text", text: state.accumulatedReasoning }],
434
+ };
435
+ events.push({
436
+ type: "response.output_item.done",
437
+ output_index: state.outputIndex,
438
+ item: reasoningItem,
439
+ });
440
+ state.completedOutput.push(reasoningItem);
441
+
442
+ state.currentBlockType = null;
443
+ state.outputIndex++;
444
+
445
+ return events;
446
+ }
447
+
448
+ /**
449
+ * Build the final output items from accumulated stream state.
450
+ */
451
+ export function buildFinalOutput(
452
+ state: ResponsesStreamState,
453
+ ): (
454
+ | ResponsesOutputMessage
455
+ | ResponsesOutputFunctionCall
456
+ | ResponsesOutputReasoning
457
+ )[] {
458
+ const output: (
459
+ | ResponsesOutputMessage
460
+ | ResponsesOutputFunctionCall
461
+ | ResponsesOutputReasoning
462
+ )[] = [...state.completedOutput];
463
+
464
+ // Add an open reasoning block if the stream ended without a finish_reason
465
+ if (state.currentBlockType === "reasoning" && state.accumulatedReasoning) {
466
+ output.push({
467
+ type: "reasoning",
468
+ id: state.reasoningId,
469
+ summary: [{ type: "summary_text", text: state.accumulatedReasoning }],
470
+ });
471
+ }
472
+
473
+ // Add an open text block if the stream ended without a finish_reason
474
+ if (state.currentBlockType === "text" && state.accumulatedText) {
475
+ output.push({
476
+ type: "message",
477
+ id: state.messageId,
478
+ role: "assistant",
479
+ status: "completed",
480
+ content: [
481
+ {
482
+ type: "output_text",
483
+ text: state.accumulatedText,
484
+ annotations: [],
485
+ },
486
+ ],
487
+ });
488
+ }
489
+
490
+ // Add open function calls if the stream ended without a finish_reason
491
+ if (state.currentBlockType === "function_call") {
492
+ for (const [, fc] of state.accumulatedToolCalls) {
493
+ output.push({
494
+ type: "function_call",
495
+ id: fc.id,
496
+ call_id: fc.callId,
497
+ name: fc.name,
498
+ arguments: fc.arguments,
499
+ status: "completed",
500
+ });
501
+ }
502
+ }
503
+
504
+ return output;
505
+ }
506
+
507
+ /**
508
+ * Build final usage from stream state — always includes details (Grok/serde fix).
509
+ */
510
+ export function buildFinalUsage(
511
+ state: ResponsesStreamState,
512
+ completionTokens: number,
513
+ ): ResponsesUsage {
514
+ const inputTokens = state.upstreamUsage.inputTokens ?? state.inputTokens;
515
+ const outputTokens = state.upstreamUsage.outputTokens ?? completionTokens;
516
+ return {
517
+ input_tokens: inputTokens,
518
+ output_tokens: outputTokens,
519
+ total_tokens: inputTokens + outputTokens,
520
+ input_tokens_details: {
521
+ cached_tokens: state.upstreamUsage.cachedTokens ?? 0,
522
+ },
523
+ output_tokens_details: {
524
+ reasoning_tokens: state.upstreamUsage.reasoningTokens ?? 0,
525
+ },
526
+ ...(state.contextMeter ? { context_meter: state.contextMeter } : {}),
527
+ };
528
+ }