ai-zero-token 2.0.4 → 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.
- package/CHANGELOG.md +26 -0
- package/README.md +23 -5
- package/README.zh-CN.md +24 -6
- package/admin-ui/dist/assets/StatCard-7TEzqn2i.js +1 -0
- package/admin-ui/dist/assets/accounts-bCDKXGg9.js +4 -0
- package/admin-ui/dist/assets/{docs-oNIugCIL.js → docs--eK_2fzC.js} +1 -1
- package/admin-ui/dist/assets/{image-bed-CQtIhjg_.js → image-bed-7wBZ1GhS.js} +1 -1
- package/admin-ui/dist/assets/index-C22_3Mxq.css +1 -0
- package/admin-ui/dist/assets/index-CdFYy5j6.js +10 -0
- package/admin-ui/dist/assets/{launch-B-2Zdz9m.js → launch-BiD1Khtg.js} +1 -1
- package/admin-ui/dist/assets/{logs-JFuSf56b.js → logs-BdoKDqh2.js} +1 -1
- package/admin-ui/dist/assets/{network-detect-SfvK6uhx.js → network-detect-BvKns5nQ.js} +1 -1
- package/admin-ui/dist/assets/overview-wm6M45fu.js +1 -0
- package/admin-ui/dist/assets/settings-DOOu7Kd8.js +5 -0
- package/admin-ui/dist/assets/{tester-ocpF053C.js → tester-NrARmlis.js} +1 -1
- package/admin-ui/dist/assets/usage-CdWRVMDV.js +1 -0
- package/admin-ui/dist/index.html +2 -2
- package/dist/core/context.js +3 -0
- package/dist/core/providers/http-client.js +247 -3
- package/dist/core/providers/openai-codex/chat.js +84 -23
- package/dist/core/providers/openai-codex/chatgpt-web-image.js +1404 -0
- package/dist/core/services/auth-service.js +64 -8
- package/dist/core/services/config-service.js +24 -5
- package/dist/core/services/image-service.js +31 -1
- package/dist/core/services/usage-service.js +349 -0
- package/dist/core/store/codex-auth-store.js +429 -4
- package/dist/core/store/settings-store.js +62 -26
- package/dist/core/store/state-paths.js +17 -1
- package/dist/server/app.js +1278 -119
- package/docs/API_USAGE.md +48 -1
- package/docs/DESKTOP_RELEASE.md +12 -1
- package/package.json +1 -1
- package/admin-ui/dist/assets/accounts-CTjk9c4F.js +0 -4
- package/admin-ui/dist/assets/index-By4r-wy3.css +0 -1
- package/admin-ui/dist/assets/index-rgcJgVAu.js +0 -10
- package/admin-ui/dist/assets/overview-X_WodIqE.js +0 -1
- package/admin-ui/dist/assets/settings-0eXUAvcm.js +0 -1
|
@@ -1,16 +1,301 @@
|
|
|
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
|
}
|
|
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;
|
|
8
14
|
function getCodexAuthPath() {
|
|
9
15
|
return path.join(getCodexHomeDir(), "auth.json");
|
|
10
16
|
}
|
|
17
|
+
function getCodexConfigPath() {
|
|
18
|
+
return path.join(getCodexHomeDir(), "config.toml");
|
|
19
|
+
}
|
|
20
|
+
function getCodexStateDbPath() {
|
|
21
|
+
return path.join(getCodexHomeDir(), "state_5.sqlite");
|
|
22
|
+
}
|
|
11
23
|
function createBackupSuffix() {
|
|
12
24
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
13
25
|
}
|
|
26
|
+
function escapeRegExp(value) {
|
|
27
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
28
|
+
}
|
|
29
|
+
function formatTomlString(value) {
|
|
30
|
+
return JSON.stringify(value);
|
|
31
|
+
}
|
|
32
|
+
function validateProviderId(providerId) {
|
|
33
|
+
if (!/^[A-Za-z0-9_-]+$/.test(providerId)) {
|
|
34
|
+
throw new Error("Codex providerId \u53EA\u80FD\u5305\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u77ED\u6A2A\u7EBF\u3002");
|
|
35
|
+
}
|
|
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
|
+
}
|
|
96
|
+
function normalizeCodexProviderBaseUrl(value) {
|
|
97
|
+
let normalized = value.trim();
|
|
98
|
+
if (!normalized) {
|
|
99
|
+
throw new Error("Codex provider base_url \u4E0D\u80FD\u4E3A\u7A7A\u3002");
|
|
100
|
+
}
|
|
101
|
+
if (!/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(normalized)) {
|
|
102
|
+
normalized = `http://${normalized}`;
|
|
103
|
+
}
|
|
104
|
+
let url;
|
|
105
|
+
try {
|
|
106
|
+
url = new URL(normalized);
|
|
107
|
+
} catch {
|
|
108
|
+
throw new Error("Codex provider base_url \u683C\u5F0F\u9519\u8BEF\uFF0C\u8BF7\u586B\u5199\u5B8C\u6574\u7684 http(s) URL\u3002");
|
|
109
|
+
}
|
|
110
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
111
|
+
throw new Error("Codex provider base_url \u5FC5\u987B\u662F http(s) URL\u3002");
|
|
112
|
+
}
|
|
113
|
+
url.hash = "";
|
|
114
|
+
url.search = "";
|
|
115
|
+
const path2 = url.pathname.replace(/\/+$/g, "");
|
|
116
|
+
if (!path2 || path2 === "/") {
|
|
117
|
+
url.pathname = "/codex/v1";
|
|
118
|
+
} else if (path2 === "/v1") {
|
|
119
|
+
url.pathname = "/codex/v1";
|
|
120
|
+
} else if (path2.endsWith("/codex")) {
|
|
121
|
+
url.pathname = `${path2}/v1`;
|
|
122
|
+
} else {
|
|
123
|
+
url.pathname = path2;
|
|
124
|
+
}
|
|
125
|
+
return url.toString().replace(/\/+$/g, "");
|
|
126
|
+
}
|
|
127
|
+
function parseTomlStringValue(value) {
|
|
128
|
+
const trimmed = value.trim().replace(/\s+#.*$/g, "");
|
|
129
|
+
if (!trimmed) {
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
132
|
+
if (trimmed.startsWith('"')) {
|
|
133
|
+
try {
|
|
134
|
+
const parsed = JSON.parse(trimmed);
|
|
135
|
+
return typeof parsed === "string" ? parsed : void 0;
|
|
136
|
+
} catch {
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'") && trimmed.length >= 2) {
|
|
141
|
+
return trimmed.slice(1, -1);
|
|
142
|
+
}
|
|
143
|
+
return trimmed;
|
|
144
|
+
}
|
|
145
|
+
function findFirstTableLine(lines) {
|
|
146
|
+
const index = lines.findIndex((line) => /^\s*\[/.test(line));
|
|
147
|
+
return index === -1 ? lines.length : index;
|
|
148
|
+
}
|
|
149
|
+
function parseRootString(raw, key) {
|
|
150
|
+
const lines = raw.split(/\r?\n/);
|
|
151
|
+
const firstTableLine = findFirstTableLine(lines);
|
|
152
|
+
const keyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*(.+)$`);
|
|
153
|
+
for (let index = 0; index < firstTableLine; index += 1) {
|
|
154
|
+
const match = keyPattern.exec(lines[index] ?? "");
|
|
155
|
+
if (match) {
|
|
156
|
+
return parseTomlStringValue(match[1] ?? "");
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
function parseRootModelProvider(raw) {
|
|
162
|
+
return parseRootString(raw, "model_provider");
|
|
163
|
+
}
|
|
164
|
+
function parseGatewayProviderTable(raw, providerId) {
|
|
165
|
+
const lines = raw.split(/\r?\n/);
|
|
166
|
+
const tablePattern = new RegExp(`^\\s*\\[\\s*model_providers\\.${escapeRegExp(providerId)}\\s*\\]\\s*$`);
|
|
167
|
+
const start = lines.findIndex((line) => tablePattern.test(line));
|
|
168
|
+
if (start === -1) {
|
|
169
|
+
return { exists: false };
|
|
170
|
+
}
|
|
171
|
+
let baseUrl;
|
|
172
|
+
for (let index = start + 1; index < lines.length && !/^\s*\[/.test(lines[index] ?? ""); index += 1) {
|
|
173
|
+
const match = /^\s*base_url\s*=\s*(.+)$/.exec(lines[index] ?? "");
|
|
174
|
+
if (match) {
|
|
175
|
+
baseUrl = parseTomlStringValue(match[1] ?? "");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return { exists: true, baseUrl };
|
|
179
|
+
}
|
|
180
|
+
function upsertRootString(raw, key, value) {
|
|
181
|
+
if (!raw.trim()) {
|
|
182
|
+
return `${key} = ${formatTomlString(value)}
|
|
183
|
+
`;
|
|
184
|
+
}
|
|
185
|
+
const lines = raw.split(/\r?\n/);
|
|
186
|
+
const firstTableLine = findFirstTableLine(lines);
|
|
187
|
+
const nextLine = `${key} = ${formatTomlString(value)}`;
|
|
188
|
+
const keyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`);
|
|
189
|
+
for (let index = 0; index < firstTableLine; index += 1) {
|
|
190
|
+
if (keyPattern.test(lines[index])) {
|
|
191
|
+
lines[index] = nextLine;
|
|
192
|
+
return lines.join("\n");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
lines.splice(firstTableLine, 0, nextLine, "");
|
|
196
|
+
return lines.join("\n");
|
|
197
|
+
}
|
|
198
|
+
function upsertRootModelProvider(raw, providerId) {
|
|
199
|
+
return upsertRootString(raw, "model_provider", providerId);
|
|
200
|
+
}
|
|
201
|
+
function buildGatewayProviderBlock(providerId, baseUrl) {
|
|
202
|
+
return [
|
|
203
|
+
"# AI Zero Token managed Codex provider",
|
|
204
|
+
`[model_providers.${providerId}]`,
|
|
205
|
+
'name = "AI Zero Token"',
|
|
206
|
+
`base_url = ${formatTomlString(baseUrl)}`,
|
|
207
|
+
'wire_api = "responses"',
|
|
208
|
+
"supports_websockets = false"
|
|
209
|
+
];
|
|
210
|
+
}
|
|
211
|
+
function upsertGatewayProviderTable(raw, providerId, baseUrl) {
|
|
212
|
+
const lines = raw.split(/\r?\n/);
|
|
213
|
+
const tablePattern = new RegExp(`^\\s*\\[\\s*model_providers\\.${escapeRegExp(providerId)}\\s*\\]\\s*$`);
|
|
214
|
+
const start = lines.findIndex((line) => tablePattern.test(line));
|
|
215
|
+
const block = buildGatewayProviderBlock(providerId, baseUrl);
|
|
216
|
+
if (start === -1) {
|
|
217
|
+
const trimmed = raw.replace(/\s+$/g, "");
|
|
218
|
+
return `${trimmed}${trimmed ? "\n\n" : ""}${block.join("\n")}
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
let end = start + 1;
|
|
222
|
+
while (end < lines.length && !/^\s*\[/.test(lines[end])) {
|
|
223
|
+
end += 1;
|
|
224
|
+
}
|
|
225
|
+
const replaceStart = start > 0 && /AI Zero Token managed Codex provider/.test(lines[start - 1]) ? start - 1 : start;
|
|
226
|
+
lines.splice(replaceStart, end - replaceStart, ...block, "");
|
|
227
|
+
return lines.join("\n").replace(/\s+$/g, "\n");
|
|
228
|
+
}
|
|
229
|
+
function applyGatewayProviderConfig(raw, providerId, baseUrl) {
|
|
230
|
+
const sanitizedRaw = removeOpenAIGatewayConfig(raw).raw;
|
|
231
|
+
return upsertGatewayProviderTable(upsertRootModelProvider(sanitizedRaw, providerId), providerId, baseUrl);
|
|
232
|
+
}
|
|
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) {
|
|
242
|
+
const lines = raw.split(/\r?\n/);
|
|
243
|
+
const firstTableLine = findFirstTableLine(lines);
|
|
244
|
+
const keyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*(.+)$`);
|
|
245
|
+
for (let index = 0; index < firstTableLine; index += 1) {
|
|
246
|
+
const match = keyPattern.exec(lines[index] ?? "");
|
|
247
|
+
if (!match) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (typeof expectedValue === "string" && parseTomlStringValue(match[1] ?? "") !== expectedValue) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
lines.splice(index, 1);
|
|
254
|
+
if (lines[index] === "" && (index === 0 || lines[index - 1] === "")) {
|
|
255
|
+
lines.splice(index, 1);
|
|
256
|
+
}
|
|
257
|
+
return { raw: lines.join("\n").replace(/^\s+\n/g, ""), removed: true };
|
|
258
|
+
}
|
|
259
|
+
return { raw, removed: false };
|
|
260
|
+
}
|
|
261
|
+
function removeRootModelProvider(raw, providerId) {
|
|
262
|
+
return removeRootString(raw, "model_provider", providerId);
|
|
263
|
+
}
|
|
264
|
+
function removeGatewayProviderTable(raw, providerId) {
|
|
265
|
+
const lines = raw.split(/\r?\n/);
|
|
266
|
+
const tablePattern = new RegExp(`^\\s*\\[\\s*model_providers\\.${escapeRegExp(providerId)}\\s*\\]\\s*$`);
|
|
267
|
+
const start = lines.findIndex((line) => tablePattern.test(line));
|
|
268
|
+
if (start === -1) {
|
|
269
|
+
return { raw, removed: false };
|
|
270
|
+
}
|
|
271
|
+
let end = start + 1;
|
|
272
|
+
while (end < lines.length && !/^\s*\[/.test(lines[end])) {
|
|
273
|
+
end += 1;
|
|
274
|
+
}
|
|
275
|
+
const replaceStart = start > 0 && /AI Zero Token managed Codex provider/.test(lines[start - 1]) ? start - 1 : start;
|
|
276
|
+
lines.splice(replaceStart, end - replaceStart);
|
|
277
|
+
return {
|
|
278
|
+
raw: lines.join("\n").replace(/\n{3,}/g, "\n\n").replace(/\s+$/g, "\n"),
|
|
279
|
+
removed: true
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function removeGatewayProviderConfig(raw, providerId) {
|
|
283
|
+
const rootRemoved = removeRootModelProvider(raw, providerId);
|
|
284
|
+
const tableRemoved = removeGatewayProviderTable(rootRemoved.raw, providerId);
|
|
285
|
+
return {
|
|
286
|
+
raw: tableRemoved.raw,
|
|
287
|
+
removed: rootRemoved.removed || tableRemoved.removed
|
|
288
|
+
};
|
|
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
|
+
}
|
|
14
299
|
function isRecord(value) {
|
|
15
300
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
301
|
}
|
|
@@ -23,14 +308,69 @@ async function readCodexAuth() {
|
|
|
23
308
|
return null;
|
|
24
309
|
}
|
|
25
310
|
}
|
|
311
|
+
async function getCodexGatewayProviderStatus(params) {
|
|
312
|
+
const requestedProviderId = params?.providerId?.trim();
|
|
313
|
+
const providerId = requestedProviderId || DEFAULT_CODEX_PROVIDER_ID;
|
|
314
|
+
validateProviderId(providerId);
|
|
315
|
+
const configPath = getCodexConfigPath();
|
|
316
|
+
let raw = "";
|
|
317
|
+
try {
|
|
318
|
+
raw = await fs.readFile(configPath, "utf8");
|
|
319
|
+
} catch {
|
|
320
|
+
return {
|
|
321
|
+
path: configPath,
|
|
322
|
+
providerId,
|
|
323
|
+
exists: false,
|
|
324
|
+
active: false
|
|
325
|
+
};
|
|
326
|
+
}
|
|
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
|
+
}
|
|
352
|
+
const table = parseGatewayProviderTable(raw, providerId);
|
|
353
|
+
return {
|
|
354
|
+
path: configPath,
|
|
355
|
+
providerId,
|
|
356
|
+
exists: table.exists,
|
|
357
|
+
active: modelProvider === providerId && table.exists,
|
|
358
|
+
baseUrl: table.baseUrl,
|
|
359
|
+
modelProvider
|
|
360
|
+
};
|
|
361
|
+
}
|
|
26
362
|
async function getCodexAuthStatus() {
|
|
27
363
|
const authPath = getCodexAuthPath();
|
|
28
|
-
const auth = await
|
|
364
|
+
const [auth, gatewayProvider] = await Promise.all([
|
|
365
|
+
readCodexAuth(),
|
|
366
|
+
getCodexGatewayProviderStatus()
|
|
367
|
+
]);
|
|
29
368
|
if (!auth) {
|
|
30
369
|
return {
|
|
31
370
|
path: authPath,
|
|
32
371
|
exists: false,
|
|
33
|
-
hasIdToken: false
|
|
372
|
+
hasIdToken: false,
|
|
373
|
+
gatewayProvider
|
|
34
374
|
};
|
|
35
375
|
}
|
|
36
376
|
const tokens = isRecord(auth.tokens) ? auth.tokens : {};
|
|
@@ -39,7 +379,8 @@ async function getCodexAuthStatus() {
|
|
|
39
379
|
exists: true,
|
|
40
380
|
accountId: typeof tokens.account_id === "string" ? tokens.account_id : void 0,
|
|
41
381
|
hasIdToken: typeof tokens.id_token === "string" && tokens.id_token.length > 0,
|
|
42
|
-
lastRefresh: typeof auth.last_refresh === "string" ? auth.last_refresh : void 0
|
|
382
|
+
lastRefresh: typeof auth.last_refresh === "string" ? auth.last_refresh : void 0,
|
|
383
|
+
gatewayProvider
|
|
43
384
|
};
|
|
44
385
|
}
|
|
45
386
|
async function applyProfileToCodexAuth(profile) {
|
|
@@ -82,13 +423,97 @@ async function applyProfileToCodexAuth(profile) {
|
|
|
82
423
|
accountId: profile.accountId,
|
|
83
424
|
hasIdToken: true,
|
|
84
425
|
lastRefresh: authFile.last_refresh,
|
|
426
|
+
gatewayProvider: await getCodexGatewayProviderStatus(),
|
|
85
427
|
backupPath,
|
|
86
428
|
appliedProfileId: profile.profileId,
|
|
87
429
|
appliedEmail: profile.email
|
|
88
430
|
};
|
|
89
431
|
}
|
|
432
|
+
async function applyGatewayToCodexProviderConfig(params) {
|
|
433
|
+
const providerId = params.providerId?.trim() || DEFAULT_CODEX_PROVIDER_ID;
|
|
434
|
+
validateProviderId(providerId);
|
|
435
|
+
const baseUrl = normalizeCodexProviderBaseUrl(params.baseUrl);
|
|
436
|
+
const configPath = getCodexConfigPath();
|
|
437
|
+
const codexHomeDir = path.dirname(configPath);
|
|
438
|
+
await fs.mkdir(codexHomeDir, { recursive: true });
|
|
439
|
+
let raw = "";
|
|
440
|
+
let backupPath;
|
|
441
|
+
try {
|
|
442
|
+
raw = await fs.readFile(configPath, "utf8");
|
|
443
|
+
backupPath = `${configPath}.azt-backup-${createBackupSuffix()}`;
|
|
444
|
+
await fs.copyFile(configPath, backupPath);
|
|
445
|
+
} catch {
|
|
446
|
+
raw = "";
|
|
447
|
+
backupPath = void 0;
|
|
448
|
+
}
|
|
449
|
+
const useOpenAIProvider = providerId === OPENAI_CODEX_PROVIDER_ID;
|
|
450
|
+
const next = useOpenAIProvider ? applyOpenAIGatewayConfig(raw, baseUrl) : applyGatewayProviderConfig(raw, providerId, baseUrl);
|
|
451
|
+
const tmpPath = `${configPath}.tmp-${process.pid}`;
|
|
452
|
+
await fs.writeFile(tmpPath, next, {
|
|
453
|
+
encoding: "utf8",
|
|
454
|
+
mode: 384
|
|
455
|
+
});
|
|
456
|
+
await fs.rename(tmpPath, configPath);
|
|
457
|
+
await fs.chmod(configPath, 384);
|
|
458
|
+
return {
|
|
459
|
+
path: configPath,
|
|
460
|
+
backupPath,
|
|
461
|
+
providerId,
|
|
462
|
+
baseUrl,
|
|
463
|
+
historyMigration: useOpenAIProvider ? await migrateLegacyCodexHistoryProvider() : void 0
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
async function removeGatewayFromCodexProviderConfig(params) {
|
|
467
|
+
const providerId = params?.providerId?.trim() || DEFAULT_CODEX_PROVIDER_ID;
|
|
468
|
+
validateProviderId(providerId);
|
|
469
|
+
const configPath = getCodexConfigPath();
|
|
470
|
+
let raw = "";
|
|
471
|
+
try {
|
|
472
|
+
raw = await fs.readFile(configPath, "utf8");
|
|
473
|
+
} catch {
|
|
474
|
+
return {
|
|
475
|
+
path: configPath,
|
|
476
|
+
providerId,
|
|
477
|
+
removed: false
|
|
478
|
+
};
|
|
479
|
+
}
|
|
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
|
+
})();
|
|
488
|
+
if (!next.removed) {
|
|
489
|
+
return {
|
|
490
|
+
path: configPath,
|
|
491
|
+
providerId,
|
|
492
|
+
removed: false
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
const backupPath = `${configPath}.azt-backup-${createBackupSuffix()}`;
|
|
496
|
+
await fs.copyFile(configPath, backupPath);
|
|
497
|
+
const tmpPath = `${configPath}.tmp-${process.pid}`;
|
|
498
|
+
await fs.writeFile(tmpPath, next.raw.trim() ? next.raw : "", {
|
|
499
|
+
encoding: "utf8",
|
|
500
|
+
mode: 384
|
|
501
|
+
});
|
|
502
|
+
await fs.rename(tmpPath, configPath);
|
|
503
|
+
await fs.chmod(configPath, 384);
|
|
504
|
+
return {
|
|
505
|
+
path: configPath,
|
|
506
|
+
backupPath,
|
|
507
|
+
providerId,
|
|
508
|
+
removed: true
|
|
509
|
+
};
|
|
510
|
+
}
|
|
90
511
|
export {
|
|
512
|
+
applyGatewayToCodexProviderConfig,
|
|
91
513
|
applyProfileToCodexAuth,
|
|
92
514
|
getCodexAuthPath,
|
|
93
|
-
getCodexAuthStatus
|
|
515
|
+
getCodexAuthStatus,
|
|
516
|
+
getCodexConfigPath,
|
|
517
|
+
getCodexGatewayProviderStatus,
|
|
518
|
+
removeGatewayFromCodexProviderConfig
|
|
94
519
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import fs from "node:fs/promises";
|
|
3
4
|
import {
|
|
4
5
|
ensureStateMigrated,
|
|
@@ -16,10 +17,14 @@ function createDefaultSettings() {
|
|
|
16
17
|
noProxy: "localhost,127.0.0.1,::1"
|
|
17
18
|
},
|
|
18
19
|
autoSwitch: {
|
|
19
|
-
enabled: false
|
|
20
|
+
enabled: false,
|
|
21
|
+
excludedProfileIds: []
|
|
20
22
|
},
|
|
21
23
|
runtime: {
|
|
22
|
-
quotaSyncConcurrency:
|
|
24
|
+
quotaSyncConcurrency: 3
|
|
25
|
+
},
|
|
26
|
+
image: {
|
|
27
|
+
freeAccountWebGenerationEnabled: false
|
|
23
28
|
},
|
|
24
29
|
server: {
|
|
25
30
|
host: "0.0.0.0",
|
|
@@ -27,48 +32,79 @@ function createDefaultSettings() {
|
|
|
27
32
|
}
|
|
28
33
|
};
|
|
29
34
|
}
|
|
35
|
+
function normalizeSettings(parsed) {
|
|
36
|
+
const defaults = createDefaultSettings();
|
|
37
|
+
return {
|
|
38
|
+
version: 1,
|
|
39
|
+
defaultProvider: parsed.defaultProvider ?? defaults.defaultProvider,
|
|
40
|
+
defaultModel: parsed.defaultModel ?? defaults.defaultModel,
|
|
41
|
+
networkProxy: {
|
|
42
|
+
enabled: parsed.networkProxy?.enabled ?? defaults.networkProxy.enabled,
|
|
43
|
+
url: parsed.networkProxy?.url ?? defaults.networkProxy.url,
|
|
44
|
+
noProxy: parsed.networkProxy?.noProxy ?? defaults.networkProxy.noProxy
|
|
45
|
+
},
|
|
46
|
+
autoSwitch: {
|
|
47
|
+
enabled: parsed.autoSwitch?.enabled ?? defaults.autoSwitch.enabled,
|
|
48
|
+
excludedProfileIds: normalizeStringList(parsed.autoSwitch?.excludedProfileIds)
|
|
49
|
+
},
|
|
50
|
+
runtime: {
|
|
51
|
+
quotaSyncConcurrency: normalizeQuotaSyncConcurrency(parsed.runtime?.quotaSyncConcurrency, defaults.runtime.quotaSyncConcurrency)
|
|
52
|
+
},
|
|
53
|
+
image: {
|
|
54
|
+
freeAccountWebGenerationEnabled: parsed.image?.freeAccountWebGenerationEnabled ?? defaults.image.freeAccountWebGenerationEnabled
|
|
55
|
+
},
|
|
56
|
+
server: {
|
|
57
|
+
host: parsed.server?.host ?? defaults.server.host,
|
|
58
|
+
port: parsed.server?.port ?? defaults.server.port
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
30
62
|
async function loadSettings() {
|
|
31
63
|
try {
|
|
32
64
|
await ensureStateMigrated();
|
|
33
65
|
const raw = await fs.readFile(getSettingsPath(), "utf8");
|
|
34
66
|
const parsed = JSON.parse(raw);
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
version: 1,
|
|
38
|
-
defaultProvider: parsed.defaultProvider ?? defaults.defaultProvider,
|
|
39
|
-
defaultModel: parsed.defaultModel ?? defaults.defaultModel,
|
|
40
|
-
networkProxy: {
|
|
41
|
-
enabled: parsed.networkProxy?.enabled ?? defaults.networkProxy.enabled,
|
|
42
|
-
url: parsed.networkProxy?.url ?? defaults.networkProxy.url,
|
|
43
|
-
noProxy: parsed.networkProxy?.noProxy ?? defaults.networkProxy.noProxy
|
|
44
|
-
},
|
|
45
|
-
autoSwitch: {
|
|
46
|
-
enabled: parsed.autoSwitch?.enabled ?? defaults.autoSwitch.enabled
|
|
47
|
-
},
|
|
48
|
-
runtime: {
|
|
49
|
-
quotaSyncConcurrency: normalizeQuotaSyncConcurrency(parsed.runtime?.quotaSyncConcurrency, defaults.runtime.quotaSyncConcurrency)
|
|
50
|
-
},
|
|
51
|
-
server: {
|
|
52
|
-
host: parsed.server?.host ?? defaults.server.host,
|
|
53
|
-
port: parsed.server?.port ?? defaults.server.port
|
|
54
|
-
}
|
|
55
|
-
};
|
|
67
|
+
return normalizeSettings(parsed);
|
|
56
68
|
} catch {
|
|
57
69
|
return createDefaultSettings();
|
|
58
70
|
}
|
|
59
71
|
}
|
|
60
|
-
function normalizeQuotaSyncConcurrency(value, fallback =
|
|
72
|
+
function normalizeQuotaSyncConcurrency(value, fallback = 3) {
|
|
61
73
|
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value, 10) : fallback;
|
|
62
74
|
if (!Number.isFinite(parsed)) {
|
|
63
75
|
return fallback;
|
|
64
76
|
}
|
|
65
77
|
return Math.min(32, Math.max(1, Math.trunc(parsed)));
|
|
66
78
|
}
|
|
67
|
-
|
|
79
|
+
function normalizeStringList(value) {
|
|
80
|
+
if (!Array.isArray(value)) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
return Array.from(
|
|
84
|
+
new Set(
|
|
85
|
+
value.map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean)
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
let settingsSaveQueue = Promise.resolve();
|
|
90
|
+
async function writeSettingsAtomic(settings) {
|
|
68
91
|
await ensureStateMigrated();
|
|
69
92
|
await fs.mkdir(getStateDir(), { recursive: true });
|
|
70
|
-
|
|
93
|
+
const settingsPath = getSettingsPath();
|
|
94
|
+
const tempPath = `${settingsPath}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
|
|
95
|
+
try {
|
|
96
|
+
await fs.writeFile(tempPath, `${JSON.stringify(settings, null, 2)}
|
|
71
97
|
`, "utf8");
|
|
98
|
+
await fs.rename(tempPath, settingsPath);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
await fs.rm(tempPath, { force: true }).catch(() => void 0);
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function saveSettings(settings) {
|
|
105
|
+
const nextSave = settingsSaveQueue.then(() => writeSettingsAtomic(settings), () => writeSettingsAtomic(settings));
|
|
106
|
+
settingsSaveQueue = nextSave.catch(() => void 0);
|
|
107
|
+
await nextSave;
|
|
72
108
|
}
|
|
73
109
|
export {
|
|
74
110
|
createDefaultSettings,
|
|
@@ -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
|
};
|