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/routes/images.ts
CHANGED
|
@@ -1,139 +1,139 @@
|
|
|
1
|
-
import type { Context } from "hono";
|
|
2
|
-
import {
|
|
3
|
-
generateImage,
|
|
4
|
-
isSupportedMediaSize,
|
|
5
|
-
MEDIA_SIZE_OPTIONS,
|
|
6
|
-
supportsPromptMediaGeneration,
|
|
7
|
-
} from "../services/media-generation.ts";
|
|
8
|
-
import { logger } from "../core/logger.ts";
|
|
9
|
-
import { sendOpenAIError } from "../api/error-helpers.ts";
|
|
10
|
-
import { ValidationError } from "../core/errors.ts";
|
|
11
|
-
|
|
12
|
-
const DEFAULT_SIZE = "auto";
|
|
13
|
-
|
|
14
|
-
interface ImageDataItem {
|
|
15
|
-
url?: string;
|
|
16
|
-
b64_json?: string;
|
|
17
|
-
revised_prompt?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface ImagesGenerationsRequest {
|
|
21
|
-
model?: unknown;
|
|
22
|
-
prompt?: unknown;
|
|
23
|
-
n?: unknown;
|
|
24
|
-
size?: unknown;
|
|
25
|
-
/** Accepted for OpenAI compatibility but ignored — Qwen has no quality parameter. */
|
|
26
|
-
quality?: unknown;
|
|
27
|
-
response_format?: unknown;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function validationError(message: string, param: string): ValidationError {
|
|
31
|
-
const err = new ValidationError(message);
|
|
32
|
-
err.param = param;
|
|
33
|
-
return err;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function urlToBase64(url: string): Promise<string> {
|
|
37
|
-
const response = await fetch(url);
|
|
38
|
-
if (!response.ok) {
|
|
39
|
-
throw new Error(`Failed to download generated image: HTTP ${response.status}`);
|
|
40
|
-
}
|
|
41
|
-
const buffer = Buffer.from(await response.arrayBuffer());
|
|
42
|
-
return buffer.toString("base64");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export async function imagesGenerations(c: Context): Promise<Response> {
|
|
46
|
-
let body: ImagesGenerationsRequest;
|
|
47
|
-
try {
|
|
48
|
-
body = await c.req.json();
|
|
49
|
-
} catch {
|
|
50
|
-
return sendOpenAIError(c, new ValidationError("Request body must be valid JSON"));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
|
|
54
|
-
if (!prompt) {
|
|
55
|
-
return sendOpenAIError(
|
|
56
|
-
c,
|
|
57
|
-
validationError("`prompt` must be a non-empty string", "prompt"),
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const n = body.n === undefined ? 1 : body.n;
|
|
62
|
-
if (typeof n !== "number" || !Number.isInteger(n) || n < 1) {
|
|
63
|
-
return sendOpenAIError(c, validationError("`n` must be a positive integer", "n"));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const size = body.size === undefined ? DEFAULT_SIZE : body.size;
|
|
67
|
-
if (!isSupportedMediaSize(size)) {
|
|
68
|
-
return sendOpenAIError(
|
|
69
|
-
c,
|
|
70
|
-
validationError(
|
|
71
|
-
`\`size\` must be one of: ${MEDIA_SIZE_OPTIONS.join(", ")}`,
|
|
72
|
-
"size",
|
|
73
|
-
),
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const responseFormat = body.response_format ?? "url";
|
|
78
|
-
if (responseFormat !== "url" && responseFormat !== "b64_json") {
|
|
79
|
-
return sendOpenAIError(
|
|
80
|
-
c,
|
|
81
|
-
validationError('`response_format` must be "url" or "b64_json"', "response_format"),
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const model =
|
|
86
|
-
typeof body.model === "string" && body.model.trim()
|
|
87
|
-
? body.model.trim()
|
|
88
|
-
: "";
|
|
89
|
-
if (!model) {
|
|
90
|
-
return sendOpenAIError(
|
|
91
|
-
c,
|
|
92
|
-
validationError("`model` must be the model selected by the client", "model"),
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
if (!supportsPromptMediaGeneration(model, "image")) {
|
|
96
|
-
return sendOpenAIError(
|
|
97
|
-
c,
|
|
98
|
-
validationError(
|
|
99
|
-
`Model \`${model}\` requires a reference image and is not available through prompt-only image generation yet`,
|
|
100
|
-
"model",
|
|
101
|
-
),
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
logger.info("Image generation request", {
|
|
106
|
-
model,
|
|
107
|
-
n,
|
|
108
|
-
size,
|
|
109
|
-
response_format: responseFormat,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
const results = await Promise.all(
|
|
114
|
-
Array.from({ length: n }, () => generateImage({ model, prompt, size })),
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
const data: ImageDataItem[] = await Promise.all(
|
|
118
|
-
results.map(async (result): Promise<ImageDataItem> => {
|
|
119
|
-
const item: ImageDataItem =
|
|
120
|
-
responseFormat === "b64_json"
|
|
121
|
-
? { b64_json: await urlToBase64(result.url) }
|
|
122
|
-
: { url: result.url };
|
|
123
|
-
if (result.revised_prompt) {
|
|
124
|
-
item.revised_prompt = result.revised_prompt;
|
|
125
|
-
}
|
|
126
|
-
return item;
|
|
127
|
-
}),
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
logger.info("Image generation completed", { model, n });
|
|
131
|
-
return c.json({ created: Math.floor(Date.now() / 1000), data });
|
|
132
|
-
} catch (error) {
|
|
133
|
-
logger.error("Image generation failed", {
|
|
134
|
-
model,
|
|
135
|
-
error: error instanceof Error ? error.message : String(error),
|
|
136
|
-
});
|
|
137
|
-
return sendOpenAIError(c, error, 500);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import {
|
|
3
|
+
generateImage,
|
|
4
|
+
isSupportedMediaSize,
|
|
5
|
+
MEDIA_SIZE_OPTIONS,
|
|
6
|
+
supportsPromptMediaGeneration,
|
|
7
|
+
} from "../services/media-generation.ts";
|
|
8
|
+
import { logger } from "../core/logger.ts";
|
|
9
|
+
import { sendOpenAIError } from "../api/error-helpers.ts";
|
|
10
|
+
import { ValidationError } from "../core/errors.ts";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_SIZE = "auto";
|
|
13
|
+
|
|
14
|
+
interface ImageDataItem {
|
|
15
|
+
url?: string;
|
|
16
|
+
b64_json?: string;
|
|
17
|
+
revised_prompt?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ImagesGenerationsRequest {
|
|
21
|
+
model?: unknown;
|
|
22
|
+
prompt?: unknown;
|
|
23
|
+
n?: unknown;
|
|
24
|
+
size?: unknown;
|
|
25
|
+
/** Accepted for OpenAI compatibility but ignored — Qwen has no quality parameter. */
|
|
26
|
+
quality?: unknown;
|
|
27
|
+
response_format?: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function validationError(message: string, param: string): ValidationError {
|
|
31
|
+
const err = new ValidationError(message);
|
|
32
|
+
err.param = param;
|
|
33
|
+
return err;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function urlToBase64(url: string): Promise<string> {
|
|
37
|
+
const response = await fetch(url);
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(`Failed to download generated image: HTTP ${response.status}`);
|
|
40
|
+
}
|
|
41
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
42
|
+
return buffer.toString("base64");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function imagesGenerations(c: Context): Promise<Response> {
|
|
46
|
+
let body: ImagesGenerationsRequest;
|
|
47
|
+
try {
|
|
48
|
+
body = await c.req.json();
|
|
49
|
+
} catch {
|
|
50
|
+
return sendOpenAIError(c, new ValidationError("Request body must be valid JSON"));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
|
|
54
|
+
if (!prompt) {
|
|
55
|
+
return sendOpenAIError(
|
|
56
|
+
c,
|
|
57
|
+
validationError("`prompt` must be a non-empty string", "prompt"),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const n = body.n === undefined ? 1 : body.n;
|
|
62
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1) {
|
|
63
|
+
return sendOpenAIError(c, validationError("`n` must be a positive integer", "n"));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const size = body.size === undefined ? DEFAULT_SIZE : body.size;
|
|
67
|
+
if (!isSupportedMediaSize(size)) {
|
|
68
|
+
return sendOpenAIError(
|
|
69
|
+
c,
|
|
70
|
+
validationError(
|
|
71
|
+
`\`size\` must be one of: ${MEDIA_SIZE_OPTIONS.join(", ")}`,
|
|
72
|
+
"size",
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const responseFormat = body.response_format ?? "url";
|
|
78
|
+
if (responseFormat !== "url" && responseFormat !== "b64_json") {
|
|
79
|
+
return sendOpenAIError(
|
|
80
|
+
c,
|
|
81
|
+
validationError('`response_format` must be "url" or "b64_json"', "response_format"),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const model =
|
|
86
|
+
typeof body.model === "string" && body.model.trim()
|
|
87
|
+
? body.model.trim()
|
|
88
|
+
: "";
|
|
89
|
+
if (!model) {
|
|
90
|
+
return sendOpenAIError(
|
|
91
|
+
c,
|
|
92
|
+
validationError("`model` must be the model selected by the client", "model"),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (!supportsPromptMediaGeneration(model, "image")) {
|
|
96
|
+
return sendOpenAIError(
|
|
97
|
+
c,
|
|
98
|
+
validationError(
|
|
99
|
+
`Model \`${model}\` requires a reference image and is not available through prompt-only image generation yet`,
|
|
100
|
+
"model",
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
logger.info("Image generation request", {
|
|
106
|
+
model,
|
|
107
|
+
n,
|
|
108
|
+
size,
|
|
109
|
+
response_format: responseFormat,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const results = await Promise.all(
|
|
114
|
+
Array.from({ length: n }, () => generateImage({ model, prompt, size })),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const data: ImageDataItem[] = await Promise.all(
|
|
118
|
+
results.map(async (result): Promise<ImageDataItem> => {
|
|
119
|
+
const item: ImageDataItem =
|
|
120
|
+
responseFormat === "b64_json"
|
|
121
|
+
? { b64_json: await urlToBase64(result.url) }
|
|
122
|
+
: { url: result.url };
|
|
123
|
+
if (result.revised_prompt) {
|
|
124
|
+
item.revised_prompt = result.revised_prompt;
|
|
125
|
+
}
|
|
126
|
+
return item;
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
logger.info("Image generation completed", { model, n });
|
|
131
|
+
return c.json({ created: Math.floor(Date.now() / 1000), data });
|
|
132
|
+
} catch (error) {
|
|
133
|
+
logger.error("Image generation failed", {
|
|
134
|
+
model,
|
|
135
|
+
error: error instanceof Error ? error.message : String(error),
|
|
136
|
+
});
|
|
137
|
+
return sendOpenAIError(c, error, 500);
|
|
138
|
+
}
|
|
139
|
+
}
|