ai-zero-token 2.0.2 → 2.0.4
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/CHANGELOG.md +20 -0
- package/README.md +31 -6
- package/README.zh-CN.md +31 -6
- package/admin-ui/dist/assets/accounts-CTjk9c4F.js +4 -0
- package/admin-ui/dist/assets/{docs-CihX3Xsm.js → docs-oNIugCIL.js} +3 -3
- package/admin-ui/dist/assets/{image-bed-BGjlDLks.js → image-bed-CQtIhjg_.js} +1 -1
- package/admin-ui/dist/assets/index-By4r-wy3.css +1 -0
- package/admin-ui/dist/assets/index-rgcJgVAu.js +10 -0
- package/admin-ui/dist/assets/{launch-BWw7Odq7.js → launch-B-2Zdz9m.js} +1 -1
- package/admin-ui/dist/assets/logs-JFuSf56b.js +1 -0
- package/admin-ui/dist/assets/{network-detect-cUdjg4zk.js → network-detect-SfvK6uhx.js} +1 -1
- package/admin-ui/dist/assets/{overview-CsjVVcvi.js → overview-X_WodIqE.js} +1 -1
- package/admin-ui/dist/assets/settings-0eXUAvcm.js +1 -0
- package/admin-ui/dist/assets/{tester-BIvH_8DY.js → tester-ocpF053C.js} +1 -1
- package/admin-ui/dist/index.html +2 -2
- package/build/mac-install-guide.txt +25 -0
- package/build/tray-icon-template.png +0 -0
- package/dist/core/providers/openai-codex/chat.js +77 -0
- package/dist/core/services/auth-service.js +88 -12
- package/dist/core/services/chat-service.js +1 -0
- package/dist/core/services/config-service.js +87 -23
- package/dist/core/services/version-service.js +1 -1
- package/dist/core/store/profile-store.js +73 -32
- package/dist/core/store/settings-store.js +14 -0
- package/dist/desktop/main.js +616 -15
- package/dist/server/app.js +512 -58
- package/dist/server/index.js +2 -1
- package/docs/API_USAGE.md +65 -1
- package/docs/DESKTOP_RELEASE.md +48 -3
- package/package.json +33 -2
- package/admin-ui/dist/assets/accounts-Ddq82u6R.js +0 -1
- package/admin-ui/dist/assets/index-CX8e0-BB.js +0 -10
- package/admin-ui/dist/assets/index-ywn2Jwpu.css +0 -1
- package/admin-ui/dist/assets/logs-DDdgDVwo.js +0 -1
- package/admin-ui/dist/assets/settings-Be99HpDD.js +0 -1
package/dist/server/app.js
CHANGED
|
@@ -11,6 +11,7 @@ import { requestText } from "../core/providers/http-client.js";
|
|
|
11
11
|
const packageRoot = path.dirname(fileURLToPath(new URL("../../package.json", import.meta.url)));
|
|
12
12
|
const adminUiDistDir = path.join(packageRoot, "admin-ui", "dist");
|
|
13
13
|
const adminUiIndexPath = path.join(adminUiDistDir, "index.html");
|
|
14
|
+
const MAX_GATEWAY_REQUEST_LOGS = 100;
|
|
14
15
|
const assetContentTypes = {
|
|
15
16
|
".css": "text/css; charset=utf-8",
|
|
16
17
|
".gif": "image/gif",
|
|
@@ -115,6 +116,9 @@ const settingsUpdateSchema = z.object({
|
|
|
115
116
|
autoSwitch: z.object({
|
|
116
117
|
enabled: z.boolean()
|
|
117
118
|
}).optional(),
|
|
119
|
+
runtime: z.object({
|
|
120
|
+
quotaSyncConcurrency: z.number().int().min(1).max(32).optional()
|
|
121
|
+
}).optional(),
|
|
118
122
|
server: z.object({
|
|
119
123
|
port: z.number().int().min(1).max(65535)
|
|
120
124
|
}).optional()
|
|
@@ -129,9 +133,15 @@ const proxyTestSchema = z.object({
|
|
|
129
133
|
const profileActionSchema = z.object({
|
|
130
134
|
profileId: z.string().min(1)
|
|
131
135
|
});
|
|
136
|
+
const profileRemoveBatchSchema = z.object({
|
|
137
|
+
profileIds: z.array(z.string().min(1)).min(1)
|
|
138
|
+
});
|
|
132
139
|
const profileImportSchema = z.object({
|
|
133
140
|
profile: z.unknown()
|
|
134
141
|
});
|
|
142
|
+
const runtimeRefreshSchema = z.object({
|
|
143
|
+
staleOnly: z.boolean().optional()
|
|
144
|
+
});
|
|
135
145
|
const profileExportSchema = z.object({
|
|
136
146
|
profileId: z.string().min(1).optional(),
|
|
137
147
|
profileIds: z.array(z.string().min(1)).optional(),
|
|
@@ -189,13 +199,6 @@ const imageEditsBodySchema = z.object({
|
|
|
189
199
|
response_format: z.enum(["b64_json", "url"]).optional(),
|
|
190
200
|
user: z.string().optional()
|
|
191
201
|
}).passthrough();
|
|
192
|
-
const chatCompletionExcludedKeys = /* @__PURE__ */ new Set([
|
|
193
|
-
"messages",
|
|
194
|
-
"n",
|
|
195
|
-
"stream",
|
|
196
|
-
"max_tokens",
|
|
197
|
-
"max_completion_tokens"
|
|
198
|
-
]);
|
|
199
202
|
function extractTextInput(input) {
|
|
200
203
|
if (typeof input === "undefined") {
|
|
201
204
|
return "";
|
|
@@ -233,7 +236,20 @@ function normalizeChatRole(role) {
|
|
|
233
236
|
}
|
|
234
237
|
return role ?? "user";
|
|
235
238
|
}
|
|
236
|
-
function
|
|
239
|
+
function safeJsonStringify(value) {
|
|
240
|
+
if (typeof value === "string") {
|
|
241
|
+
return value;
|
|
242
|
+
}
|
|
243
|
+
if (typeof value === "undefined" || value === null) {
|
|
244
|
+
return "";
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
return JSON.stringify(value);
|
|
248
|
+
} catch {
|
|
249
|
+
return String(value);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function normalizeChatContentPart(part, textType) {
|
|
237
253
|
if (part.type === "image_url") {
|
|
238
254
|
const url = typeof part.image_url === "string" ? part.image_url : part.image_url?.url;
|
|
239
255
|
if (!url) {
|
|
@@ -249,37 +265,220 @@ function normalizeChatContentPart(part) {
|
|
|
249
265
|
}
|
|
250
266
|
const text = typeof part.text === "string" ? part.text : "";
|
|
251
267
|
return {
|
|
252
|
-
type:
|
|
268
|
+
type: textType,
|
|
253
269
|
text
|
|
254
270
|
};
|
|
255
271
|
}
|
|
256
|
-
function normalizeChatContent(content) {
|
|
272
|
+
function normalizeChatContent(content, role) {
|
|
273
|
+
const textType = role === "assistant" ? "output_text" : "input_text";
|
|
257
274
|
if (typeof content === "string") {
|
|
258
|
-
return [{ type:
|
|
275
|
+
return [{ type: textType, text: content }];
|
|
259
276
|
}
|
|
260
277
|
if (!Array.isArray(content) || content.length === 0) {
|
|
261
|
-
return [{ type:
|
|
278
|
+
return [{ type: textType, text: "" }];
|
|
262
279
|
}
|
|
263
|
-
return content.map((part) => normalizeChatContentPart(part));
|
|
280
|
+
return content.map((part) => normalizeChatContentPart(part, textType));
|
|
264
281
|
}
|
|
265
282
|
function normalizeChatMessages(messages) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
283
|
+
const normalized = [];
|
|
284
|
+
for (const message of messages) {
|
|
285
|
+
const record = message;
|
|
286
|
+
if (message.role === "tool") {
|
|
287
|
+
normalized.push({
|
|
288
|
+
type: "function_call_output",
|
|
289
|
+
call_id: message.tool_call_id,
|
|
290
|
+
output: typeof message.content === "string" ? message.content : safeJsonStringify(message.content)
|
|
291
|
+
});
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
normalized.push({
|
|
295
|
+
role: normalizeChatRole(message.role),
|
|
296
|
+
content: normalizeChatContent(message.content, message.role),
|
|
297
|
+
...message.name ? { name: message.name } : {},
|
|
298
|
+
...message.tool_call_id ? { tool_call_id: message.tool_call_id } : {}
|
|
299
|
+
});
|
|
300
|
+
const toolCalls = Array.isArray(record.tool_calls) ? record.tool_calls : [];
|
|
301
|
+
for (const toolCall of toolCalls) {
|
|
302
|
+
const call = toolCall && typeof toolCall === "object" ? toolCall : {};
|
|
303
|
+
const fn = call.function && typeof call.function === "object" ? call.function : {};
|
|
304
|
+
const name = typeof fn.name === "string" ? fn.name : void 0;
|
|
305
|
+
if (!name) {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
normalized.push({
|
|
309
|
+
type: "function_call",
|
|
310
|
+
call_id: typeof call.id === "string" ? call.id : `call_${normalized.length}`,
|
|
311
|
+
name,
|
|
312
|
+
arguments: safeJsonStringify(fn.arguments)
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return normalized;
|
|
317
|
+
}
|
|
318
|
+
function normalizeChatTools(tools) {
|
|
319
|
+
if (!tools) {
|
|
320
|
+
return void 0;
|
|
321
|
+
}
|
|
322
|
+
return tools.map((tool) => {
|
|
323
|
+
if (!tool || typeof tool !== "object") {
|
|
324
|
+
return tool;
|
|
325
|
+
}
|
|
326
|
+
const record = tool;
|
|
327
|
+
const fn = record.function && typeof record.function === "object" ? record.function : null;
|
|
328
|
+
if (record.type !== "function" || !fn) {
|
|
329
|
+
return tool;
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
type: "function",
|
|
333
|
+
name: fn.name,
|
|
334
|
+
description: fn.description,
|
|
335
|
+
parameters: fn.parameters
|
|
336
|
+
};
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
function normalizeChatToolChoice(toolChoice) {
|
|
340
|
+
if (!toolChoice || typeof toolChoice !== "object") {
|
|
341
|
+
return toolChoice;
|
|
342
|
+
}
|
|
343
|
+
const record = toolChoice;
|
|
344
|
+
const fn = record.function && typeof record.function === "object" ? record.function : null;
|
|
345
|
+
if (record.type === "function" && fn && typeof fn.name === "string") {
|
|
346
|
+
return {
|
|
347
|
+
type: "function",
|
|
348
|
+
name: fn.name
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
return toolChoice;
|
|
352
|
+
}
|
|
353
|
+
function normalizeReasoningEffort(value) {
|
|
354
|
+
if (value === "low" || value === "medium" || value === "high") {
|
|
355
|
+
return value;
|
|
356
|
+
}
|
|
357
|
+
if (value === "minimal") {
|
|
358
|
+
return "low";
|
|
359
|
+
}
|
|
360
|
+
if (value === "xhigh") {
|
|
361
|
+
return "high";
|
|
362
|
+
}
|
|
363
|
+
return void 0;
|
|
364
|
+
}
|
|
365
|
+
function normalizeChatReasoning(data) {
|
|
366
|
+
const record = data;
|
|
367
|
+
const existing = record.reasoning;
|
|
368
|
+
if (existing && typeof existing === "object" && !Array.isArray(existing)) {
|
|
369
|
+
return existing;
|
|
370
|
+
}
|
|
371
|
+
const effort = normalizeReasoningEffort(record.reasoning_effort);
|
|
372
|
+
return effort ? { effort } : void 0;
|
|
373
|
+
}
|
|
374
|
+
function truncateForLog(value, maxLength = 300) {
|
|
375
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
376
|
+
if (normalized.length <= maxLength) {
|
|
377
|
+
return normalized;
|
|
378
|
+
}
|
|
379
|
+
return `${normalized.slice(0, maxLength)}...`;
|
|
380
|
+
}
|
|
381
|
+
function extractChatMessageText(message) {
|
|
382
|
+
if (typeof message.content === "string") {
|
|
383
|
+
return message.content;
|
|
384
|
+
}
|
|
385
|
+
if (!Array.isArray(message.content)) {
|
|
386
|
+
return "";
|
|
387
|
+
}
|
|
388
|
+
return message.content.map((part) => typeof part.text === "string" ? part.text : part.image_url ? "[image]" : "").filter(Boolean).join("\n");
|
|
389
|
+
}
|
|
390
|
+
function countRoles(messages) {
|
|
391
|
+
const counts = {};
|
|
392
|
+
for (const message of messages) {
|
|
393
|
+
const role = message.role ?? "user";
|
|
394
|
+
counts[role] = (counts[role] ?? 0) + 1;
|
|
395
|
+
}
|
|
396
|
+
return counts;
|
|
397
|
+
}
|
|
398
|
+
function summarizeRecentMessages(messages) {
|
|
399
|
+
return messages.slice(-8).map((message) => ({
|
|
400
|
+
role: message.role ?? "user",
|
|
401
|
+
textPreview: truncateForLog(extractChatMessageText(message), 180),
|
|
402
|
+
toolCallId: message.tool_call_id
|
|
271
403
|
}));
|
|
272
404
|
}
|
|
405
|
+
function summarizeToolNames(tools) {
|
|
406
|
+
if (!tools) {
|
|
407
|
+
return [];
|
|
408
|
+
}
|
|
409
|
+
return tools.map((tool) => {
|
|
410
|
+
if (!tool || typeof tool !== "object") {
|
|
411
|
+
return "";
|
|
412
|
+
}
|
|
413
|
+
const record = tool;
|
|
414
|
+
const fn = record.function && typeof record.function === "object" ? record.function : null;
|
|
415
|
+
return typeof fn?.name === "string" ? fn.name : typeof record.name === "string" ? record.name : "";
|
|
416
|
+
}).filter(Boolean);
|
|
417
|
+
}
|
|
418
|
+
function summarizeChatCompletionsRequest(data) {
|
|
419
|
+
const lastUserMessage = [...data.messages].reverse().find((message) => (message.role ?? "user") === "user");
|
|
420
|
+
const toolNames = summarizeToolNames(data.tools);
|
|
421
|
+
return {
|
|
422
|
+
endpoint: "/v1/chat/completions",
|
|
423
|
+
model: data.model ?? "default",
|
|
424
|
+
stream: data.stream ?? false,
|
|
425
|
+
messageCount: data.messages.length,
|
|
426
|
+
roleCounts: countRoles(data.messages),
|
|
427
|
+
recentMessages: summarizeRecentMessages(data.messages),
|
|
428
|
+
lastUserTextPreview: lastUserMessage ? truncateForLog(extractChatMessageText(lastUserMessage)) : "",
|
|
429
|
+
toolCount: data.tools?.length ?? 0,
|
|
430
|
+
toolNames: toolNames.slice(0, 50),
|
|
431
|
+
toolNamesTruncated: toolNames.length > 50,
|
|
432
|
+
toolChoice: typeof data.tool_choice === "undefined" ? "default" : typeof data.tool_choice,
|
|
433
|
+
parallelToolCalls: data.parallel_tool_calls,
|
|
434
|
+
hasReasoning: Boolean(data.reasoning || data.reasoning_effort),
|
|
435
|
+
maxTokens: data.max_completion_tokens ?? data.max_tokens
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
function summarizeCodexChatBody(body) {
|
|
439
|
+
const toolNames = summarizeToolNames(Array.isArray(body.tools) ? body.tools : void 0);
|
|
440
|
+
return {
|
|
441
|
+
keys: Object.keys(body).sort(),
|
|
442
|
+
model: body.model ?? "default",
|
|
443
|
+
stream: body.stream,
|
|
444
|
+
store: body.store,
|
|
445
|
+
inputItems: Array.isArray(body.input) ? body.input.length : void 0,
|
|
446
|
+
tools: Array.isArray(body.tools) ? body.tools.length : void 0,
|
|
447
|
+
toolNames: toolNames.slice(0, 50),
|
|
448
|
+
toolNamesTruncated: toolNames.length > 50,
|
|
449
|
+
toolChoice: typeof body.tool_choice === "undefined" ? "default" : typeof body.tool_choice,
|
|
450
|
+
parallelToolCalls: body.parallel_tool_calls,
|
|
451
|
+
hasReasoning: Boolean(body.reasoning)
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function profileLogLabel(profile) {
|
|
455
|
+
return profile?.email || profile?.accountId || profile?.profileId || "-";
|
|
456
|
+
}
|
|
457
|
+
function requestSourceFromUserAgent(userAgent) {
|
|
458
|
+
return typeof userAgent === "string" && userAgent.toLowerCase().includes("openclaw") ? "OpenClaw" : "API";
|
|
459
|
+
}
|
|
273
460
|
function createChatCompletionsCodexBody(data) {
|
|
274
|
-
const body =
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
body.
|
|
461
|
+
const body = {
|
|
462
|
+
store: false,
|
|
463
|
+
stream: true,
|
|
464
|
+
input: normalizeChatMessages(data.messages)
|
|
465
|
+
};
|
|
466
|
+
if (data.model) {
|
|
467
|
+
body.model = data.model;
|
|
468
|
+
}
|
|
469
|
+
if (typeof data.parallel_tool_calls === "boolean") {
|
|
470
|
+
body.parallel_tool_calls = data.parallel_tool_calls;
|
|
471
|
+
}
|
|
472
|
+
if (data.tools) {
|
|
473
|
+
body.tools = normalizeChatTools(data.tools);
|
|
474
|
+
}
|
|
475
|
+
if (typeof data.tool_choice !== "undefined") {
|
|
476
|
+
body.tool_choice = normalizeChatToolChoice(data.tool_choice);
|
|
477
|
+
}
|
|
478
|
+
const reasoning = normalizeChatReasoning(data);
|
|
479
|
+
if (reasoning) {
|
|
480
|
+
body.reasoning = reasoning;
|
|
281
481
|
}
|
|
282
|
-
body.input = normalizeChatMessages(data.messages);
|
|
283
482
|
return body;
|
|
284
483
|
}
|
|
285
484
|
function summarizeImageRequestForLog(body) {
|
|
@@ -364,6 +563,7 @@ function buildResponseApiBody(result, includeRaw) {
|
|
|
364
563
|
return responseBody;
|
|
365
564
|
}
|
|
366
565
|
function buildChatCompletionsBody(result) {
|
|
566
|
+
const hasToolCalls = result.toolCalls.length > 0;
|
|
367
567
|
const body = {
|
|
368
568
|
id: `chatcmpl_${randomUUID().replace(/-/g, "")}`,
|
|
369
569
|
object: "chat.completion",
|
|
@@ -372,10 +572,11 @@ function buildChatCompletionsBody(result) {
|
|
|
372
572
|
choices: [
|
|
373
573
|
{
|
|
374
574
|
index: 0,
|
|
375
|
-
finish_reason: "stop",
|
|
575
|
+
finish_reason: hasToolCalls ? "tool_calls" : "stop",
|
|
376
576
|
message: {
|
|
377
577
|
role: "assistant",
|
|
378
|
-
content: result.text
|
|
578
|
+
content: hasToolCalls ? result.text || null : result.text,
|
|
579
|
+
...hasToolCalls ? { tool_calls: result.toolCalls } : {}
|
|
379
580
|
}
|
|
380
581
|
}
|
|
381
582
|
]
|
|
@@ -385,6 +586,79 @@ function buildChatCompletionsBody(result) {
|
|
|
385
586
|
}
|
|
386
587
|
return body;
|
|
387
588
|
}
|
|
589
|
+
function writeChatCompletionsSseEvent(reply, data) {
|
|
590
|
+
reply.raw.write(`data: ${JSON.stringify(data)}
|
|
591
|
+
|
|
592
|
+
`);
|
|
593
|
+
}
|
|
594
|
+
function buildChatCompletionChunk(params) {
|
|
595
|
+
return {
|
|
596
|
+
id: params.id,
|
|
597
|
+
object: "chat.completion.chunk",
|
|
598
|
+
created: params.created,
|
|
599
|
+
model: params.model,
|
|
600
|
+
choices: [
|
|
601
|
+
{
|
|
602
|
+
index: 0,
|
|
603
|
+
delta: params.delta,
|
|
604
|
+
finish_reason: params.finishReason ?? null
|
|
605
|
+
}
|
|
606
|
+
]
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
function sendChatCompletionsStream(reply, result) {
|
|
610
|
+
const id = `chatcmpl_${randomUUID().replace(/-/g, "")}`;
|
|
611
|
+
const created = Math.floor(Date.now() / 1e3);
|
|
612
|
+
reply.raw.writeHead(200, {
|
|
613
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
614
|
+
"Cache-Control": "no-cache, no-transform",
|
|
615
|
+
Connection: "keep-alive",
|
|
616
|
+
"X-Accel-Buffering": "no"
|
|
617
|
+
});
|
|
618
|
+
writeChatCompletionsSseEvent(reply, buildChatCompletionChunk({
|
|
619
|
+
id,
|
|
620
|
+
created,
|
|
621
|
+
model: result.model,
|
|
622
|
+
delta: { role: "assistant" }
|
|
623
|
+
}));
|
|
624
|
+
if (result.text) {
|
|
625
|
+
writeChatCompletionsSseEvent(reply, buildChatCompletionChunk({
|
|
626
|
+
id,
|
|
627
|
+
created,
|
|
628
|
+
model: result.model,
|
|
629
|
+
delta: { content: result.text }
|
|
630
|
+
}));
|
|
631
|
+
}
|
|
632
|
+
result.toolCalls.forEach((toolCall, index) => {
|
|
633
|
+
writeChatCompletionsSseEvent(reply, buildChatCompletionChunk({
|
|
634
|
+
id,
|
|
635
|
+
created,
|
|
636
|
+
model: result.model,
|
|
637
|
+
delta: {
|
|
638
|
+
tool_calls: [
|
|
639
|
+
{
|
|
640
|
+
index,
|
|
641
|
+
id: toolCall.id,
|
|
642
|
+
type: toolCall.type,
|
|
643
|
+
function: {
|
|
644
|
+
name: toolCall.function.name,
|
|
645
|
+
arguments: toolCall.function.arguments
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
]
|
|
649
|
+
}
|
|
650
|
+
}));
|
|
651
|
+
});
|
|
652
|
+
writeChatCompletionsSseEvent(reply, buildChatCompletionChunk({
|
|
653
|
+
id,
|
|
654
|
+
created,
|
|
655
|
+
model: result.model,
|
|
656
|
+
delta: {},
|
|
657
|
+
finishReason: result.toolCalls.length > 0 ? "tool_calls" : "stop"
|
|
658
|
+
}));
|
|
659
|
+
reply.raw.write("data: [DONE]\n\n");
|
|
660
|
+
reply.raw.end();
|
|
661
|
+
}
|
|
388
662
|
function validateImageRequest(data) {
|
|
389
663
|
if (data.response_format === "url") {
|
|
390
664
|
return "\u5F53\u524D\u7F51\u5173\u4EC5\u652F\u6301 response_format=b64_json\uFF0C\u6682\u4E0D\u652F\u6301\u8FD4\u56DE\u6258\u7BA1\u56FE\u7247 URL\u3002";
|
|
@@ -438,6 +712,7 @@ function serializeProfile(profile) {
|
|
|
438
712
|
email: profile.email,
|
|
439
713
|
quota: profile.quota,
|
|
440
714
|
authStatus: profile.authStatus,
|
|
715
|
+
exportAudit: profile.exportAudit,
|
|
441
716
|
expiresAt: profile.expires,
|
|
442
717
|
accessTokenPreview: maskSecret(profile.access),
|
|
443
718
|
refreshTokenPreview: maskSecret(profile.refresh)
|
|
@@ -451,6 +726,7 @@ function serializeManagedProfile(profile) {
|
|
|
451
726
|
email: profile.email,
|
|
452
727
|
quota: profile.quota,
|
|
453
728
|
authStatus: profile.authStatus,
|
|
729
|
+
exportAudit: profile.exportAudit,
|
|
454
730
|
expiresAt: profile.expiresAt,
|
|
455
731
|
accessTokenPreview: profile.accessTokenPreview,
|
|
456
732
|
refreshTokenPreview: profile.refreshTokenPreview,
|
|
@@ -491,6 +767,24 @@ function createApp(params) {
|
|
|
491
767
|
bodyLimit: params?.bodyLimit
|
|
492
768
|
});
|
|
493
769
|
const ctx = createGatewayContext();
|
|
770
|
+
const gatewayRequestLogs = [];
|
|
771
|
+
function pushGatewayRequestLog(log) {
|
|
772
|
+
gatewayRequestLogs.unshift({
|
|
773
|
+
id: log.id ?? randomUUID(),
|
|
774
|
+
time: log.time ?? Date.now(),
|
|
775
|
+
method: log.method,
|
|
776
|
+
endpoint: log.endpoint,
|
|
777
|
+
account: log.account,
|
|
778
|
+
model: log.model,
|
|
779
|
+
statusCode: log.statusCode,
|
|
780
|
+
durationMs: log.durationMs,
|
|
781
|
+
source: log.source,
|
|
782
|
+
details: log.details
|
|
783
|
+
});
|
|
784
|
+
if (gatewayRequestLogs.length > MAX_GATEWAY_REQUEST_LOGS) {
|
|
785
|
+
gatewayRequestLogs.length = MAX_GATEWAY_REQUEST_LOGS;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
494
788
|
void app.register(cors, {
|
|
495
789
|
origin: params?.corsOrigin ?? true,
|
|
496
790
|
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
|
|
@@ -513,6 +807,9 @@ function createApp(params) {
|
|
|
513
807
|
}
|
|
514
808
|
};
|
|
515
809
|
});
|
|
810
|
+
app.get("/_gateway/admin/request-logs", async () => ({
|
|
811
|
+
data: gatewayRequestLogs
|
|
812
|
+
}));
|
|
516
813
|
async function buildAdminConfig(request) {
|
|
517
814
|
const [status, models, modelCatalog, versionStatus, settings, profile, profiles, codexStatus] = await Promise.all([
|
|
518
815
|
ctx.authService.getStatus(),
|
|
@@ -537,6 +834,7 @@ function createApp(params) {
|
|
|
537
834
|
adminUrl: `${origin}/`,
|
|
538
835
|
baseUrl: `${origin}/v1`,
|
|
539
836
|
restartSupported: Boolean(params?.onRestart),
|
|
837
|
+
codexRestartSupported: Boolean(params?.onRestartCodex),
|
|
540
838
|
supportedEndpoints: [
|
|
541
839
|
{
|
|
542
840
|
method: "GET",
|
|
@@ -612,10 +910,21 @@ function createApp(params) {
|
|
|
612
910
|
catalog: result.catalog
|
|
613
911
|
};
|
|
614
912
|
});
|
|
615
|
-
app.post("/_gateway/admin/runtime-refresh", async (request) => {
|
|
913
|
+
app.post("/_gateway/admin/runtime-refresh", async (request, reply) => {
|
|
914
|
+
const parsed = runtimeRefreshSchema.safeParse(request.body ?? {});
|
|
915
|
+
if (!parsed.success) {
|
|
916
|
+
reply.code(400);
|
|
917
|
+
return {
|
|
918
|
+
error: {
|
|
919
|
+
type: "validation_error",
|
|
920
|
+
message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
}
|
|
616
924
|
const [quotaSync] = await Promise.all([
|
|
617
925
|
ctx.authService.syncAllProfileQuotas("openai-codex", {
|
|
618
|
-
suppressErrors: true
|
|
926
|
+
suppressErrors: true,
|
|
927
|
+
staleAfterMs: parsed.data.staleOnly ? 30 * 60 * 1e3 : void 0
|
|
619
928
|
}),
|
|
620
929
|
ctx.versionService.getVersionStatus({
|
|
621
930
|
force: true
|
|
@@ -688,6 +997,23 @@ function createApp(params) {
|
|
|
688
997
|
await ctx.authService.removeProfile(parsed.data.profileId);
|
|
689
998
|
return buildAdminConfig(request);
|
|
690
999
|
});
|
|
1000
|
+
app.post("/_gateway/admin/profiles/remove-batch", async (request, reply) => {
|
|
1001
|
+
const parsed = profileRemoveBatchSchema.safeParse(request.body);
|
|
1002
|
+
if (!parsed.success) {
|
|
1003
|
+
reply.code(400);
|
|
1004
|
+
return {
|
|
1005
|
+
error: {
|
|
1006
|
+
type: "validation_error",
|
|
1007
|
+
message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
const removedProfileCount = await ctx.authService.removeProfiles(parsed.data.profileIds);
|
|
1012
|
+
return {
|
|
1013
|
+
...await buildAdminConfig(request),
|
|
1014
|
+
removedProfileCount
|
|
1015
|
+
};
|
|
1016
|
+
});
|
|
691
1017
|
app.post("/_gateway/admin/profiles/import", async (request, reply) => {
|
|
692
1018
|
const parsed = profileImportSchema.safeParse(request.body);
|
|
693
1019
|
if (!parsed.success) {
|
|
@@ -708,6 +1034,23 @@ function createApp(params) {
|
|
|
708
1034
|
importedProfileCount: importedProfiles.length
|
|
709
1035
|
};
|
|
710
1036
|
});
|
|
1037
|
+
app.post("/_gateway/admin/profiles/import/validate", async (request, reply) => {
|
|
1038
|
+
const parsed = profileImportSchema.safeParse(request.body);
|
|
1039
|
+
if (!parsed.success) {
|
|
1040
|
+
reply.code(400);
|
|
1041
|
+
return {
|
|
1042
|
+
error: {
|
|
1043
|
+
type: "validation_error",
|
|
1044
|
+
message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
const profiles = ctx.authService.validateProfilesImport(parsed.data.profile);
|
|
1049
|
+
return {
|
|
1050
|
+
valid: true,
|
|
1051
|
+
profileCount: profiles.length
|
|
1052
|
+
};
|
|
1053
|
+
});
|
|
711
1054
|
app.get("/_gateway/admin/profiles/import-template", async () => ({
|
|
712
1055
|
profile: ctx.authService.getProfileImportTemplate()
|
|
713
1056
|
}));
|
|
@@ -724,11 +1067,13 @@ function createApp(params) {
|
|
|
724
1067
|
}
|
|
725
1068
|
if (parsed.data.all || parsed.data.profileIds) {
|
|
726
1069
|
return {
|
|
727
|
-
profile: await ctx.authService.exportProfiles(parsed.data.profileIds)
|
|
1070
|
+
profile: await ctx.authService.exportProfiles(parsed.data.profileIds, "openai-codex", parsed.data.all ? "all" : "batch"),
|
|
1071
|
+
config: await buildAdminConfig(request)
|
|
728
1072
|
};
|
|
729
1073
|
}
|
|
730
1074
|
return {
|
|
731
|
-
profile: await ctx.authService.exportProfile(parsed.data.profileId)
|
|
1075
|
+
profile: await ctx.authService.exportProfile(parsed.data.profileId),
|
|
1076
|
+
config: await buildAdminConfig(request)
|
|
732
1077
|
};
|
|
733
1078
|
});
|
|
734
1079
|
app.post("/_gateway/admin/codex/apply", async (request, reply) => {
|
|
@@ -758,18 +1103,7 @@ function createApp(params) {
|
|
|
758
1103
|
}
|
|
759
1104
|
};
|
|
760
1105
|
}
|
|
761
|
-
|
|
762
|
-
await ctx.configService.setDefaultModel(parsed.data.defaultModel);
|
|
763
|
-
}
|
|
764
|
-
if (parsed.data.networkProxy) {
|
|
765
|
-
await ctx.configService.setNetworkProxy(parsed.data.networkProxy);
|
|
766
|
-
}
|
|
767
|
-
if (parsed.data.autoSwitch) {
|
|
768
|
-
await ctx.configService.setAutoSwitch(parsed.data.autoSwitch);
|
|
769
|
-
}
|
|
770
|
-
if (parsed.data.server) {
|
|
771
|
-
await ctx.configService.setServerConfig({ port: parsed.data.server.port });
|
|
772
|
-
}
|
|
1106
|
+
await ctx.configService.updateSettings(parsed.data);
|
|
773
1107
|
return buildAdminConfig(request);
|
|
774
1108
|
});
|
|
775
1109
|
app.post("/_gateway/admin/restart", async (_request, reply) => {
|
|
@@ -792,6 +1126,22 @@ function createApp(params) {
|
|
|
792
1126
|
restarting: true
|
|
793
1127
|
};
|
|
794
1128
|
});
|
|
1129
|
+
app.post("/_gateway/admin/desktop/restart-codex", async (_request, reply) => {
|
|
1130
|
+
if (!params?.onRestartCodex) {
|
|
1131
|
+
reply.code(501);
|
|
1132
|
+
return {
|
|
1133
|
+
error: {
|
|
1134
|
+
type: "not_supported",
|
|
1135
|
+
message: "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u91CD\u542F Codex\u3002"
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
await params.onRestartCodex();
|
|
1140
|
+
return {
|
|
1141
|
+
ok: true,
|
|
1142
|
+
restarted: true
|
|
1143
|
+
};
|
|
1144
|
+
});
|
|
795
1145
|
app.post("/_gateway/admin/settings/proxy-test", async (request, reply) => {
|
|
796
1146
|
const parsed = proxyTestSchema.safeParse(request.body);
|
|
797
1147
|
if (!parsed.success) {
|
|
@@ -983,8 +1333,27 @@ function createApp(params) {
|
|
|
983
1333
|
return buildResponseApiBody(result, parsed.data.experimental_codex?.include_raw);
|
|
984
1334
|
});
|
|
985
1335
|
app.post("/v1/chat/completions", async (request, reply) => {
|
|
1336
|
+
const startedAt = performance.now();
|
|
986
1337
|
const parsed = chatCompletionsBodySchema.safeParse(request.body);
|
|
987
1338
|
if (!parsed.success) {
|
|
1339
|
+
pushGatewayRequestLog({
|
|
1340
|
+
method: request.method,
|
|
1341
|
+
endpoint: request.url,
|
|
1342
|
+
account: "-",
|
|
1343
|
+
model: "-",
|
|
1344
|
+
statusCode: 400,
|
|
1345
|
+
durationMs: performance.now() - startedAt,
|
|
1346
|
+
source: "API",
|
|
1347
|
+
details: {
|
|
1348
|
+
requestId: request.id,
|
|
1349
|
+
remoteAddress: request.ip,
|
|
1350
|
+
userAgent: request.headers["user-agent"],
|
|
1351
|
+
error: {
|
|
1352
|
+
type: "validation_error",
|
|
1353
|
+
message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
988
1357
|
reply.code(400);
|
|
989
1358
|
return {
|
|
990
1359
|
error: {
|
|
@@ -993,16 +1362,24 @@ function createApp(params) {
|
|
|
993
1362
|
}
|
|
994
1363
|
};
|
|
995
1364
|
}
|
|
996
|
-
if (parsed.data.stream) {
|
|
997
|
-
reply.code(501);
|
|
998
|
-
return {
|
|
999
|
-
error: {
|
|
1000
|
-
type: "not_supported",
|
|
1001
|
-
message: "\u5F53\u524D\u7F51\u5173\u6682\u4E0D\u652F\u6301 chat.completions \u7684 stream=true"
|
|
1002
|
-
}
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
1365
|
if (typeof parsed.data.n === "number" && parsed.data.n > 1) {
|
|
1366
|
+
pushGatewayRequestLog({
|
|
1367
|
+
method: request.method,
|
|
1368
|
+
endpoint: request.url,
|
|
1369
|
+
account: "-",
|
|
1370
|
+
model: parsed.data.model ?? "default",
|
|
1371
|
+
statusCode: 501,
|
|
1372
|
+
durationMs: performance.now() - startedAt,
|
|
1373
|
+
source: "API",
|
|
1374
|
+
details: {
|
|
1375
|
+
requestId: request.id,
|
|
1376
|
+
request: summarizeChatCompletionsRequest(parsed.data),
|
|
1377
|
+
error: {
|
|
1378
|
+
type: "not_supported",
|
|
1379
|
+
message: "\u5F53\u524D\u7F51\u5173\u6682\u4E0D\u652F\u6301\u4E00\u6B21\u8FD4\u56DE\u591A\u4E2A choices\uFF08n > 1\uFF09"
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1006
1383
|
reply.code(501);
|
|
1007
1384
|
return {
|
|
1008
1385
|
error: {
|
|
@@ -1012,16 +1389,93 @@ function createApp(params) {
|
|
|
1012
1389
|
};
|
|
1013
1390
|
}
|
|
1014
1391
|
const codexBody = createChatCompletionsCodexBody(parsed.data);
|
|
1392
|
+
console.info("[gateway:chat:request]", {
|
|
1393
|
+
requestId: request.id,
|
|
1394
|
+
remoteAddress: request.ip,
|
|
1395
|
+
userAgent: request.headers["user-agent"],
|
|
1396
|
+
...summarizeChatCompletionsRequest(parsed.data),
|
|
1397
|
+
codex: summarizeCodexChatBody(codexBody)
|
|
1398
|
+
});
|
|
1015
1399
|
const fallbackInput = parsed.data.messages.map(
|
|
1016
1400
|
(message) => typeof message.content === "string" ? message.content : (message.content ?? []).map((part) => typeof part.text === "string" ? part.text : "").filter(Boolean).join("\n")
|
|
1017
1401
|
).filter(Boolean).join("\n").trim();
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1402
|
+
let result;
|
|
1403
|
+
try {
|
|
1404
|
+
result = await ctx.chatService.chat({
|
|
1405
|
+
model: parsed.data.model,
|
|
1406
|
+
input: fallbackInput || void 0,
|
|
1407
|
+
experimental: {
|
|
1408
|
+
codexBody
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
} catch (error) {
|
|
1412
|
+
const normalized = normalizeError(error);
|
|
1413
|
+
const statusCode = getErrorStatusCode(normalized);
|
|
1414
|
+
pushGatewayRequestLog({
|
|
1415
|
+
method: request.method,
|
|
1416
|
+
endpoint: request.url,
|
|
1417
|
+
account: profileLogLabel(await ctx.authService.getActiveProfile()),
|
|
1418
|
+
model: parsed.data.model ?? "default",
|
|
1419
|
+
statusCode,
|
|
1420
|
+
durationMs: performance.now() - startedAt,
|
|
1421
|
+
source: requestSourceFromUserAgent(request.headers["user-agent"]),
|
|
1422
|
+
details: {
|
|
1423
|
+
requestId: request.id,
|
|
1424
|
+
remoteAddress: request.ip,
|
|
1425
|
+
userAgent: request.headers["user-agent"],
|
|
1426
|
+
request: summarizeChatCompletionsRequest(parsed.data),
|
|
1427
|
+
codex: summarizeCodexChatBody(codexBody),
|
|
1428
|
+
error: {
|
|
1429
|
+
message: normalized.message,
|
|
1430
|
+
upstreamStatus: normalized.upstreamStatus,
|
|
1431
|
+
upstreamErrorCode: normalized.upstreamErrorCode,
|
|
1432
|
+
upstreamErrorMessage: normalized.upstreamErrorMessage
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1436
|
+
throw error;
|
|
1437
|
+
}
|
|
1438
|
+
pushGatewayRequestLog({
|
|
1439
|
+
method: request.method,
|
|
1440
|
+
endpoint: request.url,
|
|
1441
|
+
account: profileLogLabel(await ctx.authService.getActiveProfile()),
|
|
1442
|
+
model: result.model,
|
|
1443
|
+
statusCode: 200,
|
|
1444
|
+
durationMs: performance.now() - startedAt,
|
|
1445
|
+
source: requestSourceFromUserAgent(request.headers["user-agent"]),
|
|
1446
|
+
details: {
|
|
1447
|
+
requestId: request.id,
|
|
1448
|
+
remoteAddress: request.ip,
|
|
1449
|
+
userAgent: request.headers["user-agent"],
|
|
1450
|
+
request: summarizeChatCompletionsRequest(parsed.data),
|
|
1451
|
+
codex: summarizeCodexChatBody(codexBody),
|
|
1452
|
+
response: {
|
|
1453
|
+
textPreview: truncateForLog(result.text),
|
|
1454
|
+
textLength: result.text.length,
|
|
1455
|
+
toolCallCount: result.toolCalls.length,
|
|
1456
|
+
toolCalls: result.toolCalls.map((toolCall) => ({
|
|
1457
|
+
id: toolCall.id,
|
|
1458
|
+
name: toolCall.function.name,
|
|
1459
|
+
argumentsPreview: truncateForLog(toolCall.function.arguments)
|
|
1460
|
+
})),
|
|
1461
|
+
artifactCount: result.artifacts.length,
|
|
1462
|
+
stream: parsed.data.stream ?? false
|
|
1463
|
+
}
|
|
1023
1464
|
}
|
|
1024
1465
|
});
|
|
1466
|
+
console.info("[gateway:chat:response]", {
|
|
1467
|
+
requestId: request.id,
|
|
1468
|
+
model: result.model,
|
|
1469
|
+
stream: parsed.data.stream ?? false,
|
|
1470
|
+
durationMs: Math.round((performance.now() - startedAt) * 100) / 100,
|
|
1471
|
+
textLength: result.text.length,
|
|
1472
|
+
toolCallCount: result.toolCalls.length,
|
|
1473
|
+
artifactCount: result.artifacts.length
|
|
1474
|
+
});
|
|
1475
|
+
if (parsed.data.stream) {
|
|
1476
|
+
sendChatCompletionsStream(reply, result);
|
|
1477
|
+
return reply;
|
|
1478
|
+
}
|
|
1025
1479
|
return buildChatCompletionsBody(result);
|
|
1026
1480
|
});
|
|
1027
1481
|
app.post("/v1/images/generations", async (request, reply) => {
|