ai-zero-token 2.0.5 → 2.0.6

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 (36) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +15 -12
  3. package/README.zh-CN.md +15 -12
  4. package/admin-ui/dist/assets/StatCard-7TEzqn2i.js +1 -0
  5. package/admin-ui/dist/assets/accounts-bCDKXGg9.js +4 -0
  6. package/admin-ui/dist/assets/{docs-Dh0aFha_.js → docs--eK_2fzC.js} +1 -1
  7. package/admin-ui/dist/assets/{image-bed-C1M7-0q1.js → image-bed-7wBZ1GhS.js} +1 -1
  8. package/admin-ui/dist/assets/index-C22_3Mxq.css +1 -0
  9. package/admin-ui/dist/assets/index-CdFYy5j6.js +10 -0
  10. package/admin-ui/dist/assets/{launch-pB7YlWFI.js → launch-BiD1Khtg.js} +1 -1
  11. package/admin-ui/dist/assets/{logs-B7McijSi.js → logs-BdoKDqh2.js} +1 -1
  12. package/admin-ui/dist/assets/{network-detect-Bx3XmXPk.js → network-detect-BvKns5nQ.js} +1 -1
  13. package/admin-ui/dist/assets/overview-wm6M45fu.js +1 -0
  14. package/admin-ui/dist/assets/settings-DOOu7Kd8.js +5 -0
  15. package/admin-ui/dist/assets/{tester-BG-up8qP.js → tester-NrARmlis.js} +1 -1
  16. package/admin-ui/dist/assets/usage-CdWRVMDV.js +1 -0
  17. package/admin-ui/dist/index.html +2 -2
  18. package/dist/core/context.js +3 -0
  19. package/dist/core/providers/http-client.js +21 -2
  20. package/dist/core/providers/openai-codex/chat.js +2 -1
  21. package/dist/core/providers/openai-codex/chatgpt-web-image.js +1404 -0
  22. package/dist/core/services/auth-service.js +51 -4
  23. package/dist/core/services/config-service.js +9 -0
  24. package/dist/core/services/image-service.js +31 -1
  25. package/dist/core/services/usage-service.js +349 -0
  26. package/dist/core/store/codex-auth-store.js +149 -15
  27. package/dist/core/store/settings-store.js +8 -2
  28. package/dist/core/store/state-paths.js +17 -1
  29. package/dist/server/app.js +848 -50
  30. package/docs/API_USAGE.md +33 -3
  31. package/package.json +1 -1
  32. package/admin-ui/dist/assets/accounts-ABMyXo4H.js +0 -4
  33. package/admin-ui/dist/assets/index--rNjdmzf.js +0 -10
  34. package/admin-ui/dist/assets/index-DjtN30PC.css +0 -1
  35. package/admin-ui/dist/assets/overview-CV0H2Nsq.js +0 -1
  36. package/admin-ui/dist/assets/settings-ynCIdUvZ.js +0 -7
@@ -1,17 +1,25 @@
1
1
  #!/usr/bin/env node
2
+ import { execFile } from "node:child_process";
2
3
  import fs from "node:fs/promises";
3
4
  import os from "node:os";
4
5
  import path from "node:path";
6
+ import { promisify } from "node:util";
7
+ const execFileAsync = promisify(execFile);
5
8
  function getCodexHomeDir() {
6
9
  return process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
7
10
  }
8
- const DEFAULT_CODEX_PROVIDER_ID = "ai-zero-token";
11
+ const OPENAI_CODEX_PROVIDER_ID = "openai";
12
+ const LEGACY_CODEX_PROVIDER_ID = "ai-zero-token";
13
+ const DEFAULT_CODEX_PROVIDER_ID = OPENAI_CODEX_PROVIDER_ID;
9
14
  function getCodexAuthPath() {
10
15
  return path.join(getCodexHomeDir(), "auth.json");
11
16
  }
12
17
  function getCodexConfigPath() {
13
18
  return path.join(getCodexHomeDir(), "config.toml");
14
19
  }
20
+ function getCodexStateDbPath() {
21
+ return path.join(getCodexHomeDir(), "state_5.sqlite");
22
+ }
15
23
  function createBackupSuffix() {
16
24
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
17
25
  }
@@ -26,6 +34,65 @@ function validateProviderId(providerId) {
26
34
  throw new Error("Codex providerId \u53EA\u80FD\u5305\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u77ED\u6A2A\u7EBF\u3002");
27
35
  }
28
36
  }
37
+ async function fileExists(targetPath) {
38
+ try {
39
+ await fs.access(targetPath);
40
+ return true;
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+ function sqliteQuote(value) {
46
+ return `'${value.replace(/'/g, "''")}'`;
47
+ }
48
+ async function runSqlite(dbPath, sql) {
49
+ const { stdout } = await execFileAsync("sqlite3", [dbPath, sql], {
50
+ timeout: 15e3,
51
+ maxBuffer: 1024 * 1024
52
+ });
53
+ return stdout.trim();
54
+ }
55
+ async function migrateLegacyCodexHistoryProvider() {
56
+ const dbPath = getCodexStateDbPath();
57
+ if (!await fileExists(dbPath)) {
58
+ return {
59
+ path: dbPath,
60
+ migratedCount: 0,
61
+ skipped: true
62
+ };
63
+ }
64
+ try {
65
+ const countRaw = await runSqlite(
66
+ dbPath,
67
+ `select count(*) from threads where model_provider=${sqliteQuote(LEGACY_CODEX_PROVIDER_ID)};`
68
+ );
69
+ const migratedCount = Number.parseInt(countRaw, 10);
70
+ if (!Number.isFinite(migratedCount) || migratedCount <= 0) {
71
+ return {
72
+ path: dbPath,
73
+ migratedCount: 0,
74
+ skipped: true
75
+ };
76
+ }
77
+ const backupPath = `${dbPath}.azt-backup-${createBackupSuffix()}`;
78
+ await runSqlite(dbPath, `.backup ${sqliteQuote(backupPath)}`);
79
+ await runSqlite(
80
+ dbPath,
81
+ `update threads set model_provider=${sqliteQuote(OPENAI_CODEX_PROVIDER_ID)} where model_provider=${sqliteQuote(LEGACY_CODEX_PROVIDER_ID)};`
82
+ );
83
+ return {
84
+ path: dbPath,
85
+ backupPath,
86
+ migratedCount
87
+ };
88
+ } catch (error) {
89
+ return {
90
+ path: dbPath,
91
+ migratedCount: 0,
92
+ error: error instanceof Error ? error.message : String(error)
93
+ };
94
+ }
95
+ }
29
96
  function normalizeCodexProviderBaseUrl(value) {
30
97
  let normalized = value.trim();
31
98
  if (!normalized) {
@@ -79,17 +146,21 @@ function findFirstTableLine(lines) {
79
146
  const index = lines.findIndex((line) => /^\s*\[/.test(line));
80
147
  return index === -1 ? lines.length : index;
81
148
  }
82
- function parseRootModelProvider(raw) {
149
+ function parseRootString(raw, key) {
83
150
  const lines = raw.split(/\r?\n/);
84
151
  const firstTableLine = findFirstTableLine(lines);
152
+ const keyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*(.+)$`);
85
153
  for (let index = 0; index < firstTableLine; index += 1) {
86
- const match = /^\s*model_provider\s*=\s*(.+)$/.exec(lines[index] ?? "");
154
+ const match = keyPattern.exec(lines[index] ?? "");
87
155
  if (match) {
88
156
  return parseTomlStringValue(match[1] ?? "");
89
157
  }
90
158
  }
91
159
  return void 0;
92
160
  }
161
+ function parseRootModelProvider(raw) {
162
+ return parseRootString(raw, "model_provider");
163
+ }
93
164
  function parseGatewayProviderTable(raw, providerId) {
94
165
  const lines = raw.split(/\r?\n/);
95
166
  const tablePattern = new RegExp(`^\\s*\\[\\s*model_providers\\.${escapeRegExp(providerId)}\\s*\\]\\s*$`);
@@ -106,16 +177,17 @@ function parseGatewayProviderTable(raw, providerId) {
106
177
  }
107
178
  return { exists: true, baseUrl };
108
179
  }
109
- function upsertRootModelProvider(raw, providerId) {
180
+ function upsertRootString(raw, key, value) {
110
181
  if (!raw.trim()) {
111
- return `model_provider = ${formatTomlString(providerId)}
182
+ return `${key} = ${formatTomlString(value)}
112
183
  `;
113
184
  }
114
185
  const lines = raw.split(/\r?\n/);
115
186
  const firstTableLine = findFirstTableLine(lines);
116
- const nextLine = `model_provider = ${formatTomlString(providerId)}`;
187
+ const nextLine = `${key} = ${formatTomlString(value)}`;
188
+ const keyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`);
117
189
  for (let index = 0; index < firstTableLine; index += 1) {
118
- if (/^\s*model_provider\s*=/.test(lines[index])) {
190
+ if (keyPattern.test(lines[index])) {
119
191
  lines[index] = nextLine;
120
192
  return lines.join("\n");
121
193
  }
@@ -123,6 +195,9 @@ function upsertRootModelProvider(raw, providerId) {
123
195
  lines.splice(firstTableLine, 0, nextLine, "");
124
196
  return lines.join("\n");
125
197
  }
198
+ function upsertRootModelProvider(raw, providerId) {
199
+ return upsertRootString(raw, "model_provider", providerId);
200
+ }
126
201
  function buildGatewayProviderBlock(providerId, baseUrl) {
127
202
  return [
128
203
  "# AI Zero Token managed Codex provider",
@@ -152,14 +227,27 @@ function upsertGatewayProviderTable(raw, providerId, baseUrl) {
152
227
  return lines.join("\n").replace(/\s+$/g, "\n");
153
228
  }
154
229
  function applyGatewayProviderConfig(raw, providerId, baseUrl) {
155
- return upsertGatewayProviderTable(upsertRootModelProvider(raw, providerId), providerId, baseUrl);
230
+ const sanitizedRaw = removeOpenAIGatewayConfig(raw).raw;
231
+ return upsertGatewayProviderTable(upsertRootModelProvider(sanitizedRaw, providerId), providerId, baseUrl);
156
232
  }
157
- function removeRootModelProvider(raw, providerId) {
233
+ function applyOpenAIGatewayConfig(raw, baseUrl) {
234
+ const withoutLegacyProvider = removeGatewayProviderConfig(raw, LEGACY_CODEX_PROVIDER_ID).raw;
235
+ return upsertRootString(
236
+ upsertRootModelProvider(withoutLegacyProvider, OPENAI_CODEX_PROVIDER_ID),
237
+ "openai_base_url",
238
+ baseUrl
239
+ );
240
+ }
241
+ function removeRootString(raw, key, expectedValue) {
158
242
  const lines = raw.split(/\r?\n/);
159
243
  const firstTableLine = findFirstTableLine(lines);
244
+ const keyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*(.+)$`);
160
245
  for (let index = 0; index < firstTableLine; index += 1) {
161
- const match = /^\s*model_provider\s*=\s*(.+)$/.exec(lines[index] ?? "");
162
- if (!match || parseTomlStringValue(match[1] ?? "") !== providerId) {
246
+ const match = keyPattern.exec(lines[index] ?? "");
247
+ if (!match) {
248
+ continue;
249
+ }
250
+ if (typeof expectedValue === "string" && parseTomlStringValue(match[1] ?? "") !== expectedValue) {
163
251
  continue;
164
252
  }
165
253
  lines.splice(index, 1);
@@ -170,6 +258,9 @@ function removeRootModelProvider(raw, providerId) {
170
258
  }
171
259
  return { raw, removed: false };
172
260
  }
261
+ function removeRootModelProvider(raw, providerId) {
262
+ return removeRootString(raw, "model_provider", providerId);
263
+ }
173
264
  function removeGatewayProviderTable(raw, providerId) {
174
265
  const lines = raw.split(/\r?\n/);
175
266
  const tablePattern = new RegExp(`^\\s*\\[\\s*model_providers\\.${escapeRegExp(providerId)}\\s*\\]\\s*$`);
@@ -196,6 +287,15 @@ function removeGatewayProviderConfig(raw, providerId) {
196
287
  removed: rootRemoved.removed || tableRemoved.removed
197
288
  };
198
289
  }
290
+ function removeOpenAIGatewayConfig(raw) {
291
+ const openAIBaseRemoved = removeRootString(raw, "openai_base_url");
292
+ const openAIProviderRemoved = removeRootModelProvider(openAIBaseRemoved.raw, OPENAI_CODEX_PROVIDER_ID);
293
+ const legacyRemoved = removeGatewayProviderConfig(openAIProviderRemoved.raw, LEGACY_CODEX_PROVIDER_ID);
294
+ return {
295
+ raw: legacyRemoved.raw,
296
+ removed: openAIBaseRemoved.removed || openAIProviderRemoved.removed || legacyRemoved.removed
297
+ };
298
+ }
199
299
  function isRecord(value) {
200
300
  return typeof value === "object" && value !== null && !Array.isArray(value);
201
301
  }
@@ -209,7 +309,8 @@ async function readCodexAuth() {
209
309
  }
210
310
  }
211
311
  async function getCodexGatewayProviderStatus(params) {
212
- const providerId = params?.providerId?.trim() || DEFAULT_CODEX_PROVIDER_ID;
312
+ const requestedProviderId = params?.providerId?.trim();
313
+ const providerId = requestedProviderId || DEFAULT_CODEX_PROVIDER_ID;
213
314
  validateProviderId(providerId);
214
315
  const configPath = getCodexConfigPath();
215
316
  let raw = "";
@@ -224,6 +325,30 @@ async function getCodexGatewayProviderStatus(params) {
224
325
  };
225
326
  }
226
327
  const modelProvider = parseRootModelProvider(raw);
328
+ if (providerId === OPENAI_CODEX_PROVIDER_ID) {
329
+ const openAIBaseUrl = parseRootString(raw, "openai_base_url");
330
+ if (openAIBaseUrl && (!modelProvider || modelProvider === OPENAI_CODEX_PROVIDER_ID)) {
331
+ return {
332
+ path: configPath,
333
+ providerId: OPENAI_CODEX_PROVIDER_ID,
334
+ exists: true,
335
+ active: !modelProvider || modelProvider === OPENAI_CODEX_PROVIDER_ID,
336
+ baseUrl: openAIBaseUrl,
337
+ modelProvider
338
+ };
339
+ }
340
+ const legacyTable = parseGatewayProviderTable(raw, LEGACY_CODEX_PROVIDER_ID);
341
+ if (!requestedProviderId && legacyTable.exists) {
342
+ return {
343
+ path: configPath,
344
+ providerId: LEGACY_CODEX_PROVIDER_ID,
345
+ exists: true,
346
+ active: modelProvider === LEGACY_CODEX_PROVIDER_ID,
347
+ baseUrl: legacyTable.baseUrl,
348
+ modelProvider
349
+ };
350
+ }
351
+ }
227
352
  const table = parseGatewayProviderTable(raw, providerId);
228
353
  return {
229
354
  path: configPath,
@@ -321,7 +446,8 @@ async function applyGatewayToCodexProviderConfig(params) {
321
446
  raw = "";
322
447
  backupPath = void 0;
323
448
  }
324
- const next = applyGatewayProviderConfig(raw, providerId, baseUrl);
449
+ const useOpenAIProvider = providerId === OPENAI_CODEX_PROVIDER_ID;
450
+ const next = useOpenAIProvider ? applyOpenAIGatewayConfig(raw, baseUrl) : applyGatewayProviderConfig(raw, providerId, baseUrl);
325
451
  const tmpPath = `${configPath}.tmp-${process.pid}`;
326
452
  await fs.writeFile(tmpPath, next, {
327
453
  encoding: "utf8",
@@ -333,7 +459,8 @@ async function applyGatewayToCodexProviderConfig(params) {
333
459
  path: configPath,
334
460
  backupPath,
335
461
  providerId,
336
- baseUrl
462
+ baseUrl,
463
+ historyMigration: useOpenAIProvider ? await migrateLegacyCodexHistoryProvider() : void 0
337
464
  };
338
465
  }
339
466
  async function removeGatewayFromCodexProviderConfig(params) {
@@ -350,7 +477,14 @@ async function removeGatewayFromCodexProviderConfig(params) {
350
477
  removed: false
351
478
  };
352
479
  }
353
- const next = removeGatewayProviderConfig(raw, providerId);
480
+ const sanitized = removeOpenAIGatewayConfig(raw);
481
+ const next = providerId === OPENAI_CODEX_PROVIDER_ID || providerId === LEGACY_CODEX_PROVIDER_ID ? sanitized : (() => {
482
+ const providerRemoved = removeGatewayProviderConfig(sanitized.raw, providerId);
483
+ return {
484
+ raw: providerRemoved.raw,
485
+ removed: sanitized.removed || providerRemoved.removed
486
+ };
487
+ })();
354
488
  if (!next.removed) {
355
489
  return {
356
490
  path: configPath,
@@ -21,7 +21,10 @@ function createDefaultSettings() {
21
21
  excludedProfileIds: []
22
22
  },
23
23
  runtime: {
24
- quotaSyncConcurrency: 16
24
+ quotaSyncConcurrency: 3
25
+ },
26
+ image: {
27
+ freeAccountWebGenerationEnabled: false
25
28
  },
26
29
  server: {
27
30
  host: "0.0.0.0",
@@ -47,6 +50,9 @@ function normalizeSettings(parsed) {
47
50
  runtime: {
48
51
  quotaSyncConcurrency: normalizeQuotaSyncConcurrency(parsed.runtime?.quotaSyncConcurrency, defaults.runtime.quotaSyncConcurrency)
49
52
  },
53
+ image: {
54
+ freeAccountWebGenerationEnabled: parsed.image?.freeAccountWebGenerationEnabled ?? defaults.image.freeAccountWebGenerationEnabled
55
+ },
50
56
  server: {
51
57
  host: parsed.server?.host ?? defaults.server.host,
52
58
  port: parsed.server?.port ?? defaults.server.port
@@ -63,7 +69,7 @@ async function loadSettings() {
63
69
  return createDefaultSettings();
64
70
  }
65
71
  }
66
- function normalizeQuotaSyncConcurrency(value, fallback = 16) {
72
+ function normalizeQuotaSyncConcurrency(value, fallback = 3) {
67
73
  const parsed = typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value, 10) : fallback;
68
74
  if (!Number.isFinite(parsed)) {
69
75
  return fallback;
@@ -34,6 +34,18 @@ function getStorePath() {
34
34
  function getSettingsPath() {
35
35
  return path.join(stateDir, "settings.json");
36
36
  }
37
+ function getUsageDir() {
38
+ return path.join(stateDir, "usage");
39
+ }
40
+ function getUsageEventsDir() {
41
+ return path.join(getUsageDir(), "events");
42
+ }
43
+ function getUsageDailyPath() {
44
+ return path.join(getUsageDir(), "daily.json");
45
+ }
46
+ function getUsageLifetimePath() {
47
+ return path.join(getUsageDir(), "lifetime.json");
48
+ }
37
49
  async function ensureStateMigrated() {
38
50
  if (!migrationPromise) {
39
51
  migrationPromise = (async () => {
@@ -50,5 +62,9 @@ export {
50
62
  ensureStateMigrated,
51
63
  getSettingsPath,
52
64
  getStateDir,
53
- getStorePath
65
+ getStorePath,
66
+ getUsageDailyPath,
67
+ getUsageDir,
68
+ getUsageEventsDir,
69
+ getUsageLifetimePath
54
70
  };