ai-zero-token 1.0.1 → 1.0.3

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 (70) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +235 -69
  3. package/dist/api.js +0 -1
  4. package/dist/cli/commands/ask.js +131 -5
  5. package/dist/cli/commands/clear.js +0 -1
  6. package/dist/cli/commands/help.js +17 -11
  7. package/dist/cli/commands/login.js +0 -1
  8. package/dist/cli/commands/models.js +14 -4
  9. package/dist/cli/commands/serve.js +41 -4
  10. package/dist/cli/commands/start.js +10 -0
  11. package/dist/cli/commands/status.js +1 -1
  12. package/dist/cli/index.js +5 -2
  13. package/dist/cli/shared.js +57 -6
  14. package/dist/cli.js +0 -1
  15. package/dist/core/context.js +10 -2
  16. package/dist/core/models/openai-codex-models.js +89 -1
  17. package/dist/core/providers/http-client.js +137 -14
  18. package/dist/core/providers/openai-codex/chat.js +217 -24
  19. package/dist/core/providers/openai-codex/oauth.js +15 -4
  20. package/dist/core/providers/openai-codex/pkce.js +0 -1
  21. package/dist/core/services/auth-service.js +125 -16
  22. package/dist/core/services/chat-service.js +24 -14
  23. package/dist/core/services/config-service.js +4 -5
  24. package/dist/core/services/image-service.js +405 -0
  25. package/dist/core/services/model-service.js +35 -8
  26. package/dist/core/services/version-service.js +97 -0
  27. package/dist/core/store/profile-store.js +79 -6
  28. package/dist/core/store/settings-store.js +1 -2
  29. package/dist/core/types.js +0 -1
  30. package/dist/http.js +0 -1
  31. package/dist/models.js +0 -1
  32. package/dist/oauth.js +0 -1
  33. package/dist/pkce.js +0 -1
  34. package/dist/server/admin-page.js +3165 -0
  35. package/dist/server/app.js +599 -40
  36. package/dist/server/index.js +0 -1
  37. package/dist/store.js +0 -1
  38. package/docs/API_USAGE.md +120 -0
  39. package/package.json +14 -3
  40. package/dist/api.js.map +0 -1
  41. package/dist/cli/commands/ask.js.map +0 -1
  42. package/dist/cli/commands/clear.js.map +0 -1
  43. package/dist/cli/commands/help.js.map +0 -1
  44. package/dist/cli/commands/login.js.map +0 -1
  45. package/dist/cli/commands/models.js.map +0 -1
  46. package/dist/cli/commands/serve.js.map +0 -1
  47. package/dist/cli/commands/status.js.map +0 -1
  48. package/dist/cli/index.js.map +0 -1
  49. package/dist/cli/shared.js.map +0 -1
  50. package/dist/cli.js.map +0 -1
  51. package/dist/core/context.js.map +0 -1
  52. package/dist/core/models/openai-codex-models.js.map +0 -1
  53. package/dist/core/providers/http-client.js.map +0 -1
  54. package/dist/core/providers/openai-codex/chat.js.map +0 -1
  55. package/dist/core/providers/openai-codex/oauth.js.map +0 -1
  56. package/dist/core/providers/openai-codex/pkce.js.map +0 -1
  57. package/dist/core/services/auth-service.js.map +0 -1
  58. package/dist/core/services/chat-service.js.map +0 -1
  59. package/dist/core/services/config-service.js.map +0 -1
  60. package/dist/core/services/model-service.js.map +0 -1
  61. package/dist/core/store/profile-store.js.map +0 -1
  62. package/dist/core/store/settings-store.js.map +0 -1
  63. package/dist/core/types.js.map +0 -1
  64. package/dist/http.js.map +0 -1
  65. package/dist/models.js.map +0 -1
  66. package/dist/oauth.js.map +0 -1
  67. package/dist/pkce.js.map +0 -1
  68. package/dist/server/app.js.map +0 -1
  69. package/dist/server/index.js.map +0 -1
  70. package/dist/store.js.map +0 -1
@@ -1,28 +1,101 @@
1
1
  #!/usr/bin/env node
2
+ import { randomUUID } from "node:crypto";
2
3
  import Fastify from "fastify";
3
4
  import cors from "@fastify/cors";
4
5
  import { z } from "zod";
5
6
  import { createGatewayContext } from "../core/context.js";
7
+ import { renderAdminPage } from "./admin-page.js";
8
+ const inputPartSchema = z.object({
9
+ type: z.string().optional(),
10
+ text: z.string().optional()
11
+ }).passthrough();
12
+ const inputMessageSchema = z.object({
13
+ role: z.string().optional(),
14
+ content: z.array(inputPartSchema).optional()
15
+ }).passthrough();
6
16
  const responsesBodySchema = z.object({
7
17
  model: z.string().optional(),
8
- input: z.union([
9
- z.string(),
10
- z.array(
11
- z.object({
12
- role: z.string().optional(),
13
- content: z.array(
14
- z.object({
15
- type: z.string().optional(),
16
- text: z.string().optional()
17
- })
18
- ).optional()
19
- })
20
- )
21
- ]),
18
+ input: z.union([z.string(), z.array(inputMessageSchema)]).optional(),
22
19
  instructions: z.string().optional(),
23
- stream: z.boolean().optional()
20
+ stream: z.boolean().optional(),
21
+ tools: z.array(z.unknown()).optional(),
22
+ tool_choice: z.unknown().optional(),
23
+ include: z.array(z.string()).optional(),
24
+ text: z.record(z.string(), z.unknown()).optional(),
25
+ store: z.boolean().optional(),
26
+ parallel_tool_calls: z.boolean().optional(),
27
+ experimental_codex: z.object({
28
+ body: z.record(z.string(), z.unknown()).optional(),
29
+ allow_unknown_model: z.boolean().optional(),
30
+ include_raw: z.boolean().optional()
31
+ }).passthrough().optional()
32
+ });
33
+ const chatCompletionContentPartSchema = z.object({
34
+ type: z.string().optional(),
35
+ text: z.string().optional(),
36
+ image_url: z.union([
37
+ z.string(),
38
+ z.object({
39
+ url: z.string().optional()
40
+ }).passthrough()
41
+ ]).optional()
42
+ }).passthrough();
43
+ const chatCompletionMessageSchema = z.object({
44
+ role: z.string().optional(),
45
+ content: z.union([z.string(), z.array(chatCompletionContentPartSchema)]).optional(),
46
+ name: z.string().optional(),
47
+ tool_call_id: z.string().optional()
48
+ }).passthrough();
49
+ const chatCompletionsBodySchema = z.object({
50
+ model: z.string().optional(),
51
+ messages: z.array(chatCompletionMessageSchema).min(1),
52
+ n: z.number().int().positive().optional(),
53
+ stream: z.boolean().optional(),
54
+ tools: z.array(z.unknown()).optional(),
55
+ tool_choice: z.unknown().optional(),
56
+ response_format: z.unknown().optional(),
57
+ parallel_tool_calls: z.boolean().optional(),
58
+ store: z.boolean().optional(),
59
+ temperature: z.number().optional(),
60
+ top_p: z.number().optional(),
61
+ max_tokens: z.number().optional(),
62
+ max_completion_tokens: z.number().optional(),
63
+ presence_penalty: z.number().optional(),
64
+ frequency_penalty: z.number().optional(),
65
+ metadata: z.record(z.string(), z.unknown()).optional(),
66
+ stop: z.union([z.string(), z.array(z.string())]).optional(),
67
+ user: z.string().optional()
68
+ }).passthrough();
69
+ const settingsUpdateSchema = z.object({
70
+ defaultModel: z.string().min(1)
71
+ });
72
+ const profileActionSchema = z.object({
73
+ profileId: z.string().min(1)
24
74
  });
75
+ const imageGenerationsBodySchema = z.object({
76
+ prompt: z.string().min(1),
77
+ model: z.string().optional(),
78
+ n: z.number().int().positive().optional(),
79
+ quality: z.enum(["low", "medium", "high", "auto"]).optional(),
80
+ size: z.enum(["1024x1024", "1024x1536", "1536x1024", "auto"]).optional(),
81
+ background: z.enum(["transparent", "opaque", "auto"]).optional(),
82
+ output_format: z.enum(["png", "webp", "jpeg"]).optional(),
83
+ output_compression: z.number().int().min(0).max(100).optional(),
84
+ moderation: z.enum(["auto", "low"]).optional(),
85
+ response_format: z.enum(["b64_json", "url"]).optional(),
86
+ user: z.string().optional()
87
+ }).passthrough();
88
+ const chatCompletionExcludedKeys = /* @__PURE__ */ new Set([
89
+ "messages",
90
+ "n",
91
+ "stream",
92
+ "max_tokens",
93
+ "max_completion_tokens"
94
+ ]);
25
95
  function extractTextInput(input) {
96
+ if (typeof input === "undefined") {
97
+ return "";
98
+ }
26
99
  if (typeof input === "string") {
27
100
  return input;
28
101
  }
@@ -36,6 +109,208 @@ function extractTextInput(input) {
36
109
  }
37
110
  return chunks.join("\n").trim();
38
111
  }
112
+ function normalizeResponseInput(input) {
113
+ if (typeof input === "undefined") {
114
+ return void 0;
115
+ }
116
+ if (typeof input === "string") {
117
+ return [
118
+ {
119
+ role: "user",
120
+ content: [{ type: "input_text", text: input }]
121
+ }
122
+ ];
123
+ }
124
+ return input;
125
+ }
126
+ function normalizeChatRole(role) {
127
+ if (role === "developer") {
128
+ return "system";
129
+ }
130
+ return role ?? "user";
131
+ }
132
+ function normalizeChatContentPart(part) {
133
+ if (part.type === "image_url") {
134
+ const url = typeof part.image_url === "string" ? part.image_url : part.image_url?.url;
135
+ if (!url) {
136
+ throw new Error("chat.completions \u6D88\u606F\u91CC\u7684 image_url \u7F3A\u5C11 url\u3002");
137
+ }
138
+ return {
139
+ type: "input_image",
140
+ image_url: url
141
+ };
142
+ }
143
+ if (part.type === "input_image") {
144
+ return part;
145
+ }
146
+ const text = typeof part.text === "string" ? part.text : "";
147
+ return {
148
+ type: "input_text",
149
+ text
150
+ };
151
+ }
152
+ function normalizeChatContent(content) {
153
+ if (typeof content === "string") {
154
+ return [{ type: "input_text", text: content }];
155
+ }
156
+ if (!Array.isArray(content) || content.length === 0) {
157
+ return [{ type: "input_text", text: "" }];
158
+ }
159
+ return content.map((part) => normalizeChatContentPart(part));
160
+ }
161
+ function normalizeChatMessages(messages) {
162
+ return messages.map((message) => ({
163
+ role: normalizeChatRole(message.role),
164
+ content: normalizeChatContent(message.content),
165
+ ...message.name ? { name: message.name } : {},
166
+ ...message.tool_call_id ? { tool_call_id: message.tool_call_id } : {}
167
+ }));
168
+ }
169
+ function createChatCompletionsCodexBody(data) {
170
+ const body = Object.fromEntries(
171
+ Object.entries(data).filter(([key]) => !chatCompletionExcludedKeys.has(key))
172
+ );
173
+ if (typeof data.max_completion_tokens === "number") {
174
+ body.max_output_tokens = data.max_completion_tokens;
175
+ } else if (typeof data.max_tokens === "number") {
176
+ body.max_output_tokens = data.max_tokens;
177
+ }
178
+ body.input = normalizeChatMessages(data.messages);
179
+ return body;
180
+ }
181
+ function summarizeImageRequestForLog(body) {
182
+ return {
183
+ model: body.model ?? "default",
184
+ promptLength: body.prompt.length,
185
+ size: body.size ?? "default",
186
+ quality: body.quality ?? "default",
187
+ background: body.background ?? "default",
188
+ output_format: body.output_format ?? "default",
189
+ output_compression: typeof body.output_compression === "number" ? body.output_compression : void 0,
190
+ moderation: body.moderation ?? "default",
191
+ response_format: body.response_format ?? "default",
192
+ user: body.user ?? void 0
193
+ };
194
+ }
195
+ function buildResponseApiBody(result, includeRaw) {
196
+ const responseBody = {
197
+ object: "response",
198
+ provider: result.provider,
199
+ model: result.model,
200
+ output_text: result.text,
201
+ output: [
202
+ {
203
+ type: "message",
204
+ role: "assistant",
205
+ content: [
206
+ {
207
+ type: "output_text",
208
+ text: result.text
209
+ }
210
+ ]
211
+ }
212
+ ]
213
+ };
214
+ if (result.artifacts.length > 0) {
215
+ responseBody.artifacts = result.artifacts;
216
+ }
217
+ if (includeRaw) {
218
+ responseBody.raw = result.raw;
219
+ }
220
+ return responseBody;
221
+ }
222
+ function buildChatCompletionsBody(result) {
223
+ const body = {
224
+ id: `chatcmpl_${randomUUID().replace(/-/g, "")}`,
225
+ object: "chat.completion",
226
+ created: Math.floor(Date.now() / 1e3),
227
+ model: result.model,
228
+ choices: [
229
+ {
230
+ index: 0,
231
+ finish_reason: "stop",
232
+ message: {
233
+ role: "assistant",
234
+ content: result.text
235
+ }
236
+ }
237
+ ]
238
+ };
239
+ if (result.artifacts.length > 0) {
240
+ body.artifacts = result.artifacts;
241
+ }
242
+ return body;
243
+ }
244
+ function validateImageRequest(data) {
245
+ if (data.response_format === "url") {
246
+ 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";
247
+ }
248
+ if (data.background === "transparent" && typeof data.output_format === "string" && !["png", "webp"].includes(data.output_format)) {
249
+ return "transparent \u80CC\u666F\u4EC5\u652F\u6301 output_format=png \u6216 webp\u3002";
250
+ }
251
+ if (typeof data.output_compression === "number" && data.output_format === "png") {
252
+ return "output_compression \u4EC5\u652F\u6301 jpeg \u6216 webp \u8F93\u51FA\u3002";
253
+ }
254
+ return null;
255
+ }
256
+ function maskSecret(value) {
257
+ if (value.length <= 12) {
258
+ return value;
259
+ }
260
+ return `${value.slice(0, 8)}...${value.slice(-6)}`;
261
+ }
262
+ function serializeProfile(profile) {
263
+ if (!profile) {
264
+ return null;
265
+ }
266
+ return {
267
+ provider: profile.provider,
268
+ profileId: profile.profileId,
269
+ accountId: profile.accountId,
270
+ email: profile.email,
271
+ quota: profile.quota,
272
+ expiresAt: profile.expires,
273
+ accessTokenPreview: maskSecret(profile.access),
274
+ refreshTokenPreview: maskSecret(profile.refresh)
275
+ };
276
+ }
277
+ function serializeManagedProfile(profile) {
278
+ return {
279
+ provider: profile.provider,
280
+ profileId: profile.profileId,
281
+ accountId: profile.accountId,
282
+ email: profile.email,
283
+ quota: profile.quota,
284
+ expiresAt: profile.expiresAt,
285
+ accessTokenPreview: profile.accessTokenPreview,
286
+ refreshTokenPreview: profile.refreshTokenPreview,
287
+ isActive: profile.isActive
288
+ };
289
+ }
290
+ function resolveOrigin(request) {
291
+ const host = request.headers.host;
292
+ if (host) {
293
+ return `${request.protocol}://${host}`;
294
+ }
295
+ return "http://127.0.0.1:8787";
296
+ }
297
+ function normalizeError(error) {
298
+ return error instanceof Error ? error : new Error(String(error));
299
+ }
300
+ function getErrorStatusCode(error) {
301
+ const normalized = normalizeError(error);
302
+ if (typeof normalized.statusCode === "number") {
303
+ return normalized.statusCode;
304
+ }
305
+ const message = normalized.message;
306
+ if (message.includes("\u7F3A\u5C11") || message.includes("\u683C\u5F0F\u9519\u8BEF") || message.includes("\u672A\u5185\u7F6E\u6A21\u578B") || message.includes("\u4E0D\u652F\u6301") || message.includes("\u6CA1\u6709\u63D0\u4F9B")) {
307
+ return 400;
308
+ }
309
+ if (message.includes("\u8FD8\u6CA1\u6709\u767B\u5F55")) {
310
+ return 401;
311
+ }
312
+ return 500;
313
+ }
39
314
  function createApp(params) {
40
315
  const app = Fastify({
41
316
  logger: false
@@ -43,13 +318,164 @@ function createApp(params) {
43
318
  const ctx = createGatewayContext();
44
319
  void app.register(cors, {
45
320
  origin: params?.corsOrigin ?? true,
46
- methods: ["GET", "POST", "OPTIONS"]
321
+ methods: ["GET", "POST", "PUT", "OPTIONS"]
322
+ });
323
+ app.setErrorHandler((error, request, reply) => {
324
+ const normalized = normalizeError(error);
325
+ const statusCode = getErrorStatusCode(normalized);
326
+ console.error("[gateway:error]", {
327
+ method: request.method,
328
+ url: request.url,
329
+ statusCode,
330
+ message: normalized.message,
331
+ stack: normalized.stack
332
+ });
333
+ reply.code(statusCode);
334
+ return {
335
+ error: {
336
+ type: "gateway_error",
337
+ message: normalized.message
338
+ }
339
+ };
340
+ });
341
+ async function buildAdminConfig(request) {
342
+ const [status, models, modelCatalog, versionStatus, settings, profile, profiles] = await Promise.all([
343
+ ctx.authService.getStatus(),
344
+ ctx.modelService.listModels(),
345
+ ctx.modelService.getCatalog(),
346
+ ctx.versionService.getVersionStatus(),
347
+ ctx.configService.getSettings(),
348
+ ctx.authService.getActiveProfile(),
349
+ ctx.authService.listProfiles()
350
+ ]);
351
+ const origin = resolveOrigin(request);
352
+ return {
353
+ status,
354
+ settings,
355
+ models,
356
+ modelCatalog,
357
+ versionStatus,
358
+ profile: serializeProfile(profile),
359
+ profiles: profiles.map((item) => serializeManagedProfile(item)),
360
+ adminUrl: `${origin}/`,
361
+ baseUrl: `${origin}/v1`,
362
+ supportedEndpoints: [
363
+ {
364
+ method: "GET",
365
+ path: "/v1/models",
366
+ description: "OpenAI models \u5217\u8868\u517C\u5BB9\u63A5\u53E3\u3002"
367
+ },
368
+ {
369
+ method: "POST",
370
+ path: "/v1/responses",
371
+ description: "OpenAI responses \u517C\u5BB9\u63A5\u53E3\u3002"
372
+ },
373
+ {
374
+ method: "POST",
375
+ path: "/v1/chat/completions",
376
+ description: "OpenAI chat.completions \u517C\u5BB9\u63A5\u53E3\u3002"
377
+ },
378
+ {
379
+ method: "POST",
380
+ path: "/v1/images/generations",
381
+ description: "OpenAI images.generations \u517C\u5BB9\u63A5\u53E3\u3002"
382
+ }
383
+ ]
384
+ };
385
+ }
386
+ app.get("/", async (_request, reply) => {
387
+ reply.header("Content-Type", "text/html; charset=utf-8");
388
+ return renderAdminPage();
389
+ });
390
+ app.get("/favicon.ico", async (_request, reply) => {
391
+ reply.code(204);
392
+ return "";
47
393
  });
48
394
  app.get("/_gateway/health", async () => ({ ok: true }));
49
395
  app.get("/_gateway/status", async () => ctx.authService.getStatus());
50
396
  app.get("/_gateway/models", async () => ({
51
- data: await ctx.modelService.listModels()
397
+ data: await ctx.modelService.listModels(),
398
+ catalog: await ctx.modelService.getCatalog()
52
399
  }));
400
+ app.post("/_gateway/models/refresh", async () => {
401
+ const result = await ctx.modelService.refreshModels();
402
+ return {
403
+ data: result.models,
404
+ catalog: result.catalog
405
+ };
406
+ });
407
+ app.post("/_gateway/admin/runtime-refresh", async (request) => {
408
+ await Promise.all([
409
+ ctx.authService.syncActiveProfileQuota("openai-codex", {
410
+ suppressErrors: true
411
+ }),
412
+ ctx.versionService.getVersionStatus({
413
+ force: true
414
+ })
415
+ ]);
416
+ return buildAdminConfig(request);
417
+ });
418
+ app.get("/_gateway/admin/config", async (request) => buildAdminConfig(request));
419
+ app.post("/_gateway/admin/login", async (request) => {
420
+ await ctx.authService.login("openai-codex");
421
+ await ctx.authService.syncActiveProfileQuota("openai-codex", {
422
+ suppressErrors: true
423
+ });
424
+ return buildAdminConfig(request);
425
+ });
426
+ app.post("/_gateway/admin/logout", async (request) => {
427
+ await ctx.authService.logoutAll();
428
+ return buildAdminConfig(request);
429
+ });
430
+ app.post("/_gateway/admin/profiles/activate", async (request, reply) => {
431
+ const parsed = profileActionSchema.safeParse(request.body);
432
+ if (!parsed.success) {
433
+ reply.code(400);
434
+ return {
435
+ error: {
436
+ type: "validation_error",
437
+ message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
438
+ }
439
+ };
440
+ }
441
+ await ctx.authService.activateProfile(parsed.data.profileId);
442
+ await ctx.authService.syncActiveProfileQuota("openai-codex", {
443
+ suppressErrors: true
444
+ });
445
+ return buildAdminConfig(request);
446
+ });
447
+ app.post("/_gateway/admin/profiles/sync-quota", async (request) => {
448
+ await ctx.authService.syncActiveProfileQuota("openai-codex");
449
+ return buildAdminConfig(request);
450
+ });
451
+ app.post("/_gateway/admin/profiles/remove", async (request, reply) => {
452
+ const parsed = profileActionSchema.safeParse(request.body);
453
+ if (!parsed.success) {
454
+ reply.code(400);
455
+ return {
456
+ error: {
457
+ type: "validation_error",
458
+ message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
459
+ }
460
+ };
461
+ }
462
+ await ctx.authService.removeProfile(parsed.data.profileId);
463
+ return buildAdminConfig(request);
464
+ });
465
+ app.put("/_gateway/admin/settings", async (request, reply) => {
466
+ const parsed = settingsUpdateSchema.safeParse(request.body);
467
+ if (!parsed.success) {
468
+ reply.code(400);
469
+ return {
470
+ error: {
471
+ type: "validation_error",
472
+ message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
473
+ }
474
+ };
475
+ }
476
+ await ctx.configService.setDefaultModel(parsed.data.defaultModel);
477
+ return buildAdminConfig(request);
478
+ });
53
479
  app.get("/v1/models", async () => ({
54
480
  object: "list",
55
481
  data: (await ctx.modelService.listModels()).map((model) => ({
@@ -74,47 +500,180 @@ function createApp(params) {
74
500
  return {
75
501
  error: {
76
502
  type: "not_supported",
77
- message: "\u7B2C\u4E00\u9636\u6BB5\u6682\u4E0D\u652F\u6301 stream=true"
503
+ message: "\u5F53\u524D\u7F51\u5173\u6682\u4E0D\u652F\u6301 stream=true"
78
504
  }
79
505
  };
80
506
  }
81
507
  const input = extractTextInput(parsed.data.input);
82
- if (!input) {
508
+ const hasInput = typeof parsed.data.input !== "undefined" || typeof parsed.data.experimental_codex?.body?.input !== "undefined";
509
+ if (!hasInput) {
83
510
  reply.code(400);
84
511
  return {
85
512
  error: {
86
513
  type: "validation_error",
87
- message: "\u6CA1\u6709\u89E3\u6790\u51FA\u6709\u6548\u7684 input \u6587\u672C"
514
+ message: "\u6CA1\u6709\u63D0\u4F9B input\uFF0C\u4E5F\u6CA1\u6709\u5728 experimental_codex.body \u91CC\u900F\u4F20 input"
88
515
  }
89
516
  };
90
517
  }
518
+ const codexBody = {
519
+ ...parsed.data.experimental_codex?.body ?? {}
520
+ };
521
+ const normalizedInput = normalizeResponseInput(parsed.data.input);
522
+ if (typeof normalizedInput !== "undefined") {
523
+ codexBody.input = normalizedInput;
524
+ }
525
+ if (typeof parsed.data.instructions === "string") {
526
+ codexBody.instructions = parsed.data.instructions;
527
+ }
528
+ if (parsed.data.tools) {
529
+ codexBody.tools = parsed.data.tools;
530
+ }
531
+ if (typeof parsed.data.tool_choice !== "undefined") {
532
+ codexBody.tool_choice = parsed.data.tool_choice;
533
+ }
534
+ if (parsed.data.include) {
535
+ codexBody.include = parsed.data.include;
536
+ }
537
+ if (parsed.data.text) {
538
+ codexBody.text = parsed.data.text;
539
+ }
540
+ if (typeof parsed.data.store === "boolean") {
541
+ codexBody.store = parsed.data.store;
542
+ }
543
+ if (typeof parsed.data.parallel_tool_calls === "boolean") {
544
+ codexBody.parallel_tool_calls = parsed.data.parallel_tool_calls;
545
+ }
91
546
  const result = await ctx.chatService.chat({
92
547
  model: parsed.data.model,
93
- input,
94
- system: parsed.data.instructions
548
+ input: input || void 0,
549
+ system: parsed.data.instructions,
550
+ experimental: {
551
+ codexBody,
552
+ allowUnknownModel: parsed.data.experimental_codex?.allow_unknown_model
553
+ }
95
554
  });
96
- return {
97
- object: "response",
98
- provider: result.provider,
99
- model: result.model,
100
- output_text: result.text,
101
- output: [
102
- {
103
- type: "message",
104
- role: "assistant",
105
- content: [
106
- {
107
- type: "output_text",
108
- text: result.text
109
- }
110
- ]
555
+ return buildResponseApiBody(result, parsed.data.experimental_codex?.include_raw);
556
+ });
557
+ app.post("/v1/chat/completions", async (request, reply) => {
558
+ const parsed = chatCompletionsBodySchema.safeParse(request.body);
559
+ if (!parsed.success) {
560
+ reply.code(400);
561
+ return {
562
+ error: {
563
+ type: "validation_error",
564
+ message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
111
565
  }
112
- ]
113
- };
566
+ };
567
+ }
568
+ if (parsed.data.stream) {
569
+ reply.code(501);
570
+ return {
571
+ error: {
572
+ type: "not_supported",
573
+ message: "\u5F53\u524D\u7F51\u5173\u6682\u4E0D\u652F\u6301 chat.completions \u7684 stream=true"
574
+ }
575
+ };
576
+ }
577
+ if (typeof parsed.data.n === "number" && parsed.data.n > 1) {
578
+ reply.code(501);
579
+ return {
580
+ error: {
581
+ type: "not_supported",
582
+ message: "\u5F53\u524D\u7F51\u5173\u6682\u4E0D\u652F\u6301\u4E00\u6B21\u8FD4\u56DE\u591A\u4E2A choices\uFF08n > 1\uFF09"
583
+ }
584
+ };
585
+ }
586
+ const codexBody = createChatCompletionsCodexBody(parsed.data);
587
+ const fallbackInput = parsed.data.messages.map(
588
+ (message) => typeof message.content === "string" ? message.content : (message.content ?? []).map((part) => typeof part.text === "string" ? part.text : "").filter(Boolean).join("\n")
589
+ ).filter(Boolean).join("\n").trim();
590
+ const result = await ctx.chatService.chat({
591
+ model: parsed.data.model,
592
+ input: fallbackInput || void 0,
593
+ experimental: {
594
+ codexBody
595
+ }
596
+ });
597
+ return buildChatCompletionsBody(result);
598
+ });
599
+ app.post("/v1/images/generations", async (request, reply) => {
600
+ const parsed = imageGenerationsBodySchema.safeParse(request.body);
601
+ if (!parsed.success) {
602
+ console.error("[gateway:image] validation failure", {
603
+ method: request.method,
604
+ url: request.url,
605
+ issue: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
606
+ });
607
+ reply.code(400);
608
+ return {
609
+ error: {
610
+ type: "validation_error",
611
+ message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
612
+ }
613
+ };
614
+ }
615
+ const validationError = validateImageRequest(parsed.data);
616
+ if (validationError) {
617
+ console.error("[gateway:image] validation failure", {
618
+ method: request.method,
619
+ url: request.url,
620
+ summary: summarizeImageRequestForLog(parsed.data),
621
+ issue: validationError
622
+ });
623
+ reply.code(400);
624
+ return {
625
+ error: {
626
+ type: "validation_error",
627
+ message: validationError
628
+ }
629
+ };
630
+ }
631
+ if (typeof parsed.data.n === "number" && parsed.data.n > 1) {
632
+ console.error("[gateway:image] not supported", {
633
+ method: request.method,
634
+ url: request.url,
635
+ summary: summarizeImageRequestForLog(parsed.data),
636
+ issue: "\u5F53\u524D\u7F51\u5173\u6682\u4E0D\u652F\u6301 images.generations \u4E00\u6B21\u8FD4\u56DE\u591A\u5F20\u56FE\uFF08n > 1\uFF09"
637
+ });
638
+ reply.code(501);
639
+ return {
640
+ error: {
641
+ type: "not_supported",
642
+ message: "\u5F53\u524D\u7F51\u5173\u6682\u4E0D\u652F\u6301 images.generations \u4E00\u6B21\u8FD4\u56DE\u591A\u5F20\u56FE\uFF08n > 1\uFF09"
643
+ }
644
+ };
645
+ }
646
+ const requestSummary = summarizeImageRequestForLog(parsed.data);
647
+ console.info("[gateway:image] request accepted", {
648
+ method: request.method,
649
+ url: request.url,
650
+ summary: requestSummary
651
+ });
652
+ const response = await ctx.imageService.generate({
653
+ prompt: parsed.data.prompt,
654
+ model: parsed.data.model,
655
+ n: parsed.data.n,
656
+ size: parsed.data.size,
657
+ quality: parsed.data.quality,
658
+ background: parsed.data.background,
659
+ outputFormat: parsed.data.output_format,
660
+ outputCompression: parsed.data.output_compression,
661
+ moderation: parsed.data.moderation
662
+ });
663
+ console.info("[gateway:image] response ready", {
664
+ method: request.method,
665
+ url: request.url,
666
+ summary: requestSummary,
667
+ created: response.created,
668
+ imageCount: response.data.length,
669
+ output_format: response.output_format,
670
+ quality: response.quality,
671
+ size: response.size
672
+ });
673
+ return response;
114
674
  });
115
675
  return app;
116
676
  }
117
677
  export {
118
678
  createApp
119
679
  };
120
- //# sourceMappingURL=app.js.map
@@ -31,4 +31,3 @@ async function startServer(params) {
31
31
  export {
32
32
  startServer
33
33
  };
34
- //# sourceMappingURL=index.js.map
package/dist/store.js CHANGED
@@ -17,4 +17,3 @@ export {
17
17
  saveProfile,
18
18
  saveStore
19
19
  };
20
- //# sourceMappingURL=store.js.map