ai-zero-token 1.0.1 → 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.
Files changed (67) hide show
  1. package/README.md +227 -70
  2. package/dist/api.js +0 -1
  3. package/dist/cli/commands/ask.js +131 -5
  4. package/dist/cli/commands/clear.js +0 -1
  5. package/dist/cli/commands/help.js +15 -10
  6. package/dist/cli/commands/login.js +0 -1
  7. package/dist/cli/commands/models.js +0 -1
  8. package/dist/cli/commands/serve.js +41 -4
  9. package/dist/cli/commands/start.js +10 -0
  10. package/dist/cli/commands/status.js +1 -1
  11. package/dist/cli/index.js +4 -1
  12. package/dist/cli/shared.js +57 -6
  13. package/dist/cli.js +0 -1
  14. package/dist/core/context.js +7 -2
  15. package/dist/core/models/openai-codex-models.js +0 -1
  16. package/dist/core/providers/http-client.js +97 -9
  17. package/dist/core/providers/openai-codex/chat.js +217 -24
  18. package/dist/core/providers/openai-codex/oauth.js +15 -4
  19. package/dist/core/providers/openai-codex/pkce.js +0 -1
  20. package/dist/core/services/auth-service.js +89 -16
  21. package/dist/core/services/chat-service.js +24 -14
  22. package/dist/core/services/config-service.js +0 -1
  23. package/dist/core/services/image-service.js +360 -0
  24. package/dist/core/services/model-service.js +4 -2
  25. package/dist/core/store/profile-store.js +79 -6
  26. package/dist/core/store/settings-store.js +1 -2
  27. package/dist/core/types.js +0 -1
  28. package/dist/http.js +0 -1
  29. package/dist/models.js +0 -1
  30. package/dist/oauth.js +0 -1
  31. package/dist/pkce.js +0 -1
  32. package/dist/server/admin-page.js +2615 -0
  33. package/dist/server/app.js +561 -39
  34. package/dist/server/index.js +0 -1
  35. package/dist/store.js +0 -1
  36. package/package.json +12 -3
  37. package/dist/api.js.map +0 -1
  38. package/dist/cli/commands/ask.js.map +0 -1
  39. package/dist/cli/commands/clear.js.map +0 -1
  40. package/dist/cli/commands/help.js.map +0 -1
  41. package/dist/cli/commands/login.js.map +0 -1
  42. package/dist/cli/commands/models.js.map +0 -1
  43. package/dist/cli/commands/serve.js.map +0 -1
  44. package/dist/cli/commands/status.js.map +0 -1
  45. package/dist/cli/index.js.map +0 -1
  46. package/dist/cli/shared.js.map +0 -1
  47. package/dist/cli.js.map +0 -1
  48. package/dist/core/context.js.map +0 -1
  49. package/dist/core/models/openai-codex-models.js.map +0 -1
  50. package/dist/core/providers/http-client.js.map +0 -1
  51. package/dist/core/providers/openai-codex/chat.js.map +0 -1
  52. package/dist/core/providers/openai-codex/oauth.js.map +0 -1
  53. package/dist/core/providers/openai-codex/pkce.js.map +0 -1
  54. package/dist/core/services/auth-service.js.map +0 -1
  55. package/dist/core/services/chat-service.js.map +0 -1
  56. package/dist/core/services/config-service.js.map +0 -1
  57. package/dist/core/services/model-service.js.map +0 -1
  58. package/dist/core/store/profile-store.js.map +0 -1
  59. package/dist/core/store/settings-store.js.map +0 -1
  60. package/dist/core/types.js.map +0 -1
  61. package/dist/http.js.map +0 -1
  62. package/dist/models.js.map +0 -1
  63. package/dist/oauth.js.map +0 -1
  64. package/dist/pkce.js.map +0 -1
  65. package/dist/server/app.js.map +0 -1
  66. package/dist/server/index.js.map +0 -1
  67. 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,204 @@ 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 message = normalizeError(error).message;
302
+ 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")) {
303
+ return 400;
304
+ }
305
+ if (message.includes("\u8FD8\u6CA1\u6709\u767B\u5F55")) {
306
+ return 401;
307
+ }
308
+ return 500;
309
+ }
39
310
  function createApp(params) {
40
311
  const app = Fastify({
41
312
  logger: false
@@ -43,13 +314,131 @@ function createApp(params) {
43
314
  const ctx = createGatewayContext();
44
315
  void app.register(cors, {
45
316
  origin: params?.corsOrigin ?? true,
46
- methods: ["GET", "POST", "OPTIONS"]
317
+ methods: ["GET", "POST", "PUT", "OPTIONS"]
318
+ });
319
+ app.setErrorHandler((error, request, reply) => {
320
+ const normalized = normalizeError(error);
321
+ const statusCode = getErrorStatusCode(normalized);
322
+ console.error("[gateway:error]", {
323
+ method: request.method,
324
+ url: request.url,
325
+ statusCode,
326
+ message: normalized.message,
327
+ stack: normalized.stack
328
+ });
329
+ reply.code(statusCode);
330
+ return {
331
+ error: {
332
+ type: "gateway_error",
333
+ message: normalized.message
334
+ }
335
+ };
336
+ });
337
+ async function buildAdminConfig(request) {
338
+ const [status, models, settings, profile, profiles] = await Promise.all([
339
+ ctx.authService.getStatus(),
340
+ ctx.modelService.listModels(),
341
+ ctx.configService.getSettings(),
342
+ ctx.authService.getActiveProfile(),
343
+ ctx.authService.listProfiles()
344
+ ]);
345
+ const origin = resolveOrigin(request);
346
+ return {
347
+ status,
348
+ settings,
349
+ models,
350
+ profile: serializeProfile(profile),
351
+ profiles: profiles.map((item) => serializeManagedProfile(item)),
352
+ adminUrl: `${origin}/`,
353
+ baseUrl: `${origin}/v1`,
354
+ supportedEndpoints: [
355
+ {
356
+ method: "GET",
357
+ path: "/v1/models",
358
+ description: "OpenAI models \u5217\u8868\u517C\u5BB9\u63A5\u53E3\u3002"
359
+ },
360
+ {
361
+ method: "POST",
362
+ path: "/v1/responses",
363
+ description: "OpenAI responses \u517C\u5BB9\u63A5\u53E3\u3002"
364
+ },
365
+ {
366
+ method: "POST",
367
+ path: "/v1/chat/completions",
368
+ description: "OpenAI chat.completions \u517C\u5BB9\u63A5\u53E3\u3002"
369
+ },
370
+ {
371
+ method: "POST",
372
+ path: "/v1/images/generations",
373
+ description: "OpenAI images.generations \u517C\u5BB9\u63A5\u53E3\u3002"
374
+ }
375
+ ]
376
+ };
377
+ }
378
+ app.get("/", async (_request, reply) => {
379
+ reply.header("Content-Type", "text/html; charset=utf-8");
380
+ return renderAdminPage();
381
+ });
382
+ app.get("/favicon.ico", async (_request, reply) => {
383
+ reply.code(204);
384
+ return "";
47
385
  });
48
386
  app.get("/_gateway/health", async () => ({ ok: true }));
49
387
  app.get("/_gateway/status", async () => ctx.authService.getStatus());
50
388
  app.get("/_gateway/models", async () => ({
51
389
  data: await ctx.modelService.listModels()
52
390
  }));
391
+ app.get("/_gateway/admin/config", async (request) => buildAdminConfig(request));
392
+ app.post("/_gateway/admin/login", async (request) => {
393
+ await ctx.authService.login("openai-codex");
394
+ return buildAdminConfig(request);
395
+ });
396
+ app.post("/_gateway/admin/logout", async (request) => {
397
+ await ctx.authService.logoutAll();
398
+ return buildAdminConfig(request);
399
+ });
400
+ app.post("/_gateway/admin/profiles/activate", async (request, reply) => {
401
+ const parsed = profileActionSchema.safeParse(request.body);
402
+ if (!parsed.success) {
403
+ reply.code(400);
404
+ return {
405
+ error: {
406
+ type: "validation_error",
407
+ message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
408
+ }
409
+ };
410
+ }
411
+ await ctx.authService.activateProfile(parsed.data.profileId);
412
+ return buildAdminConfig(request);
413
+ });
414
+ app.post("/_gateway/admin/profiles/remove", async (request, reply) => {
415
+ const parsed = profileActionSchema.safeParse(request.body);
416
+ if (!parsed.success) {
417
+ reply.code(400);
418
+ return {
419
+ error: {
420
+ type: "validation_error",
421
+ message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
422
+ }
423
+ };
424
+ }
425
+ await ctx.authService.removeProfile(parsed.data.profileId);
426
+ return buildAdminConfig(request);
427
+ });
428
+ app.put("/_gateway/admin/settings", async (request, reply) => {
429
+ const parsed = settingsUpdateSchema.safeParse(request.body);
430
+ if (!parsed.success) {
431
+ reply.code(400);
432
+ return {
433
+ error: {
434
+ type: "validation_error",
435
+ message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
436
+ }
437
+ };
438
+ }
439
+ await ctx.configService.setDefaultModel(parsed.data.defaultModel);
440
+ return buildAdminConfig(request);
441
+ });
53
442
  app.get("/v1/models", async () => ({
54
443
  object: "list",
55
444
  data: (await ctx.modelService.listModels()).map((model) => ({
@@ -74,47 +463,180 @@ function createApp(params) {
74
463
  return {
75
464
  error: {
76
465
  type: "not_supported",
77
- message: "\u7B2C\u4E00\u9636\u6BB5\u6682\u4E0D\u652F\u6301 stream=true"
466
+ message: "\u5F53\u524D\u7F51\u5173\u6682\u4E0D\u652F\u6301 stream=true"
78
467
  }
79
468
  };
80
469
  }
81
470
  const input = extractTextInput(parsed.data.input);
82
- if (!input) {
471
+ const hasInput = typeof parsed.data.input !== "undefined" || typeof parsed.data.experimental_codex?.body?.input !== "undefined";
472
+ if (!hasInput) {
83
473
  reply.code(400);
84
474
  return {
85
475
  error: {
86
476
  type: "validation_error",
87
- message: "\u6CA1\u6709\u89E3\u6790\u51FA\u6709\u6548\u7684 input \u6587\u672C"
477
+ message: "\u6CA1\u6709\u63D0\u4F9B input\uFF0C\u4E5F\u6CA1\u6709\u5728 experimental_codex.body \u91CC\u900F\u4F20 input"
88
478
  }
89
479
  };
90
480
  }
481
+ const codexBody = {
482
+ ...parsed.data.experimental_codex?.body ?? {}
483
+ };
484
+ const normalizedInput = normalizeResponseInput(parsed.data.input);
485
+ if (typeof normalizedInput !== "undefined") {
486
+ codexBody.input = normalizedInput;
487
+ }
488
+ if (typeof parsed.data.instructions === "string") {
489
+ codexBody.instructions = parsed.data.instructions;
490
+ }
491
+ if (parsed.data.tools) {
492
+ codexBody.tools = parsed.data.tools;
493
+ }
494
+ if (typeof parsed.data.tool_choice !== "undefined") {
495
+ codexBody.tool_choice = parsed.data.tool_choice;
496
+ }
497
+ if (parsed.data.include) {
498
+ codexBody.include = parsed.data.include;
499
+ }
500
+ if (parsed.data.text) {
501
+ codexBody.text = parsed.data.text;
502
+ }
503
+ if (typeof parsed.data.store === "boolean") {
504
+ codexBody.store = parsed.data.store;
505
+ }
506
+ if (typeof parsed.data.parallel_tool_calls === "boolean") {
507
+ codexBody.parallel_tool_calls = parsed.data.parallel_tool_calls;
508
+ }
91
509
  const result = await ctx.chatService.chat({
92
510
  model: parsed.data.model,
93
- input,
94
- system: parsed.data.instructions
511
+ input: input || void 0,
512
+ system: parsed.data.instructions,
513
+ experimental: {
514
+ codexBody,
515
+ allowUnknownModel: parsed.data.experimental_codex?.allow_unknown_model
516
+ }
95
517
  });
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
- ]
518
+ return buildResponseApiBody(result, parsed.data.experimental_codex?.include_raw);
519
+ });
520
+ app.post("/v1/chat/completions", async (request, reply) => {
521
+ const parsed = chatCompletionsBodySchema.safeParse(request.body);
522
+ if (!parsed.success) {
523
+ reply.code(400);
524
+ return {
525
+ error: {
526
+ type: "validation_error",
527
+ message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
111
528
  }
112
- ]
113
- };
529
+ };
530
+ }
531
+ if (parsed.data.stream) {
532
+ reply.code(501);
533
+ return {
534
+ error: {
535
+ type: "not_supported",
536
+ message: "\u5F53\u524D\u7F51\u5173\u6682\u4E0D\u652F\u6301 chat.completions \u7684 stream=true"
537
+ }
538
+ };
539
+ }
540
+ if (typeof parsed.data.n === "number" && parsed.data.n > 1) {
541
+ reply.code(501);
542
+ return {
543
+ error: {
544
+ type: "not_supported",
545
+ message: "\u5F53\u524D\u7F51\u5173\u6682\u4E0D\u652F\u6301\u4E00\u6B21\u8FD4\u56DE\u591A\u4E2A choices\uFF08n > 1\uFF09"
546
+ }
547
+ };
548
+ }
549
+ const codexBody = createChatCompletionsCodexBody(parsed.data);
550
+ const fallbackInput = parsed.data.messages.map(
551
+ (message) => typeof message.content === "string" ? message.content : (message.content ?? []).map((part) => typeof part.text === "string" ? part.text : "").filter(Boolean).join("\n")
552
+ ).filter(Boolean).join("\n").trim();
553
+ const result = await ctx.chatService.chat({
554
+ model: parsed.data.model,
555
+ input: fallbackInput || void 0,
556
+ experimental: {
557
+ codexBody
558
+ }
559
+ });
560
+ return buildChatCompletionsBody(result);
561
+ });
562
+ app.post("/v1/images/generations", async (request, reply) => {
563
+ const parsed = imageGenerationsBodySchema.safeParse(request.body);
564
+ if (!parsed.success) {
565
+ console.error("[gateway:image] validation failure", {
566
+ method: request.method,
567
+ url: request.url,
568
+ issue: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
569
+ });
570
+ reply.code(400);
571
+ return {
572
+ error: {
573
+ type: "validation_error",
574
+ message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
575
+ }
576
+ };
577
+ }
578
+ const validationError = validateImageRequest(parsed.data);
579
+ if (validationError) {
580
+ console.error("[gateway:image] validation failure", {
581
+ method: request.method,
582
+ url: request.url,
583
+ summary: summarizeImageRequestForLog(parsed.data),
584
+ issue: validationError
585
+ });
586
+ reply.code(400);
587
+ return {
588
+ error: {
589
+ type: "validation_error",
590
+ message: validationError
591
+ }
592
+ };
593
+ }
594
+ if (typeof parsed.data.n === "number" && parsed.data.n > 1) {
595
+ console.error("[gateway:image] not supported", {
596
+ method: request.method,
597
+ url: request.url,
598
+ summary: summarizeImageRequestForLog(parsed.data),
599
+ issue: "\u5F53\u524D\u7F51\u5173\u6682\u4E0D\u652F\u6301 images.generations \u4E00\u6B21\u8FD4\u56DE\u591A\u5F20\u56FE\uFF08n > 1\uFF09"
600
+ });
601
+ reply.code(501);
602
+ return {
603
+ error: {
604
+ type: "not_supported",
605
+ message: "\u5F53\u524D\u7F51\u5173\u6682\u4E0D\u652F\u6301 images.generations \u4E00\u6B21\u8FD4\u56DE\u591A\u5F20\u56FE\uFF08n > 1\uFF09"
606
+ }
607
+ };
608
+ }
609
+ const requestSummary = summarizeImageRequestForLog(parsed.data);
610
+ console.info("[gateway:image] request accepted", {
611
+ method: request.method,
612
+ url: request.url,
613
+ summary: requestSummary
614
+ });
615
+ const response = await ctx.imageService.generate({
616
+ prompt: parsed.data.prompt,
617
+ model: parsed.data.model,
618
+ n: parsed.data.n,
619
+ size: parsed.data.size,
620
+ quality: parsed.data.quality,
621
+ background: parsed.data.background,
622
+ outputFormat: parsed.data.output_format,
623
+ outputCompression: parsed.data.output_compression,
624
+ moderation: parsed.data.moderation
625
+ });
626
+ console.info("[gateway:image] response ready", {
627
+ method: request.method,
628
+ url: request.url,
629
+ summary: requestSummary,
630
+ created: response.created,
631
+ imageCount: response.data.length,
632
+ output_format: response.output_format,
633
+ quality: response.quality,
634
+ size: response.size
635
+ });
636
+ return response;
114
637
  });
115
638
  return app;
116
639
  }
117
640
  export {
118
641
  createApp
119
642
  };
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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ai-zero-token",
3
- "version": "1.0.1",
4
- "description": "A local-first single-user AI CLI and gateway with OpenAI Codex OAuth support.",
3
+ "version": "1.0.2",
4
+ "description": "Local-first OpenAI-compatible AI CLI and gateway with Codex OAuth, multi-account management, and gpt-image-2 image generation.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "repository": {
@@ -22,7 +22,14 @@
22
22
  "gateway",
23
23
  "oauth",
24
24
  "openai",
25
- "codex"
25
+ "codex",
26
+ "openai-compatible",
27
+ "chatgpt",
28
+ "gpt-image-2",
29
+ "image-generation",
30
+ "images-generations",
31
+ "local-gateway",
32
+ "multi-account"
26
33
  ],
27
34
  "engines": {
28
35
  "node": ">=22"
@@ -31,9 +38,11 @@
31
38
  "login": "bun src/cli.ts login",
32
39
  "status": "bun src/cli.ts status",
33
40
  "ask": "bun src/cli.ts ask",
41
+ "start": "bun src/cli.ts start",
34
42
  "serve": "bun src/cli.ts serve",
35
43
  "build": "tsup",
36
44
  "typecheck": "bunx tsc -p tsconfig.json --noEmit",
45
+ "prepack": "npm run build",
37
46
  "pack:dry": "npm pack --dry-run"
38
47
  },
39
48
  "dependencies": {