ai-zero-token 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.
Files changed (67) hide show
  1. package/README.md +235 -58
  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 +42 -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 +566 -39
  34. package/dist/server/index.js +13 -3
  35. package/dist/store.js +0 -1
  36. package/package.json +14 -6
  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
@@ -12,6 +12,7 @@ const TOKEN_URL = "https://auth.openai.com/oauth/token";
12
12
  const REDIRECT_URI = "http://localhost:1455/auth/callback";
13
13
  const SCOPE = "openid profile email offline_access";
14
14
  const JWT_CLAIM_PATH = "https://api.openai.com/auth";
15
+ const PROFILE_CLAIM_PATH = "https://api.openai.com/profile";
15
16
  const SUCCESS_HTML = `<!doctype html>
16
17
  <html lang="zh-CN">
17
18
  <head>
@@ -41,6 +42,18 @@ function decodeJwtPayload(token) {
41
42
  return null;
42
43
  }
43
44
  }
45
+ function extractEmailFromPayload(payload) {
46
+ const profileClaim = payload?.[PROFILE_CLAIM_PATH];
47
+ const nestedEmail = profileClaim?.email;
48
+ if (typeof nestedEmail === "string" && nestedEmail.trim()) {
49
+ return nestedEmail.trim();
50
+ }
51
+ const topLevelEmail = payload?.email;
52
+ if (typeof topLevelEmail === "string" && topLevelEmail.trim()) {
53
+ return topLevelEmail.trim();
54
+ }
55
+ return void 0;
56
+ }
44
57
  function parseAuthorizationInput(value) {
45
58
  const trimmed = value.trim();
46
59
  if (!trimmed) {
@@ -74,11 +87,10 @@ function extractProfile(accessToken, refreshToken, expires) {
74
87
  if (typeof accountId !== "string" || !accountId.trim()) {
75
88
  throw new Error("\u65E0\u6CD5\u4ECE access token \u4E2D\u63D0\u53D6 accountId\u3002");
76
89
  }
77
- const email = typeof payload?.email === "string" && payload.email.trim() ? payload.email.trim() : void 0;
78
- const profileKey = email ?? accountId;
90
+ const email = extractEmailFromPayload(payload);
79
91
  return {
80
92
  provider: "openai-codex",
81
- profileId: `openai-codex:${profileKey}`,
93
+ profileId: `openai-codex:${accountId}`,
82
94
  mode: "oauth_account",
83
95
  access: accessToken,
84
96
  refresh: refreshToken,
@@ -281,4 +293,3 @@ export {
281
293
  loginOpenAICodex,
282
294
  refreshOpenAICodexToken
283
295
  };
284
- //# sourceMappingURL=oauth.js.map
@@ -18,4 +18,3 @@ async function generatePKCE() {
18
18
  export {
19
19
  generatePKCE
20
20
  };
21
- //# sourceMappingURL=pkce.js.map
@@ -2,7 +2,11 @@
2
2
  import {
3
3
  clearStore,
4
4
  getActiveProfile,
5
- saveProfile
5
+ listProfiles,
6
+ removeProfile,
7
+ saveProfile,
8
+ setActiveProfile,
9
+ updateProfile
6
10
  } from "../store/profile-store.js";
7
11
  import {
8
12
  loginOpenAICodex,
@@ -12,47 +16,116 @@ class AuthService {
12
16
  constructor(configService) {
13
17
  this.configService = configService;
14
18
  }
19
+ maskSecret(value) {
20
+ if (value.length <= 12) {
21
+ return value;
22
+ }
23
+ return `${value.slice(0, 8)}...${value.slice(-6)}`;
24
+ }
25
+ toManagedProfile(profile) {
26
+ return {
27
+ ...profile,
28
+ mode: "oauth_account"
29
+ };
30
+ }
31
+ toProfileSummary(profile, activeProfileId) {
32
+ return {
33
+ provider: profile.provider,
34
+ profileId: profile.profileId,
35
+ accountId: profile.accountId,
36
+ email: profile.email,
37
+ quota: profile.quota,
38
+ expiresAt: profile.expires,
39
+ accessTokenPreview: this.maskSecret(profile.access),
40
+ refreshTokenPreview: this.maskSecret(profile.refresh),
41
+ isActive: profile.profileId === activeProfileId
42
+ };
43
+ }
15
44
  async login(provider) {
16
45
  if (provider !== "openai-codex") {
17
46
  throw new Error(`\u6682\u4E0D\u652F\u6301 provider: ${provider}`);
18
47
  }
19
48
  const profile = await loginOpenAICodex();
20
49
  await saveProfile(profile);
21
- return {
22
- ...profile,
23
- mode: "oauth_account"
24
- };
50
+ return this.toManagedProfile(profile);
25
51
  }
26
52
  async getActiveProfile(provider = "openai-codex") {
27
53
  const profile = await getActiveProfile();
28
54
  if (!profile || profile.provider !== provider) {
29
55
  return null;
30
56
  }
31
- return {
32
- ...profile,
33
- mode: "oauth_account"
34
- };
57
+ return this.toManagedProfile(profile);
58
+ }
59
+ async listProfiles(provider = "openai-codex") {
60
+ const [profiles, activeProfile] = await Promise.all([
61
+ listProfiles(),
62
+ this.getActiveProfile(provider)
63
+ ]);
64
+ const activeProfileId = activeProfile?.profileId;
65
+ return profiles.filter((profile) => profile.provider === provider).sort((left, right) => {
66
+ if (left.profileId === activeProfileId) {
67
+ return -1;
68
+ }
69
+ if (right.profileId === activeProfileId) {
70
+ return 1;
71
+ }
72
+ return right.expires - left.expires;
73
+ }).map((profile) => this.toProfileSummary(profile, activeProfileId));
74
+ }
75
+ async activateProfile(profileId, provider = "openai-codex") {
76
+ const profiles = await listProfiles();
77
+ const target = profiles.find((profile) => profile.profileId === profileId && profile.provider === provider);
78
+ if (!target) {
79
+ throw new Error(`\u6CA1\u6709\u627E\u5230\u53EF\u5207\u6362\u7684\u8D26\u53F7: ${profileId}`);
80
+ }
81
+ const activated = await setActiveProfile(profileId);
82
+ if (!activated) {
83
+ throw new Error(`\u5207\u6362\u8D26\u53F7\u5931\u8D25: ${profileId}`);
84
+ }
85
+ return this.toManagedProfile(activated);
86
+ }
87
+ async removeProfile(profileId, provider = "openai-codex") {
88
+ const profiles = await listProfiles();
89
+ const target = profiles.find((profile) => profile.profileId === profileId && profile.provider === provider);
90
+ if (!target) {
91
+ throw new Error(`\u6CA1\u6709\u627E\u5230\u8981\u5220\u9664\u7684\u8D26\u53F7: ${profileId}`);
92
+ }
93
+ await removeProfile(profileId);
35
94
  }
36
95
  async requireUsableProfile(provider = "openai-codex") {
37
96
  const profile = await this.getActiveProfile(provider);
38
97
  if (!profile) {
39
- throw new Error(`\u8FD8\u6CA1\u6709\u767B\u5F55 ${provider}\u3002\u5148\u8FD0\u884C bun src/cli.js login`);
98
+ throw new Error(`\u8FD8\u6CA1\u6709\u767B\u5F55 ${provider}\u3002\u5148\u8FD0\u884C azt login`);
40
99
  }
41
100
  if (Date.now() < profile.expires) {
42
101
  return profile;
43
102
  }
44
103
  const refreshed = await refreshOpenAICodexToken(profile);
45
104
  await saveProfile(refreshed);
46
- return {
47
- ...refreshed,
48
- mode: "oauth_account"
49
- };
105
+ return this.toManagedProfile(refreshed);
50
106
  }
51
107
  async logoutAll() {
52
108
  await clearStore();
53
109
  }
110
+ async updateProfileQuota(profileId, quota, provider = "openai-codex") {
111
+ if (!quota) {
112
+ return;
113
+ }
114
+ await updateProfile(profileId, (profile) => {
115
+ if (profile.provider !== provider) {
116
+ return profile;
117
+ }
118
+ return {
119
+ ...profile,
120
+ quota
121
+ };
122
+ });
123
+ }
54
124
  async getStatus() {
55
- const profile = await this.getActiveProfile();
125
+ const [profile, profiles] = await Promise.all([
126
+ this.getActiveProfile(),
127
+ this.listProfiles()
128
+ ]);
56
129
  const defaultModel = await this.configService.getDefaultModel();
57
130
  const server = await this.configService.getServerConfig();
58
131
  return {
@@ -62,6 +135,7 @@ class AuthService {
62
135
  defaultModel,
63
136
  loggedIn: Boolean(profile),
64
137
  expiresAt: profile?.expires,
138
+ profileCount: profiles.length,
65
139
  serverHost: server.host,
66
140
  serverPort: server.port
67
141
  };
@@ -70,4 +144,3 @@ class AuthService {
70
144
  export {
71
145
  AuthService
72
146
  };
73
- //# sourceMappingURL=auth-service.js.map
@@ -6,23 +6,33 @@ class ChatService {
6
6
  }
7
7
  async chat(request) {
8
8
  const provider = request.provider ?? "openai-codex";
9
- const model = await this.deps.modelService.resolveModel(provider, request.model);
10
- const profile = await this.deps.authService.requireUsableProfile(provider);
11
- const result = await askOpenAICodex({
12
- profile,
13
- prompt: request.input,
14
- model,
15
- system: request.system
9
+ const model = await this.deps.modelService.resolveModel(provider, request.model, {
10
+ allowUnknown: request.experimental?.allowUnknownModel
16
11
  });
17
- return {
18
- provider,
19
- model,
20
- text: result.text,
21
- raw: result.raw
22
- };
12
+ const profile = await this.deps.authService.requireUsableProfile(provider);
13
+ try {
14
+ const result = await askOpenAICodex({
15
+ profile,
16
+ prompt: request.input,
17
+ model,
18
+ system: request.system,
19
+ bodyOverride: request.experimental?.codexBody
20
+ });
21
+ await this.deps.authService.updateProfileQuota(profile.profileId, result.quota, provider);
22
+ return {
23
+ provider,
24
+ model,
25
+ text: result.text,
26
+ raw: result.raw,
27
+ artifacts: result.artifacts
28
+ };
29
+ } catch (error) {
30
+ const quota = error.quota;
31
+ await this.deps.authService.updateProfileQuota(profile.profileId, quota, provider);
32
+ throw error;
33
+ }
23
34
  }
24
35
  }
25
36
  export {
26
37
  ChatService
27
38
  };
28
- //# sourceMappingURL=chat-service.js.map
@@ -66,4 +66,3 @@ class ConfigService {
66
66
  export {
67
67
  ConfigService
68
68
  };
69
- //# sourceMappingURL=config-service.js.map
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/env node
2
+ import { askOpenAICodex } from "../providers/openai-codex/chat.js";
3
+ const SUPPORTED_IMAGE_MODELS = /* @__PURE__ */ new Set([
4
+ "gpt-image-1",
5
+ "gpt-image-1-mini",
6
+ "gpt-image-1.5",
7
+ "gpt-image-2"
8
+ ]);
9
+ const SUPPORTED_IMAGE_SIZES = /* @__PURE__ */ new Set([
10
+ "1024x1024",
11
+ "1024x1536",
12
+ "1536x1024"
13
+ ]);
14
+ const SUPPORTED_IMAGE_QUALITIES = /* @__PURE__ */ new Set([
15
+ "low",
16
+ "medium",
17
+ "high"
18
+ ]);
19
+ const SUPPORTED_IMAGE_FORMATS = /* @__PURE__ */ new Set([
20
+ "png",
21
+ "webp",
22
+ "jpeg"
23
+ ]);
24
+ const SUPPORTED_IMAGE_BACKGROUNDS = /* @__PURE__ */ new Set([
25
+ "transparent",
26
+ "opaque"
27
+ ]);
28
+ function truncateForLog(value, max = 160) {
29
+ if (value.length <= max) {
30
+ return value;
31
+ }
32
+ return `${value.slice(0, max)}...`;
33
+ }
34
+ function isRecord(value) {
35
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
36
+ }
37
+ function toImageGenerationOutput(value) {
38
+ if (!isRecord(value) || value.type !== "image_generation_call") {
39
+ return null;
40
+ }
41
+ return value;
42
+ }
43
+ function toImageGenerationEventOutput(value) {
44
+ if (!isRecord(value) || typeof value.type !== "string") {
45
+ return null;
46
+ }
47
+ if (value.type.startsWith("response.output_item.") && isRecord(value.item)) {
48
+ return toImageGenerationOutput(value.item);
49
+ }
50
+ if (value.type === "response.image_generation_call.partial_image") {
51
+ return {
52
+ id: typeof value.item_id === "string" ? value.item_id : void 0,
53
+ type: "image_generation_call",
54
+ partial_image_b64: typeof value.partial_image_b64 === "string" ? value.partial_image_b64 : void 0,
55
+ background: typeof value.background === "string" ? value.background : void 0,
56
+ output_format: typeof value.output_format === "string" ? value.output_format : void 0,
57
+ size: typeof value.size === "string" ? value.size : void 0
58
+ };
59
+ }
60
+ return null;
61
+ }
62
+ function normalizeReturnedSize(size, fallback) {
63
+ if (typeof size === "string" && SUPPORTED_IMAGE_SIZES.has(size)) {
64
+ return size;
65
+ }
66
+ if (typeof fallback === "string" && SUPPORTED_IMAGE_SIZES.has(fallback)) {
67
+ return fallback;
68
+ }
69
+ return void 0;
70
+ }
71
+ function normalizeReturnedQuality(quality) {
72
+ if (typeof quality === "string" && SUPPORTED_IMAGE_QUALITIES.has(quality)) {
73
+ return quality;
74
+ }
75
+ return void 0;
76
+ }
77
+ function normalizeReturnedFormat(format) {
78
+ if (typeof format === "string" && SUPPORTED_IMAGE_FORMATS.has(format)) {
79
+ return format;
80
+ }
81
+ return void 0;
82
+ }
83
+ function normalizeReturnedBackground(background) {
84
+ if (typeof background === "string" && SUPPORTED_IMAGE_BACKGROUNDS.has(background)) {
85
+ return background;
86
+ }
87
+ return void 0;
88
+ }
89
+ function collectImageGenerationOutputs(raw) {
90
+ if (!isRecord(raw)) {
91
+ return [];
92
+ }
93
+ const finalItems = /* @__PURE__ */ new Map();
94
+ const partialItems = /* @__PURE__ */ new Map();
95
+ const response = isRecord(raw.response) ? raw.response : null;
96
+ const events = Array.isArray(raw.events) ? raw.events : [];
97
+ if (response && Array.isArray(response.output)) {
98
+ for (const output of response.output) {
99
+ const image = toImageGenerationOutput(output);
100
+ if (!image || !image.id) {
101
+ continue;
102
+ }
103
+ if (typeof image.result === "string" && image.result.length > 0) {
104
+ finalItems.set(image.id, image);
105
+ } else if (typeof image.partial_image_b64 === "string" && image.partial_image_b64.length > 0) {
106
+ partialItems.set(image.id, image);
107
+ }
108
+ }
109
+ }
110
+ for (const event of events) {
111
+ const image = toImageGenerationEventOutput(event);
112
+ if (!image || !image.id) {
113
+ continue;
114
+ }
115
+ if (typeof image.result === "string" && image.result.length > 0) {
116
+ finalItems.set(image.id, image);
117
+ } else if (typeof image.partial_image_b64 === "string" && image.partial_image_b64.length > 0) {
118
+ partialItems.set(image.id, image);
119
+ }
120
+ }
121
+ if (finalItems.size > 0) {
122
+ return Array.from(finalItems.values());
123
+ }
124
+ return Array.from(partialItems.values()).map((item) => ({
125
+ ...item,
126
+ result: item.partial_image_b64
127
+ }));
128
+ }
129
+ function summarizeImageDebug(raw) {
130
+ if (!isRecord(raw)) {
131
+ return {
132
+ rawType: typeof raw
133
+ };
134
+ }
135
+ const response = isRecord(raw.response) ? raw.response : null;
136
+ const events = Array.isArray(raw.events) ? raw.events : [];
137
+ const imageEvents = events.filter((event) => isRecord(event) && typeof event.type === "string" && event.type.includes("image_generation")).slice(0, 12).map((event) => {
138
+ const safeEvent = event;
139
+ return {
140
+ type: safeEvent.type,
141
+ item_id: typeof safeEvent.item_id === "string" ? safeEvent.item_id : void 0,
142
+ output_index: typeof safeEvent.output_index === "number" ? safeEvent.output_index : void 0,
143
+ partial_image_b64_length: typeof safeEvent.partial_image_b64 === "string" ? safeEvent.partial_image_b64.length : void 0
144
+ };
145
+ });
146
+ return {
147
+ response_status: typeof response?.status === "string" ? response.status : void 0,
148
+ response_error: isRecord(response?.error) ? {
149
+ code: typeof response.error.code === "string" ? response.error.code : void 0,
150
+ message: typeof response.error.message === "string" ? response.error.message : void 0,
151
+ type: typeof response.error.type === "string" ? response.error.type : void 0
152
+ } : void 0,
153
+ response_output_length: Array.isArray(response?.output) ? response.output.length : 0,
154
+ event_count: events.length,
155
+ event_types: events.filter((event) => isRecord(event) && typeof event.type === "string").slice(0, 20).map((event) => event.type),
156
+ error_events: events.filter((event) => isRecord(event) && (event.type === "error" || event.type === "response.failed")).slice(0, 5).map((event) => {
157
+ const safeEvent = event;
158
+ const eventError = isRecord(safeEvent.error) ? safeEvent.error : null;
159
+ const eventResponse = isRecord(safeEvent.response) ? safeEvent.response : null;
160
+ const responseError = eventResponse && isRecord(eventResponse.error) ? eventResponse.error : null;
161
+ return {
162
+ type: safeEvent.type,
163
+ code: typeof eventError?.code === "string" ? eventError.code : typeof responseError?.code === "string" ? responseError.code : void 0,
164
+ message: typeof eventError?.message === "string" ? eventError.message : typeof responseError?.message === "string" ? responseError.message : void 0
165
+ };
166
+ }),
167
+ image_events: imageEvents
168
+ };
169
+ }
170
+ function extractImageFailureMessage(raw) {
171
+ if (!isRecord(raw)) {
172
+ return null;
173
+ }
174
+ const response = isRecord(raw.response) ? raw.response : null;
175
+ if (response) {
176
+ const responseError = isRecord(response.error) ? response.error : null;
177
+ const responseStatus = typeof response.status === "string" ? response.status : void 0;
178
+ const responseMessage = typeof responseError?.message === "string" ? responseError.message : typeof responseError?.code === "string" ? responseError.code : null;
179
+ if (responseStatus === "failed" && responseMessage) {
180
+ return responseMessage;
181
+ }
182
+ }
183
+ const events = Array.isArray(raw.events) ? raw.events : [];
184
+ for (const event of events) {
185
+ if (!isRecord(event)) {
186
+ continue;
187
+ }
188
+ if (event.type === "error") {
189
+ const eventError = isRecord(event.error) ? event.error : event;
190
+ const message = typeof eventError.message === "string" ? eventError.message : typeof eventError.code === "string" ? eventError.code : null;
191
+ if (message) {
192
+ return message;
193
+ }
194
+ }
195
+ if (event.type === "response.failed" && isRecord(event.response)) {
196
+ const responseError = isRecord(event.response.error) ? event.response.error : null;
197
+ const message = typeof responseError?.message === "string" ? responseError.message : typeof responseError?.code === "string" ? responseError.code : null;
198
+ if (message) {
199
+ return message;
200
+ }
201
+ }
202
+ }
203
+ return null;
204
+ }
205
+ function extractImageUsage(raw) {
206
+ if (!isRecord(raw) || !isRecord(raw.response)) {
207
+ return void 0;
208
+ }
209
+ const toolUsage = isRecord(raw.response.tool_usage) ? raw.response.tool_usage : null;
210
+ const imageGen = toolUsage && isRecord(toolUsage.image_gen) ? toolUsage.image_gen : null;
211
+ if (!imageGen || typeof imageGen.input_tokens !== "number" || typeof imageGen.output_tokens !== "number" || typeof imageGen.total_tokens !== "number") {
212
+ return void 0;
213
+ }
214
+ return {
215
+ input_tokens: imageGen.input_tokens,
216
+ input_tokens_details: isRecord(imageGen.input_tokens_details) ? {
217
+ image_tokens: Number(imageGen.input_tokens_details.image_tokens ?? 0),
218
+ text_tokens: Number(imageGen.input_tokens_details.text_tokens ?? 0)
219
+ } : void 0,
220
+ output_tokens: imageGen.output_tokens,
221
+ output_tokens_details: isRecord(imageGen.output_tokens_details) ? {
222
+ image_tokens: Number(imageGen.output_tokens_details.image_tokens ?? 0),
223
+ text_tokens: Number(imageGen.output_tokens_details.text_tokens ?? 0)
224
+ } : void 0,
225
+ total_tokens: imageGen.total_tokens
226
+ };
227
+ }
228
+ class ImageService {
229
+ constructor(deps) {
230
+ this.deps = deps;
231
+ }
232
+ resolveRequestedImageModel(model) {
233
+ if (!model) {
234
+ return "gpt-image-2";
235
+ }
236
+ if (!SUPPORTED_IMAGE_MODELS.has(model)) {
237
+ throw new Error(`\u5F53\u524D\u7F51\u5173\u4EC5\u652F\u6301\u8FD9\u4E9B\u751F\u56FE\u6A21\u578B: ${Array.from(SUPPORTED_IMAGE_MODELS).join(", ")}`);
238
+ }
239
+ return model;
240
+ }
241
+ isFreePlan(profile) {
242
+ return profile.quota?.planType === "free";
243
+ }
244
+ async generate(request) {
245
+ const profile = await this.deps.authService.requireUsableProfile("openai-codex");
246
+ if (this.isFreePlan(profile)) {
247
+ throw new Error("\u5F53\u524D\u8D26\u53F7\u4E3A free \u5957\u9910\uFF0C\u4E0D\u652F\u6301\u56FE\u7247\u751F\u6210\u3002\u8BF7\u5207\u6362\u5230 Plus \u6216\u66F4\u9AD8\u5957\u9910\u8D26\u53F7\u3002");
248
+ }
249
+ const orchestratorModel = await this.deps.configService.getDefaultModel();
250
+ const requestedImageModel = this.resolveRequestedImageModel(request.model);
251
+ const requestSummary = {
252
+ requestedImageModel,
253
+ orchestratorModel,
254
+ promptLength: request.prompt.length,
255
+ promptPreview: truncateForLog(request.prompt),
256
+ size: request.size ?? "default",
257
+ quality: request.quality ?? "default",
258
+ background: request.background ?? "default",
259
+ outputFormat: request.outputFormat ?? "default",
260
+ outputCompression: typeof request.outputCompression === "number" ? request.outputCompression : void 0,
261
+ moderation: request.moderation ?? "default"
262
+ };
263
+ console.info("[gateway:image] upstream request", requestSummary);
264
+ const tool = {
265
+ type: "image_generation",
266
+ model: requestedImageModel
267
+ };
268
+ if (request.size) {
269
+ tool.size = request.size;
270
+ }
271
+ if (request.quality) {
272
+ tool.quality = request.quality;
273
+ }
274
+ if (request.background) {
275
+ tool.background = request.background;
276
+ }
277
+ if (request.outputFormat) {
278
+ tool.output_format = request.outputFormat;
279
+ }
280
+ if (typeof request.outputCompression === "number") {
281
+ tool.output_compression = request.outputCompression;
282
+ }
283
+ if (request.moderation) {
284
+ tool.moderation = request.moderation;
285
+ }
286
+ let result;
287
+ try {
288
+ result = await askOpenAICodex({
289
+ profile,
290
+ model: orchestratorModel,
291
+ bodyOverride: {
292
+ model: orchestratorModel,
293
+ input: [
294
+ {
295
+ role: "user",
296
+ content: [
297
+ {
298
+ type: "input_text",
299
+ text: request.prompt
300
+ }
301
+ ]
302
+ }
303
+ ],
304
+ tools: [tool],
305
+ tool_choice: {
306
+ type: "image_generation"
307
+ },
308
+ include: ["reasoning.encrypted_content"]
309
+ }
310
+ });
311
+ await this.deps.authService.updateProfileQuota(profile.profileId, result.quota, "openai-codex");
312
+ } catch (error) {
313
+ const quota = error.quota;
314
+ await this.deps.authService.updateProfileQuota(profile.profileId, quota, "openai-codex");
315
+ throw error;
316
+ }
317
+ const raw = isRecord(result.raw) ? result.raw : {};
318
+ const response = isRecord(raw.response) ? raw.response : null;
319
+ const images = collectImageGenerationOutputs(raw);
320
+ const debugSummary = summarizeImageDebug(raw);
321
+ if (images.length === 0) {
322
+ const upstreamFailure = extractImageFailureMessage(raw);
323
+ console.error("[gateway:image] parse failure", {
324
+ ...requestSummary,
325
+ upstreamFailure,
326
+ debug: debugSummary
327
+ });
328
+ if (upstreamFailure) {
329
+ throw new Error(`\u4E0A\u6E38\u56FE\u7247\u751F\u6210\u5931\u8D25: ${upstreamFailure}`);
330
+ }
331
+ throw new Error("\u56FE\u7247\u751F\u6210\u8BF7\u6C42\u5DF2\u5B8C\u6210\uFF0C\u4F46\u6CA1\u6709\u89E3\u6790\u51FA image_generation_call \u7ED3\u679C\u3002");
332
+ }
333
+ const first = images[0];
334
+ const imageResult = {
335
+ created: typeof response?.created_at === "number" ? response.created_at : Math.floor(Date.now() / 1e3),
336
+ data: images.map((image) => ({
337
+ b64_json: image.result ?? "",
338
+ ...image.revised_prompt ? { revised_prompt: image.revised_prompt } : {}
339
+ })),
340
+ background: normalizeReturnedBackground(first.background),
341
+ output_format: normalizeReturnedFormat(first.output_format),
342
+ quality: normalizeReturnedQuality(first.quality),
343
+ size: normalizeReturnedSize(first.size, request.size),
344
+ usage: extractImageUsage(raw)
345
+ };
346
+ console.info("[gateway:image] upstream response", {
347
+ ...requestSummary,
348
+ imageCount: imageResult.data.length,
349
+ firstImageBase64Length: imageResult.data[0]?.b64_json.length ?? 0,
350
+ outputFormat: imageResult.output_format ?? request.outputFormat ?? "unknown",
351
+ quality: imageResult.quality ?? request.quality ?? "unknown",
352
+ size: imageResult.size ?? request.size ?? "unknown",
353
+ debug: debugSummary
354
+ });
355
+ return imageResult;
356
+ }
357
+ }
358
+ export {
359
+ ImageService
360
+ };
@@ -23,13 +23,16 @@ class ModelService {
23
23
  }
24
24
  return this.configService.getDefaultModel(provider);
25
25
  }
26
- async resolveModel(provider = "openai-codex", requested) {
26
+ async resolveModel(provider = "openai-codex", requested, options) {
27
27
  if (provider !== "openai-codex") {
28
28
  throw new Error(`\u6682\u4E0D\u652F\u6301 provider: ${provider}`);
29
29
  }
30
30
  if (!requested) {
31
31
  return this.configService.getDefaultModel(provider);
32
32
  }
33
+ if (options?.allowUnknown) {
34
+ return requested;
35
+ }
33
36
  if (!isSupportedCodexModel(requested)) {
34
37
  throw new Error(`\u5F53\u524D demo \u672A\u5185\u7F6E\u6A21\u578B: ${requested}`);
35
38
  }
@@ -39,4 +42,3 @@ class ModelService {
39
42
  export {
40
43
  ModelService
41
44
  };
42
- //# sourceMappingURL=model-service.js.map