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
@@ -2,6 +2,101 @@
2
2
  import { DEFAULT_CODEX_MODEL } from "../../models/openai-codex-models.js";
3
3
  import { requestText } from "../http-client.js";
4
4
  const CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
5
+ const URL_KEY_RE = /(url|uri|href|download|preview|thumbnail|image|asset|file)/i;
6
+ const REFERENCE_KEY_RE = /(image|asset|file|media|blob|artifact|download|preview|thumbnail)/i;
7
+ const REFERENCE_VALUE_RE = /^(file|asset|image|img|media|blob)-[\w-]+$/i;
8
+ function parseOptionalNumber(value) {
9
+ if (typeof value !== "string") {
10
+ return void 0;
11
+ }
12
+ const trimmed = value.trim();
13
+ if (!trimmed) {
14
+ return void 0;
15
+ }
16
+ const parsed = Number(trimmed);
17
+ return Number.isFinite(parsed) ? parsed : void 0;
18
+ }
19
+ function parseOptionalBoolean(value) {
20
+ if (typeof value !== "string") {
21
+ return void 0;
22
+ }
23
+ const trimmed = value.trim().toLowerCase();
24
+ if (trimmed === "true") {
25
+ return true;
26
+ }
27
+ if (trimmed === "false") {
28
+ return false;
29
+ }
30
+ return void 0;
31
+ }
32
+ function parseOptionalText(value) {
33
+ if (typeof value !== "string") {
34
+ return void 0;
35
+ }
36
+ const trimmed = value.trim();
37
+ return trimmed ? trimmed : void 0;
38
+ }
39
+ function extractCodexQuotaSnapshot(headers, requestId) {
40
+ const activeLimit = parseOptionalText(headers["x-codex-active-limit"]);
41
+ const planType = parseOptionalText(headers["x-codex-plan-type"]);
42
+ const primaryUsedPercent = parseOptionalNumber(headers["x-codex-primary-used-percent"]);
43
+ const secondaryUsedPercent = parseOptionalNumber(headers["x-codex-secondary-used-percent"]);
44
+ const primaryWindowMinutes = parseOptionalNumber(headers["x-codex-primary-window-minutes"]);
45
+ const secondaryWindowMinutes = parseOptionalNumber(headers["x-codex-secondary-window-minutes"]);
46
+ const primaryResetAfterSeconds = parseOptionalNumber(headers["x-codex-primary-reset-after-seconds"]);
47
+ const secondaryResetAfterSeconds = parseOptionalNumber(headers["x-codex-secondary-reset-after-seconds"]);
48
+ const primaryResetAt = parseOptionalNumber(headers["x-codex-primary-reset-at"]);
49
+ const secondaryResetAt = parseOptionalNumber(headers["x-codex-secondary-reset-at"]);
50
+ const primaryOverSecondaryLimitPercent = parseOptionalNumber(
51
+ headers["x-codex-primary-over-secondary-limit-percent"]
52
+ );
53
+ const creditsHasCredits = parseOptionalBoolean(headers["x-codex-credits-has-credits"]);
54
+ const creditsUnlimited = parseOptionalBoolean(headers["x-codex-credits-unlimited"]);
55
+ const creditsBalance = parseOptionalText(headers["x-codex-credits-balance"]);
56
+ const promoCampaignId = parseOptionalText(headers["x-codex-promo-campaign-id"]);
57
+ const promoMessage = parseOptionalText(headers["x-codex-promo-message"]);
58
+ const hasQuotaData = [
59
+ activeLimit,
60
+ planType,
61
+ primaryUsedPercent,
62
+ secondaryUsedPercent,
63
+ primaryWindowMinutes,
64
+ secondaryWindowMinutes,
65
+ primaryResetAfterSeconds,
66
+ secondaryResetAfterSeconds,
67
+ primaryResetAt,
68
+ secondaryResetAt,
69
+ primaryOverSecondaryLimitPercent,
70
+ creditsHasCredits,
71
+ creditsUnlimited,
72
+ creditsBalance,
73
+ promoCampaignId,
74
+ promoMessage
75
+ ].some((value) => typeof value !== "undefined");
76
+ if (!hasQuotaData) {
77
+ return void 0;
78
+ }
79
+ return {
80
+ capturedAt: Date.now(),
81
+ sourceRequestId: requestId,
82
+ activeLimit,
83
+ planType,
84
+ primaryUsedPercent,
85
+ secondaryUsedPercent,
86
+ primaryWindowMinutes,
87
+ secondaryWindowMinutes,
88
+ primaryResetAfterSeconds,
89
+ secondaryResetAfterSeconds,
90
+ primaryResetAt,
91
+ secondaryResetAt,
92
+ primaryOverSecondaryLimitPercent,
93
+ creditsHasCredits,
94
+ creditsUnlimited,
95
+ creditsBalance,
96
+ promoCampaignId,
97
+ promoMessage
98
+ };
99
+ }
5
100
  function extractOutputText(payload) {
6
101
  if (!payload || typeof payload !== "object") {
7
102
  return "";
@@ -43,12 +138,92 @@ function parseSseEvents(body) {
43
138
  }
44
139
  return events;
45
140
  }
46
- function extractCodexText(body) {
141
+ function pushArtifactCandidate(items, dedupe, candidate) {
142
+ const signature = `${candidate.source}:${candidate.path}:${candidate.key}:${candidate.kind}:${candidate.value}`;
143
+ if (dedupe.has(signature)) {
144
+ return;
145
+ }
146
+ dedupe.add(signature);
147
+ items.push(candidate);
148
+ }
149
+ function collectArtifactCandidates(value, source, path = [], items = [], dedupe = /* @__PURE__ */ new Set()) {
150
+ if (typeof value === "string") {
151
+ const key = path[path.length - 1] ?? "";
152
+ const joinedPath = path.join(".");
153
+ const trimmed = value.trim();
154
+ if (!trimmed) {
155
+ return items;
156
+ }
157
+ if (/^https?:\/\//i.test(trimmed)) {
158
+ pushArtifactCandidate(items, dedupe, {
159
+ source,
160
+ path: joinedPath,
161
+ key,
162
+ kind: "url",
163
+ value: trimmed
164
+ });
165
+ return items;
166
+ }
167
+ if (REFERENCE_KEY_RE.test(key) || REFERENCE_VALUE_RE.test(trimmed)) {
168
+ pushArtifactCandidate(items, dedupe, {
169
+ source,
170
+ path: joinedPath,
171
+ key,
172
+ kind: "reference",
173
+ value: trimmed
174
+ });
175
+ }
176
+ return items;
177
+ }
178
+ if (Array.isArray(value)) {
179
+ value.forEach((item, index) => {
180
+ collectArtifactCandidates(item, source, [...path, String(index)], items, dedupe);
181
+ });
182
+ return items;
183
+ }
184
+ if (!value || typeof value !== "object") {
185
+ return items;
186
+ }
187
+ for (const [key, nested] of Object.entries(value)) {
188
+ const nextPath = [...path, key];
189
+ if (typeof nested === "string" && (URL_KEY_RE.test(key) || /^https?:\/\//i.test(nested))) {
190
+ collectArtifactCandidates(nested, source, nextPath, items, dedupe);
191
+ continue;
192
+ }
193
+ collectArtifactCandidates(nested, source, nextPath, items, dedupe);
194
+ }
195
+ return items;
196
+ }
197
+ function buildDefaultRequestBody(params) {
198
+ const body = {
199
+ model: params.model ?? DEFAULT_CODEX_MODEL,
200
+ store: false,
201
+ stream: true,
202
+ instructions: params.system ?? "",
203
+ text: { verbosity: "medium" },
204
+ include: ["reasoning.encrypted_content"],
205
+ tool_choice: "auto",
206
+ parallel_tool_calls: true
207
+ };
208
+ if (typeof params.prompt === "string" && params.prompt.trim()) {
209
+ body.input = [
210
+ {
211
+ role: "user",
212
+ content: [{ type: "input_text", text: params.prompt }]
213
+ }
214
+ ];
215
+ }
216
+ return body;
217
+ }
218
+ function extractCodexText(body, requestBody) {
47
219
  const events = parseSseEvents(body);
48
220
  let responsePayload;
49
221
  let accumulated = "";
50
222
  for (const event of events) {
51
- if (event.type === "response.completed" || event.type === "response.done" || event.type === "response.incomplete") {
223
+ if (typeof event.response !== "undefined") {
224
+ responsePayload = event.response;
225
+ }
226
+ if (event.type === "response.completed" || event.type === "response.done" || event.type === "response.incomplete" || event.type === "response.failed") {
52
227
  responsePayload = event.response;
53
228
  }
54
229
  if (typeof event.delta === "string" && event.delta) {
@@ -56,12 +231,39 @@ function extractCodexText(body) {
56
231
  }
57
232
  }
58
233
  const completedText = extractOutputText(responsePayload);
234
+ const artifacts = [
235
+ ...collectArtifactCandidates(responsePayload, "response"),
236
+ ...collectArtifactCandidates(events, "event")
237
+ ];
59
238
  if (completedText) {
60
- return { text: completedText, raw: responsePayload ?? events };
239
+ return {
240
+ text: completedText,
241
+ raw: {
242
+ request: requestBody,
243
+ response: responsePayload ?? null,
244
+ events
245
+ },
246
+ artifacts
247
+ };
61
248
  }
62
- return { text: accumulated.trim(), raw: responsePayload ?? events };
249
+ return {
250
+ text: accumulated.trim(),
251
+ raw: {
252
+ request: requestBody,
253
+ response: responsePayload ?? null,
254
+ events
255
+ },
256
+ artifacts
257
+ };
63
258
  }
64
259
  async function askOpenAICodex(params) {
260
+ const requestBody = {
261
+ ...buildDefaultRequestBody(params),
262
+ ...params.bodyOverride ?? {}
263
+ };
264
+ if (typeof requestBody.input === "undefined") {
265
+ throw new Error("Codex \u8BF7\u6C42\u7F3A\u5C11 input\u3002\u8BF7\u63D0\u4F9B prompt \u6216\u5728\u5B9E\u9A8C\u8BF7\u6C42\u4F53\u91CC\u663E\u5F0F\u4F20\u5165 input\u3002");
266
+ }
65
267
  const response = await requestText({
66
268
  method: "POST",
67
269
  url: CODEX_RESPONSES_URL,
@@ -74,29 +276,20 @@ async function askOpenAICodex(params) {
74
276
  Originator: "pi",
75
277
  "User-Agent": "pi (bun demo)"
76
278
  },
77
- body: JSON.stringify({
78
- model: params.model ?? DEFAULT_CODEX_MODEL,
79
- store: false,
80
- stream: true,
81
- instructions: params.system ?? "",
82
- input: [
83
- {
84
- role: "user",
85
- content: [{ type: "input_text", text: params.prompt }]
86
- }
87
- ],
88
- text: { verbosity: "medium" },
89
- include: ["reasoning.encrypted_content"],
90
- tool_choice: "auto",
91
- parallel_tool_calls: true
92
- })
279
+ body: JSON.stringify(requestBody)
93
280
  });
281
+ const quota = extractCodexQuotaSnapshot(response.headers, response.requestId);
94
282
  if (response.status < 200 || response.status >= 300) {
95
- throw new Error(`\u8C03\u7528 Responses API \u5931\u8D25: HTTP ${response.status} via ${response.transport} ${response.body}`);
283
+ const error = new Error(`\u8C03\u7528 Responses API \u5931\u8D25: HTTP ${response.status} via ${response.transport} ${response.body}`);
284
+ error.quota = quota;
285
+ throw error;
96
286
  }
97
- return extractCodexText(response.body);
287
+ return {
288
+ ...extractCodexText(response.body, requestBody),
289
+ quota
290
+ };
98
291
  }
99
292
  export {
100
- askOpenAICodex
293
+ askOpenAICodex,
294
+ extractCodexQuotaSnapshot
101
295
  };
102
- //# sourceMappingURL=chat.js.map
@@ -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,57 +2,166 @@
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,
9
13
  refreshOpenAICodexToken
10
14
  } from "../providers/openai-codex/oauth.js";
15
+ import { askOpenAICodex } from "../providers/openai-codex/chat.js";
11
16
  class AuthService {
12
17
  constructor(configService) {
13
18
  this.configService = configService;
14
19
  }
20
+ maskSecret(value) {
21
+ if (value.length <= 12) {
22
+ return value;
23
+ }
24
+ return `${value.slice(0, 8)}...${value.slice(-6)}`;
25
+ }
26
+ toManagedProfile(profile) {
27
+ return {
28
+ ...profile,
29
+ mode: "oauth_account"
30
+ };
31
+ }
32
+ toProfileSummary(profile, activeProfileId) {
33
+ return {
34
+ provider: profile.provider,
35
+ profileId: profile.profileId,
36
+ accountId: profile.accountId,
37
+ email: profile.email,
38
+ quota: profile.quota,
39
+ expiresAt: profile.expires,
40
+ accessTokenPreview: this.maskSecret(profile.access),
41
+ refreshTokenPreview: this.maskSecret(profile.refresh),
42
+ isActive: profile.profileId === activeProfileId
43
+ };
44
+ }
15
45
  async login(provider) {
16
46
  if (provider !== "openai-codex") {
17
47
  throw new Error(`\u6682\u4E0D\u652F\u6301 provider: ${provider}`);
18
48
  }
19
49
  const profile = await loginOpenAICodex();
20
50
  await saveProfile(profile);
21
- return {
22
- ...profile,
23
- mode: "oauth_account"
24
- };
51
+ return this.toManagedProfile(profile);
25
52
  }
26
53
  async getActiveProfile(provider = "openai-codex") {
27
54
  const profile = await getActiveProfile();
28
55
  if (!profile || profile.provider !== provider) {
29
56
  return null;
30
57
  }
31
- return {
32
- ...profile,
33
- mode: "oauth_account"
34
- };
58
+ return this.toManagedProfile(profile);
59
+ }
60
+ async listProfiles(provider = "openai-codex") {
61
+ const [profiles, activeProfile] = await Promise.all([
62
+ listProfiles(),
63
+ this.getActiveProfile(provider)
64
+ ]);
65
+ const activeProfileId = activeProfile?.profileId;
66
+ return profiles.filter((profile) => profile.provider === provider).sort((left, right) => {
67
+ if (left.profileId === activeProfileId) {
68
+ return -1;
69
+ }
70
+ if (right.profileId === activeProfileId) {
71
+ return 1;
72
+ }
73
+ return right.expires - left.expires;
74
+ }).map((profile) => this.toProfileSummary(profile, activeProfileId));
75
+ }
76
+ async activateProfile(profileId, provider = "openai-codex") {
77
+ const profiles = await listProfiles();
78
+ const target = profiles.find((profile) => profile.profileId === profileId && profile.provider === provider);
79
+ if (!target) {
80
+ throw new Error(`\u6CA1\u6709\u627E\u5230\u53EF\u5207\u6362\u7684\u8D26\u53F7: ${profileId}`);
81
+ }
82
+ const activated = await setActiveProfile(profileId);
83
+ if (!activated) {
84
+ throw new Error(`\u5207\u6362\u8D26\u53F7\u5931\u8D25: ${profileId}`);
85
+ }
86
+ return this.toManagedProfile(activated);
87
+ }
88
+ async removeProfile(profileId, provider = "openai-codex") {
89
+ const profiles = await listProfiles();
90
+ const target = profiles.find((profile) => profile.profileId === profileId && profile.provider === provider);
91
+ if (!target) {
92
+ throw new Error(`\u6CA1\u6709\u627E\u5230\u8981\u5220\u9664\u7684\u8D26\u53F7: ${profileId}`);
93
+ }
94
+ await removeProfile(profileId);
35
95
  }
36
96
  async requireUsableProfile(provider = "openai-codex") {
37
97
  const profile = await this.getActiveProfile(provider);
38
98
  if (!profile) {
39
- throw new Error(`\u8FD8\u6CA1\u6709\u767B\u5F55 ${provider}\u3002\u5148\u8FD0\u884C bun src/cli.js login`);
99
+ throw new Error(`\u8FD8\u6CA1\u6709\u767B\u5F55 ${provider}\u3002\u5148\u8FD0\u884C azt login`);
40
100
  }
41
101
  if (Date.now() < profile.expires) {
42
102
  return profile;
43
103
  }
44
104
  const refreshed = await refreshOpenAICodexToken(profile);
45
105
  await saveProfile(refreshed);
46
- return {
47
- ...refreshed,
48
- mode: "oauth_account"
49
- };
106
+ return this.toManagedProfile(refreshed);
50
107
  }
51
108
  async logoutAll() {
52
109
  await clearStore();
53
110
  }
111
+ async syncActiveProfileQuota(provider = "openai-codex", options) {
112
+ let profile;
113
+ try {
114
+ profile = await this.requireUsableProfile(provider);
115
+ } catch (error) {
116
+ if (options?.suppressErrors) {
117
+ return;
118
+ }
119
+ throw error;
120
+ }
121
+ const model = await this.configService.getDefaultModel(provider);
122
+ try {
123
+ const result = await askOpenAICodex({
124
+ profile,
125
+ model,
126
+ system: "Reply with OK only.",
127
+ prompt: "ping",
128
+ bodyOverride: {
129
+ text: { verbosity: "low" }
130
+ }
131
+ });
132
+ await this.updateProfileQuota(profile.profileId, result.quota, provider);
133
+ } catch (error) {
134
+ const quota = error.quota;
135
+ await this.updateProfileQuota(profile.profileId, quota, provider);
136
+ if (!options?.suppressErrors) {
137
+ throw error;
138
+ }
139
+ console.warn("[auth] sync active profile quota failed", {
140
+ provider,
141
+ profileId: profile.profileId,
142
+ error: error instanceof Error ? error.message : String(error)
143
+ });
144
+ }
145
+ }
146
+ async updateProfileQuota(profileId, quota, provider = "openai-codex") {
147
+ if (!quota) {
148
+ return;
149
+ }
150
+ await updateProfile(profileId, (profile) => {
151
+ if (profile.provider !== provider) {
152
+ return profile;
153
+ }
154
+ return {
155
+ ...profile,
156
+ quota
157
+ };
158
+ });
159
+ }
54
160
  async getStatus() {
55
- const profile = await this.getActiveProfile();
161
+ const [profile, profiles] = await Promise.all([
162
+ this.getActiveProfile(),
163
+ this.listProfiles()
164
+ ]);
56
165
  const defaultModel = await this.configService.getDefaultModel();
57
166
  const server = await this.configService.getServerConfig();
58
167
  return {
@@ -62,6 +171,7 @@ class AuthService {
62
171
  defaultModel,
63
172
  loggedIn: Boolean(profile),
64
173
  expiresAt: profile?.expires,
174
+ profileCount: profiles.length,
65
175
  serverHost: server.host,
66
176
  serverPort: server.port
67
177
  };
@@ -70,4 +180,3 @@ class AuthService {
70
180
  export {
71
181
  AuthService
72
182
  };
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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { DEFAULT_CODEX_MODEL, isSupportedCodexModel } from "../models/openai-codex-models.js";
2
+ import { getPreferredCodexModel, hasCodexModel } from "../models/openai-codex-models.js";
3
3
  import {
4
4
  createDefaultSettings,
5
5
  loadSettings,
@@ -23,14 +23,14 @@ class ConfigService {
23
23
  if (provider !== "openai-codex") {
24
24
  throw new Error(`\u6682\u4E0D\u652F\u6301 provider: ${provider}`);
25
25
  }
26
- return isSupportedCodexModel(settings.defaultModel) ? settings.defaultModel : DEFAULT_CODEX_MODEL;
26
+ return await hasCodexModel(settings.defaultModel) ? settings.defaultModel : getPreferredCodexModel();
27
27
  }
28
28
  async setDefaultModel(model, provider = "openai-codex") {
29
29
  if (provider !== "openai-codex") {
30
30
  throw new Error(`\u6682\u4E0D\u652F\u6301 provider: ${provider}`);
31
31
  }
32
- if (!isSupportedCodexModel(model)) {
33
- throw new Error(`\u5F53\u524D demo \u672A\u5185\u7F6E\u6A21\u578B: ${model}`);
32
+ if (!await hasCodexModel(model)) {
33
+ throw new Error(`\u5F53\u524D\u7F51\u5173\u672A\u627E\u5230\u53EF\u7528\u6A21\u578B: ${model}`);
34
34
  }
35
35
  const settings = await this.getSettings();
36
36
  const next = {
@@ -66,4 +66,3 @@ class ConfigService {
66
66
  export {
67
67
  ConfigService
68
68
  };
69
- //# sourceMappingURL=config-service.js.map