ai-zero-token 2.0.6 → 2.0.7
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 +9 -1
- package/README.md +5 -5
- package/README.zh-CN.md +5 -5
- package/admin-ui/dist/assets/accounts-D3tsDc3k.js +4 -0
- package/admin-ui/dist/assets/{docs--eK_2fzC.js → docs-BO-aSEzh.js} +1 -1
- package/admin-ui/dist/assets/{image-bed-7wBZ1GhS.js → image-bed-Dql7Vqd9.js} +1 -1
- package/admin-ui/dist/assets/{index-CdFYy5j6.js → index-CCiBaGwU.js} +3 -3
- package/admin-ui/dist/assets/{launch-BiD1Khtg.js → launch-DXLo-NIM.js} +1 -1
- package/admin-ui/dist/assets/{logs-BdoKDqh2.js → logs-Cwn8-rDu.js} +1 -1
- package/admin-ui/dist/assets/{network-detect-BvKns5nQ.js → network-detect-vzWfL-Tz.js} +1 -1
- package/admin-ui/dist/assets/{overview-wm6M45fu.js → overview-B_yad8ge.js} +1 -1
- package/admin-ui/dist/assets/{profiles-DMOjJORP.js → profiles-C5SmQvju.js} +1 -1
- package/admin-ui/dist/assets/{settings-DOOu7Kd8.js → settings-BdRWcKJb.js} +1 -1
- package/admin-ui/dist/assets/{tester-NrARmlis.js → tester-BKoMSoCz.js} +1 -1
- package/admin-ui/dist/assets/{usage-CdWRVMDV.js → usage-B-qQxXzQ.js} +1 -1
- package/admin-ui/dist/index.html +2 -2
- package/dist/cli/commands/help.js +1 -1
- package/dist/cli/commands/models.js +3 -2
- package/dist/core/context.js +1 -1
- package/dist/core/models/openai-codex-models.js +106 -1
- package/dist/core/providers/http-client.js +142 -12
- package/dist/core/services/auth-service.js +104 -7
- package/dist/core/services/chat-service.js +16 -18
- package/dist/core/services/model-service.js +22 -8
- package/dist/server/app.js +179 -23
- package/dist/server/index.js +1 -1
- package/docs/API_USAGE.md +1 -1
- package/docs/DESKTOP_RELEASE.md +12 -1
- package/package.json +1 -1
- package/admin-ui/dist/assets/accounts-bCDKXGg9.js +0 -4
package/dist/server/app.js
CHANGED
|
@@ -15,13 +15,17 @@ import Fastify from "fastify";
|
|
|
15
15
|
import cors from "@fastify/cors";
|
|
16
16
|
import { z } from "zod";
|
|
17
17
|
import { createGatewayContext } from "../core/context.js";
|
|
18
|
-
import { requestText } from "../core/providers/http-client.js";
|
|
18
|
+
import { isTransientHttpError, requestText } from "../core/providers/http-client.js";
|
|
19
19
|
import { streamOpenAICodex } from "../core/providers/openai-codex/chat.js";
|
|
20
20
|
import { generateChatGPTWebImage } from "../core/providers/openai-codex/chatgpt-web-image.js";
|
|
21
21
|
const packageRoot = path.dirname(fileURLToPath(new URL("../../package.json", import.meta.url)));
|
|
22
22
|
const adminUiDistDir = path.join(packageRoot, "admin-ui", "dist");
|
|
23
23
|
const adminUiIndexPath = path.join(adminUiDistDir, "index.html");
|
|
24
|
+
const BYTES_PER_MIB = 1024 * 1024;
|
|
24
25
|
const MAX_GATEWAY_REQUEST_LOGS = 100;
|
|
26
|
+
const MAX_CODEX_RESPONSE_PROFILE_BINDINGS = 5e3;
|
|
27
|
+
const DEFAULT_ROUTE_BODY_LIMIT_BYTES = 128 * BYTES_PER_MIB;
|
|
28
|
+
const CODEX_COMPACT_BODY_LIMIT_BYTES = 256 * BYTES_PER_MIB;
|
|
25
29
|
const gunzipAsync = promisify(gunzip);
|
|
26
30
|
const inflateAsync = promisify(inflate);
|
|
27
31
|
const brotliDecompressAsync = promisify(brotliDecompress);
|
|
@@ -610,9 +614,23 @@ function summarizeResponsesRequest(data, endpoint = "/v1/responses") {
|
|
|
610
614
|
toolNamesTruncated: toolNames.length > 50,
|
|
611
615
|
toolChoice: typeof data.tool_choice === "undefined" ? "default" : typeof data.tool_choice,
|
|
612
616
|
parallelToolCalls: data.parallel_tool_calls,
|
|
613
|
-
hasReasoning: Boolean(data.reasoning)
|
|
617
|
+
hasReasoning: Boolean(data.reasoning),
|
|
618
|
+
hasPreviousResponseId: Boolean(getPreviousResponseId(data))
|
|
614
619
|
};
|
|
615
620
|
}
|
|
621
|
+
function getPreviousResponseId(data) {
|
|
622
|
+
const direct = data.previous_response_id;
|
|
623
|
+
if (typeof direct === "string" && direct.trim()) {
|
|
624
|
+
return direct.trim();
|
|
625
|
+
}
|
|
626
|
+
const experimental = data.experimental_codex?.body?.previous_response_id;
|
|
627
|
+
return typeof experimental === "string" && experimental.trim() ? experimental.trim() : void 0;
|
|
628
|
+
}
|
|
629
|
+
function removePreviousResponseId(body) {
|
|
630
|
+
const next = { ...body };
|
|
631
|
+
delete next.previous_response_id;
|
|
632
|
+
return next;
|
|
633
|
+
}
|
|
616
634
|
function createResponsesCodexBody(data) {
|
|
617
635
|
const experimentalBody = data.experimental_codex?.body ?? {};
|
|
618
636
|
const body = {
|
|
@@ -1139,18 +1157,35 @@ function getErrorStatusCode(error) {
|
|
|
1139
1157
|
}
|
|
1140
1158
|
return 500;
|
|
1141
1159
|
}
|
|
1142
|
-
function
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1160
|
+
function formatBytesAsMiB(bytes) {
|
|
1161
|
+
if (typeof bytes !== "number" || !Number.isFinite(bytes) || bytes <= 0) {
|
|
1162
|
+
return "\u672A\u77E5";
|
|
1163
|
+
}
|
|
1164
|
+
return `${Math.round(bytes / BYTES_PER_MIB * 10) / 10} MB`;
|
|
1146
1165
|
}
|
|
1147
1166
|
function createSseStreamStats() {
|
|
1148
1167
|
return {
|
|
1149
1168
|
buffer: "",
|
|
1150
1169
|
bytes: 0,
|
|
1151
|
-
completed: false
|
|
1170
|
+
completed: false,
|
|
1171
|
+
responseIds: /* @__PURE__ */ new Set(),
|
|
1172
|
+
tokenUsage: null
|
|
1152
1173
|
};
|
|
1153
1174
|
}
|
|
1175
|
+
function extractSseResponseId(value) {
|
|
1176
|
+
if (!isObjectRecord(value)) {
|
|
1177
|
+
return void 0;
|
|
1178
|
+
}
|
|
1179
|
+
const directId = value.id;
|
|
1180
|
+
if (typeof directId === "string" && directId.startsWith("resp_")) {
|
|
1181
|
+
return directId;
|
|
1182
|
+
}
|
|
1183
|
+
const response = value.response;
|
|
1184
|
+
if (isObjectRecord(response) && typeof response.id === "string" && response.id.startsWith("resp_")) {
|
|
1185
|
+
return response.id;
|
|
1186
|
+
}
|
|
1187
|
+
return void 0;
|
|
1188
|
+
}
|
|
1154
1189
|
function trackSseChunk(stats, chunk) {
|
|
1155
1190
|
const text = typeof chunk === "string" ? chunk : chunk instanceof Uint8Array ? Buffer.from(chunk).toString("utf8") : String(chunk);
|
|
1156
1191
|
stats.bytes += Buffer.byteLength(text);
|
|
@@ -1168,6 +1203,14 @@ function trackSseChunk(stats, chunk) {
|
|
|
1168
1203
|
if (typeof parsed.type === "string") {
|
|
1169
1204
|
eventType = parsed.type;
|
|
1170
1205
|
}
|
|
1206
|
+
const responseId = extractSseResponseId(parsed);
|
|
1207
|
+
if (responseId) {
|
|
1208
|
+
stats.responseIds.add(responseId);
|
|
1209
|
+
}
|
|
1210
|
+
const tokenUsage = extractTokenUsage(parsed);
|
|
1211
|
+
if (tokenUsage) {
|
|
1212
|
+
stats.tokenUsage = tokenUsage;
|
|
1213
|
+
}
|
|
1171
1214
|
} catch {
|
|
1172
1215
|
}
|
|
1173
1216
|
}
|
|
@@ -1184,9 +1227,11 @@ function trackSseChunk(stats, chunk) {
|
|
|
1184
1227
|
}
|
|
1185
1228
|
}
|
|
1186
1229
|
function createApp(params) {
|
|
1230
|
+
const defaultBodyLimit = params?.bodyLimit ?? DEFAULT_ROUTE_BODY_LIMIT_BYTES;
|
|
1231
|
+
const codexCompactBodyLimit = Math.max(defaultBodyLimit, CODEX_COMPACT_BODY_LIMIT_BYTES);
|
|
1187
1232
|
const app = Fastify({
|
|
1188
1233
|
logger: false,
|
|
1189
|
-
bodyLimit:
|
|
1234
|
+
bodyLimit: defaultBodyLimit
|
|
1190
1235
|
});
|
|
1191
1236
|
app.removeContentTypeParser("application/json");
|
|
1192
1237
|
app.addContentTypeParser(
|
|
@@ -1198,6 +1243,22 @@ function createApp(params) {
|
|
|
1198
1243
|
);
|
|
1199
1244
|
const ctx = createGatewayContext();
|
|
1200
1245
|
const gatewayRequestLogs = [];
|
|
1246
|
+
const codexResponseProfileBindings = /* @__PURE__ */ new Map();
|
|
1247
|
+
function rememberCodexResponseProfile(responseId, profile) {
|
|
1248
|
+
codexResponseProfileBindings.set(responseId, {
|
|
1249
|
+
profileId: profile.profileId,
|
|
1250
|
+
accountId: profile.accountId,
|
|
1251
|
+
seenAt: Date.now()
|
|
1252
|
+
});
|
|
1253
|
+
if (codexResponseProfileBindings.size <= MAX_CODEX_RESPONSE_PROFILE_BINDINGS) {
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
const overflow = codexResponseProfileBindings.size - MAX_CODEX_RESPONSE_PROFILE_BINDINGS;
|
|
1257
|
+
const oldest = Array.from(codexResponseProfileBindings.entries()).sort((left, right) => left[1].seenAt - right[1].seenAt).slice(0, overflow);
|
|
1258
|
+
for (const [key] of oldest) {
|
|
1259
|
+
codexResponseProfileBindings.delete(key);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1201
1262
|
function pushGatewayRequestLog(log) {
|
|
1202
1263
|
const entry = {
|
|
1203
1264
|
id: log.id ?? randomUUID(),
|
|
@@ -1246,18 +1307,22 @@ function createApp(params) {
|
|
|
1246
1307
|
app.setErrorHandler((error, request, reply) => {
|
|
1247
1308
|
const normalized = normalizeError(error);
|
|
1248
1309
|
const statusCode = getErrorStatusCode(normalized);
|
|
1310
|
+
const isBodyTooLarge = statusCode === 413;
|
|
1311
|
+
const message = isBodyTooLarge ? `\u8BF7\u6C42\u4F53\u8FC7\u5927\uFF0C\u5F53\u524D\u7F51\u5173\u9ED8\u8BA4\u4E0A\u9650 ${formatBytesAsMiB(defaultBodyLimit)}\uFF0CCodex compact \u4E0A\u9650 ${formatBytesAsMiB(codexCompactBodyLimit)}\u3002\u5982\u4ECD\u4E0D\u591F\uFF0C\u8BF7\u7528 AZT_BODY_LIMIT_MB \u8C03\u5927\u540E\u91CD\u542F\u7F51\u5173\u3002` : normalized.message;
|
|
1249
1312
|
console.error("[gateway:error]", {
|
|
1250
1313
|
method: request.method,
|
|
1251
1314
|
url: request.url,
|
|
1252
1315
|
statusCode,
|
|
1253
|
-
message
|
|
1316
|
+
message,
|
|
1317
|
+
code: normalized.code,
|
|
1318
|
+
upstreamRequestId: normalized.requestId,
|
|
1254
1319
|
stack: normalized.stack
|
|
1255
1320
|
});
|
|
1256
1321
|
reply.code(statusCode);
|
|
1257
1322
|
return {
|
|
1258
1323
|
error: {
|
|
1259
1324
|
type: "gateway_error",
|
|
1260
|
-
message
|
|
1325
|
+
message
|
|
1261
1326
|
}
|
|
1262
1327
|
};
|
|
1263
1328
|
});
|
|
@@ -1780,6 +1845,9 @@ function createApp(params) {
|
|
|
1780
1845
|
let retryCount = 0;
|
|
1781
1846
|
let failureRecorded = false;
|
|
1782
1847
|
let codexImageRoute = "none";
|
|
1848
|
+
const originalPreviousResponseId = getPreviousResponseId(data);
|
|
1849
|
+
let adventureFallbackUsed = false;
|
|
1850
|
+
let adventureFallbackReason;
|
|
1783
1851
|
reply.raw.on("close", () => {
|
|
1784
1852
|
if (!streamFinished) {
|
|
1785
1853
|
abortController.abort();
|
|
@@ -1789,7 +1857,42 @@ function createApp(params) {
|
|
|
1789
1857
|
const model = await ctx.modelService.resolveModel("openai-codex", data.model, {
|
|
1790
1858
|
allowUnknown: data.experimental_codex?.allow_unknown_model
|
|
1791
1859
|
});
|
|
1792
|
-
|
|
1860
|
+
let codexBody = createCodexPassthroughBody(data, model);
|
|
1861
|
+
let activePreviousResponseId = originalPreviousResponseId;
|
|
1862
|
+
let keepProfileSticky = Boolean(activePreviousResponseId);
|
|
1863
|
+
let stickyProfileId = activePreviousResponseId ? codexResponseProfileBindings.get(activePreviousResponseId)?.profileId : void 0;
|
|
1864
|
+
const useAdventureFallback = async (error, quota) => {
|
|
1865
|
+
if (!keepProfileSticky || abortController.signal.aborted) {
|
|
1866
|
+
return false;
|
|
1867
|
+
}
|
|
1868
|
+
const failedProfileId = profile?.profileId ?? stickyProfileId;
|
|
1869
|
+
if (failedProfileId) {
|
|
1870
|
+
await ctx.authService.recordProfileRequestFailure(failedProfileId, error, quota, "openai-codex", {
|
|
1871
|
+
skipAutoSwitch: true
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
codexBody = removePreviousResponseId(codexBody);
|
|
1875
|
+
activePreviousResponseId = void 0;
|
|
1876
|
+
keepProfileSticky = false;
|
|
1877
|
+
stickyProfileId = void 0;
|
|
1878
|
+
adventureFallbackUsed = true;
|
|
1879
|
+
adventureFallbackReason = error instanceof Error ? error.message : String(error);
|
|
1880
|
+
retryCount += 1;
|
|
1881
|
+
profile = null;
|
|
1882
|
+
failureRecorded = false;
|
|
1883
|
+
console.warn("[gateway:codex:stream] sticky continuation failed; dropping previous_response_id and retrying as new session", {
|
|
1884
|
+
requestId: request.id,
|
|
1885
|
+
model,
|
|
1886
|
+
retryCount,
|
|
1887
|
+
previousResponseId: "[present]",
|
|
1888
|
+
failedAccount: failedProfileId,
|
|
1889
|
+
errorCode: error.code,
|
|
1890
|
+
upstreamStatus: error.upstreamStatus,
|
|
1891
|
+
upstreamRequestId: error.requestId,
|
|
1892
|
+
message: adventureFallbackReason
|
|
1893
|
+
});
|
|
1894
|
+
return true;
|
|
1895
|
+
};
|
|
1793
1896
|
const imageRequest = upstreamEndpoint === "responses" ? extractCodexImageGenerationRequest(codexBody) : null;
|
|
1794
1897
|
if (imageRequest) {
|
|
1795
1898
|
codexImageRoute = "codex-tool";
|
|
@@ -1867,9 +1970,13 @@ function createApp(params) {
|
|
|
1867
1970
|
}
|
|
1868
1971
|
let upstream = null;
|
|
1869
1972
|
const maxProfileAttempts = 5;
|
|
1973
|
+
const maxTransientStreamRetries = 1;
|
|
1974
|
+
let transientStreamRetryCount = 0;
|
|
1870
1975
|
for (let attempt = 0; attempt < maxProfileAttempts; attempt += 1) {
|
|
1871
|
-
profile = await ctx.authService.requireUsableProfile("openai-codex");
|
|
1872
1976
|
try {
|
|
1977
|
+
profile = stickyProfileId ? await ctx.authService.requireUsableProfileById(stickyProfileId, "openai-codex") : await ctx.authService.requireUsableProfile("openai-codex", {
|
|
1978
|
+
skipAutoSwitch: keepProfileSticky
|
|
1979
|
+
});
|
|
1873
1980
|
upstream = await streamOpenAICodex({
|
|
1874
1981
|
profile,
|
|
1875
1982
|
model,
|
|
@@ -1881,9 +1988,31 @@ function createApp(params) {
|
|
|
1881
1988
|
break;
|
|
1882
1989
|
} catch (error) {
|
|
1883
1990
|
const quota = error.quota;
|
|
1884
|
-
|
|
1991
|
+
if (keepProfileSticky && attempt < maxProfileAttempts - 1 && await useAdventureFallback(error, quota)) {
|
|
1992
|
+
continue;
|
|
1993
|
+
}
|
|
1994
|
+
if (!keepProfileSticky && isTransientHttpError(error) && transientStreamRetryCount < maxTransientStreamRetries && attempt < maxProfileAttempts - 1 && !abortController.signal.aborted) {
|
|
1995
|
+
transientStreamRetryCount += 1;
|
|
1996
|
+
retryCount += 1;
|
|
1997
|
+
console.warn("[gateway:codex:stream] transient curl stream failure before headers; retrying request", {
|
|
1998
|
+
requestId: request.id,
|
|
1999
|
+
account: profileLogLabel(profile),
|
|
2000
|
+
model,
|
|
2001
|
+
retryCount,
|
|
2002
|
+
errorCode: error.code,
|
|
2003
|
+
upstreamRequestId: error.requestId,
|
|
2004
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2005
|
+
});
|
|
2006
|
+
continue;
|
|
2007
|
+
}
|
|
2008
|
+
if (!profile) {
|
|
2009
|
+
throw error;
|
|
2010
|
+
}
|
|
2011
|
+
const switchedProfile = await ctx.authService.recordProfileRequestFailure(profile.profileId, error, quota, "openai-codex", {
|
|
2012
|
+
skipAutoSwitch: keepProfileSticky
|
|
2013
|
+
});
|
|
1885
2014
|
failureRecorded = true;
|
|
1886
|
-
if (attempt < maxProfileAttempts - 1 &&
|
|
2015
|
+
if (!keepProfileSticky && attempt < maxProfileAttempts - 1 && ctx.authService.isRotationTrigger(error, quota) && switchedProfile && switchedProfile.profileId !== profile.profileId && !abortController.signal.aborted) {
|
|
1887
2016
|
retryCount += 1;
|
|
1888
2017
|
failureRecorded = false;
|
|
1889
2018
|
continue;
|
|
@@ -1894,13 +2023,18 @@ function createApp(params) {
|
|
|
1894
2023
|
if (!upstream || !profile) {
|
|
1895
2024
|
throw new Error("Codex stream \u672A\u80FD\u5EFA\u7ACB\u3002");
|
|
1896
2025
|
}
|
|
1897
|
-
await ctx.authService.recordProfileRequestSuccess(profile.profileId, upstream.quota, "openai-codex"
|
|
2026
|
+
await ctx.authService.recordProfileRequestSuccess(profile.profileId, upstream.quota, "openai-codex", {
|
|
2027
|
+
skipAutoSwitch: keepProfileSticky
|
|
2028
|
+
});
|
|
1898
2029
|
const headers = {
|
|
1899
2030
|
"Content-Type": upstream.headers["content-type"] ?? "text/event-stream; charset=utf-8",
|
|
1900
2031
|
"Cache-Control": "no-cache, no-transform",
|
|
1901
2032
|
Connection: "keep-alive",
|
|
1902
2033
|
"X-Accel-Buffering": "no"
|
|
1903
2034
|
};
|
|
2035
|
+
if (adventureFallbackUsed) {
|
|
2036
|
+
headers["X-AZT-Codex-Continuation-Mode"] = "adventure-fallback";
|
|
2037
|
+
}
|
|
1904
2038
|
for (const [key, value] of Object.entries(upstream.headers)) {
|
|
1905
2039
|
if (key.startsWith("x-codex-") || key === "x-request-id") {
|
|
1906
2040
|
headers[key] = value;
|
|
@@ -1918,6 +2052,9 @@ function createApp(params) {
|
|
|
1918
2052
|
}
|
|
1919
2053
|
streamFinished = true;
|
|
1920
2054
|
reply.raw.end();
|
|
2055
|
+
for (const responseId of streamStats.responseIds) {
|
|
2056
|
+
rememberCodexResponseProfile(responseId, profile);
|
|
2057
|
+
}
|
|
1921
2058
|
if (!streamStats.completed) {
|
|
1922
2059
|
console.warn("[gateway:codex:stream] upstream stream ended without response.completed", {
|
|
1923
2060
|
requestId: request.id,
|
|
@@ -1947,13 +2084,21 @@ function createApp(params) {
|
|
|
1947
2084
|
passthrough: true,
|
|
1948
2085
|
upstreamEndpoint,
|
|
1949
2086
|
retryCount,
|
|
2087
|
+
profileSticky: keepProfileSticky,
|
|
2088
|
+
previousResponseId: originalPreviousResponseId ? "[present]" : void 0,
|
|
2089
|
+
previousResponseDropped: adventureFallbackUsed,
|
|
2090
|
+
adventureFallbackReason: adventureFallbackUsed ? truncateForLog(adventureFallbackReason ?? "") : void 0,
|
|
2091
|
+
stickyProfileResolved: Boolean(stickyProfileId),
|
|
2092
|
+
responseIdsTracked: streamStats.responseIds.size,
|
|
1950
2093
|
completed: streamStats.completed,
|
|
1951
2094
|
terminalEvent: streamStats.terminalEvent,
|
|
1952
|
-
bytes: streamStats.bytes
|
|
2095
|
+
bytes: streamStats.bytes,
|
|
2096
|
+
usageCaptured: Boolean(streamStats.tokenUsage)
|
|
1953
2097
|
}
|
|
1954
2098
|
},
|
|
1955
2099
|
usage: {
|
|
1956
2100
|
profile,
|
|
2101
|
+
tokenUsage: streamStats.tokenUsage,
|
|
1957
2102
|
imageRoute: codexImageRoute
|
|
1958
2103
|
}
|
|
1959
2104
|
});
|
|
@@ -1961,7 +2106,9 @@ function createApp(params) {
|
|
|
1961
2106
|
} catch (error) {
|
|
1962
2107
|
const quota = error.quota;
|
|
1963
2108
|
if (profile && !failureRecorded) {
|
|
1964
|
-
await ctx.authService.recordProfileRequestFailure(profile.profileId, error, quota, "openai-codex"
|
|
2109
|
+
await ctx.authService.recordProfileRequestFailure(profile.profileId, error, quota, "openai-codex", {
|
|
2110
|
+
skipAutoSwitch: Boolean(originalPreviousResponseId) && !adventureFallbackUsed
|
|
2111
|
+
});
|
|
1965
2112
|
}
|
|
1966
2113
|
const normalized = normalizeError(error);
|
|
1967
2114
|
const statusCode = getErrorStatusCode(normalized);
|
|
@@ -1980,10 +2127,17 @@ function createApp(params) {
|
|
|
1980
2127
|
request: summarizeResponsesRequest(data, request.url),
|
|
1981
2128
|
response: {
|
|
1982
2129
|
upstreamEndpoint,
|
|
1983
|
-
retryCount
|
|
2130
|
+
retryCount,
|
|
2131
|
+
profileSticky: Boolean(originalPreviousResponseId) && !adventureFallbackUsed,
|
|
2132
|
+
previousResponseId: originalPreviousResponseId ? "[present]" : void 0,
|
|
2133
|
+
previousResponseDropped: adventureFallbackUsed,
|
|
2134
|
+
adventureFallbackReason: adventureFallbackUsed ? truncateForLog(adventureFallbackReason ?? "") : void 0,
|
|
2135
|
+
stickyProfileResolved: Boolean(originalPreviousResponseId && codexResponseProfileBindings.has(originalPreviousResponseId))
|
|
1984
2136
|
},
|
|
1985
2137
|
error: {
|
|
1986
2138
|
message: normalized.message,
|
|
2139
|
+
code: normalized.code,
|
|
2140
|
+
upstreamRequestId: normalized.requestId,
|
|
1987
2141
|
upstreamStatus: normalized.upstreamStatus,
|
|
1988
2142
|
upstreamErrorCode: normalized.upstreamErrorCode,
|
|
1989
2143
|
upstreamErrorMessage: normalized.upstreamErrorMessage
|
|
@@ -2034,7 +2188,7 @@ function createApp(params) {
|
|
|
2034
2188
|
}
|
|
2035
2189
|
return handleCodexResponsesPassthrough(request, reply, parsed.data, startedAt);
|
|
2036
2190
|
});
|
|
2037
|
-
app.post("/codex/v1/responses/compact", async (request, reply) => {
|
|
2191
|
+
app.post("/codex/v1/responses/compact", { bodyLimit: codexCompactBodyLimit }, async (request, reply) => {
|
|
2038
2192
|
const startedAt = performance.now();
|
|
2039
2193
|
const parsed = responsesBodySchema.safeParse(request.body);
|
|
2040
2194
|
if (!parsed.success) {
|
|
@@ -2198,7 +2352,7 @@ function createApp(params) {
|
|
|
2198
2352
|
});
|
|
2199
2353
|
throw error;
|
|
2200
2354
|
}
|
|
2201
|
-
const activeProfile = await ctx.authService.getActiveProfile();
|
|
2355
|
+
const activeProfile = result.profile ?? await ctx.authService.getActiveProfile();
|
|
2202
2356
|
pushGatewayRequestLog({
|
|
2203
2357
|
method: request.method,
|
|
2204
2358
|
endpoint: request.url,
|
|
@@ -2216,7 +2370,8 @@ function createApp(params) {
|
|
|
2216
2370
|
response: {
|
|
2217
2371
|
textPreview: truncateForLog(result.text),
|
|
2218
2372
|
textLength: result.text.length,
|
|
2219
|
-
artifactCount: result.artifacts.length
|
|
2373
|
+
artifactCount: result.artifacts.length,
|
|
2374
|
+
retryCount: result.retryCount ?? 0
|
|
2220
2375
|
}
|
|
2221
2376
|
},
|
|
2222
2377
|
usage: {
|
|
@@ -2333,7 +2488,7 @@ function createApp(params) {
|
|
|
2333
2488
|
});
|
|
2334
2489
|
throw error;
|
|
2335
2490
|
}
|
|
2336
|
-
const activeProfile = await ctx.authService.getActiveProfile();
|
|
2491
|
+
const activeProfile = result.profile ?? await ctx.authService.getActiveProfile();
|
|
2337
2492
|
pushGatewayRequestLog({
|
|
2338
2493
|
method: request.method,
|
|
2339
2494
|
endpoint: request.url,
|
|
@@ -2358,7 +2513,8 @@ function createApp(params) {
|
|
|
2358
2513
|
argumentsPreview: truncateForLog(toolCall.function.arguments)
|
|
2359
2514
|
})),
|
|
2360
2515
|
artifactCount: result.artifacts.length,
|
|
2361
|
-
stream: parsed.data.stream ?? false
|
|
2516
|
+
stream: parsed.data.stream ?? false,
|
|
2517
|
+
retryCount: result.retryCount ?? 0
|
|
2362
2518
|
}
|
|
2363
2519
|
},
|
|
2364
2520
|
usage: {
|
package/dist/server/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { ConfigService } from "../core/services/config-service.js";
|
|
3
3
|
import { createApp } from "./app.js";
|
|
4
|
-
const DEFAULT_BODY_LIMIT_MB =
|
|
4
|
+
const DEFAULT_BODY_LIMIT_MB = 128;
|
|
5
5
|
function resolveCorsOrigin() {
|
|
6
6
|
const raw = process.env.AZT_CORS_ORIGIN?.trim();
|
|
7
7
|
if (!raw || raw === "*") {
|
package/docs/API_USAGE.md
CHANGED
|
@@ -110,7 +110,7 @@ curl http://127.0.0.1:8787/_gateway/admin/usage
|
|
|
110
110
|
|
|
111
111
|
The summary includes today, current-process, lifetime, daily trend, account, model, endpoint, error, image-route, and source breakdowns. Token totals are counted only when the upstream response returns `usage`; requests without upstream usage are counted separately as requests with missing usage. Usage files keep metadata only and do not store prompts, messages, access tokens, or base64 image payloads.
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
Sync the Codex model list from the Codex backend into the local cache:
|
|
114
114
|
|
|
115
115
|
```bash
|
|
116
116
|
azt models --refresh
|
package/docs/DESKTOP_RELEASE.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
This project ships the desktop app with Electron. The desktop main process starts the existing local Fastify gateway and loads the React management UI served by that gateway.
|
|
4
4
|
|
|
5
|
+
## 2.0.6 Release Notes
|
|
6
|
+
|
|
7
|
+
Version `2.0.6` adds Free-account image routing controls and local usage/account statistics:
|
|
8
|
+
|
|
9
|
+
- Opt-in Free-account ChatGPT web image route for API image requests and Codex `image_generation` tool requests.
|
|
10
|
+
- Red Settings warning for Free-account image risk and limited quota.
|
|
11
|
+
- Persistent local usage statistics for today, current process, lifetime totals, daily trend, and account/model/endpoint/error/source breakdowns.
|
|
12
|
+
- Account-management statistics strip with clickable filters for availability, plan, active status, login/auth state, quota exhaustion, and auto-switch inclusion.
|
|
13
|
+
- Filtered-result bulk selection controls for batch account operations.
|
|
14
|
+
- Clearer usage labels that separate known upstream-returned token totals from requests that did not return usage metadata.
|
|
15
|
+
|
|
5
16
|
## 2.0.5 Release Notes
|
|
6
17
|
|
|
7
18
|
Version `2.0.5` adds Codex custom provider routing and finer account rotation controls:
|
|
@@ -114,7 +125,7 @@ AI Zero Token Setup {version}.exe
|
|
|
114
125
|
AI Zero Token-{version}-win.zip
|
|
115
126
|
```
|
|
116
127
|
|
|
117
|
-
For `2.0.
|
|
128
|
+
For `2.0.6`, replace `{version}` with `2.0.6`.
|
|
118
129
|
|
|
119
130
|
Artifact purpose:
|
|
120
131
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-zero-token",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
4
4
|
"description": "Local-first OpenAI-compatible AI CLI and gateway with Codex OAuth, multi-account management, and gpt-image-2 image generation/editing.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "AI Zero Token Contributors",
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import{a as e,r as t,t as n}from"./jsx-runtime-DqpGtLhh.js";import{t as r}from"./earth-DFdZaQIi.js";import{t as i}from"./refresh-cw-CAAH2rqe.js";import{t as a}from"./search-B2hz41D3.js";import{C as o,_ as s,a as c,b as l,d as u,f as d,g as f,h as p,i as m,m as h,n as g,o as _,p as v,r as y,s as b,t as x,u as S,v as C,w,y as T}from"./profiles-DMOjJORP.js";import{_ as E,d as D,p as O,r as k,x as A}from"./index-CdFYy5j6.js";import{t as j}from"./InfoRow-0ULI9iI3.js";var M=e(t(),1),N=n();function P(e){let t=e.config?.codex?.accountId,n=e.profiles.length<=0?``:e.profiles.length===1?`profile-count-1`:e.profiles.length===2?`profile-count-2`:e.profiles.length===3?`profile-count-3`:`profile-count-many`;return(0,N.jsxs)(`section`,{className:`card`,id:`accounts`,children:[(0,N.jsxs)(`div`,{className:`section-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`h2`,{children:`账号额度预览`}),(0,N.jsx)(`p`,{children:`账号信息采用卡片式布局展示,支持搜索、状态筛选和额度排序。`})]}),(0,N.jsxs)(`div`,{className:`section-actions`,children:[(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onLocate,children:`定位当前账号`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onExportSelected,children:`导出所选`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onSelectVisible,disabled:e.visibleCount===0,children:`全选筛选结果`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onClearSelected,disabled:e.selectedCount===0,children:`取消选择`}),(0,N.jsx)(`button`,{className:`btn-danger`,type:`button`,onClick:e.onRemoveSelected,disabled:e.selectedCount===0||e.busy===`bulk-remove`,children:`删除所选`}),(0,N.jsx)(`button`,{className:`btn-primary`,type:`button`,onClick:e.onAddAccount,children:`新增账号`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onRefreshStatus,children:`刷新状态`}),(0,N.jsx)(`button`,{className:`btn-danger`,type:`button`,onClick:e.onClearAccounts,children:`清空账号`})]})]}),(0,N.jsx)(`div`,{className:`account-stat-strip`,"aria-label":`账号池统计`,children:e.accountStats.map(t=>(0,N.jsxs)(`button`,{className:`account-stat-pill tone-${t.tone} ${e.filter.status===t.key?`is-active`:``}`,type:`button`,onClick:()=>e.onFilter({...e.filter,status:t.key}),children:[(0,N.jsx)(`span`,{children:t.label}),(0,N.jsx)(`strong`,{children:t.value})]},t.key))}),(0,N.jsxs)(`div`,{className:`filter-row`,children:[(0,N.jsxs)(`label`,{className:`search-box`,children:[(0,N.jsx)(a,{size:16}),(0,N.jsx)(`input`,{value:e.filter.search,onChange:t=>e.onFilter({...e.filter,search:t.target.value}),placeholder:`搜索邮箱、账号 ID 或 Profile ID`})]}),(0,N.jsxs)(`select`,{className:`control`,value:e.filter.status,onChange:t=>e.onFilter({...e.filter,status:t.target.value}),children:[(0,N.jsx)(`option`,{value:`all`,children:`全部状态`}),(0,N.jsx)(`option`,{value:`available`,children:`可用`}),(0,N.jsx)(`option`,{value:`unavailable`,children:`不可用`}),(0,N.jsx)(`option`,{value:`active`,children:`使用中`}),(0,N.jsx)(`option`,{value:`api-active`,children:`API 使用中`}),(0,N.jsx)(`option`,{value:`codex-active`,children:`Codex 使用中`}),(0,N.jsx)(`option`,{value:`healthy`,children:`健康`}),(0,N.jsx)(`option`,{value:`warning`,children:`即将耗尽`}),(0,N.jsx)(`option`,{value:`exhausted`,children:`额度耗尽`}),(0,N.jsx)(`option`,{value:`invalid`,children:`登录/认证异常`}),(0,N.jsx)(`option`,{value:`login-invalid`,children:`登录失效`}),(0,N.jsx)(`option`,{value:`auth-error`,children:`认证异常`}),(0,N.jsx)(`option`,{value:`expired`,children:`已过期`}),(0,N.jsx)(`option`,{value:`free`,children:`Free`}),(0,N.jsx)(`option`,{value:`plus`,children:`Plus`}),(0,N.jsx)(`option`,{value:`pro-team`,children:`Pro/Team`}),(0,N.jsx)(`option`,{value:`auto-included`,children:`参与轮换`}),(0,N.jsx)(`option`,{value:`auto-excluded`,children:`排除轮换`})]}),(0,N.jsxs)(`select`,{className:`control`,value:e.filter.sort,onChange:t=>e.onFilter({...e.filter,sort:t.target.value}),children:[(0,N.jsx)(`option`,{value:`quota-desc`,children:`默认排序`}),(0,N.jsx)(`option`,{value:`latency-asc`,children:`按额度更新时间`}),(0,N.jsx)(`option`,{value:`expiry-asc`,children:`按过期时间`}),(0,N.jsx)(`option`,{value:`name-asc`,children:`按名称排序`}),(0,N.jsx)(`option`,{value:`quota-asc`,children:`按剩余额度升序`}),(0,N.jsx)(`option`,{value:`plan-desc`,children:`按套餐排序`}),(0,N.jsx)(`option`,{value:`email-asc`,children:`按邮箱排序`})]}),(0,N.jsxs)(`span`,{className:`account-selected-count`,children:[`已选择 `,e.selectedCount,` 个`]})]}),(0,N.jsx)(`div`,{className:`account-grid ${n}`,children:e.profiles.length===0?(0,N.jsx)(`div`,{className:`empty-state`,children:`还没有匹配的账号。可以新增账号或调整筛选条件。`}):e.profiles.map(n=>{let a=d(n),o=u(n),p=T(n),y=!!e.expandedProfiles[n.profileId],b=!!(t&&n.accountId===t),S=l(n,b),w=_(n),D=c(n),O=n.exportAudit,k=O?.exported?`已导出 ${O.count} 次`:`未导出`,M=typeof e.busy==`string`&&e.busy.startsWith(`profile:`)&&e.busy.endsWith(n.profileId),P=e.busy===`profile:sync-quota:${n.profileId}`;return(0,N.jsxs)(`article`,{className:`account-card plan-${g(n)} ${w?`is-auth-invalid`:``}`,"data-profile-card":n.profileId,title:w?x(n):void 0,children:[S&&(0,N.jsx)(`span`,{className:`usage-corner ${S.className}`,children:(0,N.jsx)(`span`,{children:S.label})}),(0,N.jsxs)(`div`,{className:`account-head`,children:[(0,N.jsxs)(`div`,{className:`account-title`,children:[(0,N.jsxs)(`div`,{className:`account-name`,children:[(0,N.jsx)(`span`,{className:`avatar`,children:v(n)}),(0,N.jsx)(`strong`,{children:h(n,e.showEmails)}),(0,N.jsx)(`button`,{"aria-label":`刷新额度`,className:`account-icon-btn`,disabled:M,onClick:()=>e.onAction(`sync-quota`,n),title:`刷新额度`,type:`button`,children:P?(0,N.jsx)(E,{className:`spin`,size:14}):(0,N.jsx)(i,{size:14})})]}),(0,N.jsxs)(`div`,{className:`badge-row`,children:[(0,N.jsx)(`span`,{className:`badge brand`,children:m(n)}),(0,N.jsx)(`span`,{className:`badge ${a.tone}`,children:a.label}),(0,N.jsx)(`span`,{className:`badge ${D.ok?`green`:`orange`}`,children:`gpt-image-2`}),(0,N.jsx)(`span`,{className:`badge ${O?.exported?`orange`:`muted`}`,children:k})]})]}),(0,N.jsxs)(`label`,{className:`account-select`,children:[(0,N.jsx)(`input`,{type:`checkbox`,checked:!!e.selectedProfiles[n.profileId],onChange:t=>e.onSelect(n.profileId,t.target.checked)}),(0,N.jsx)(`span`,{children:`选择`})]})]}),(0,N.jsxs)(`div`,{className:`account-metrics`,children:[(0,N.jsx)(L,{label:s(n,`primary`),value:o,tone:f(o)}),(0,N.jsx)(L,{label:s(n,`secondary`),value:p,tone:f(p)})]}),(0,N.jsxs)(`div`,{className:`usage-status-row`,children:[(0,N.jsxs)(`span`,{className:`usage-status ${n.isActive?`is-active`:``}`,children:[(0,N.jsx)(r,{size:14}),(0,N.jsx)(`span`,{children:`API`}),(0,N.jsx)(`span`,{className:`usage-dot ${n.isActive?`active`:``}`}),(0,N.jsx)(`span`,{className:`usage-state-text`,children:n.isActive?`使用中`:`未使用`})]}),(0,N.jsxs)(`span`,{className:`usage-status ${b?`is-active`:``}`,children:[(0,N.jsx)(A,{size:14}),(0,N.jsx)(`span`,{children:`Codex`}),(0,N.jsx)(`span`,{className:`usage-dot ${b?`active`:``}`}),(0,N.jsx)(`span`,{className:`usage-state-text`,children:b?`使用中`:`未使用`})]})]}),(0,N.jsxs)(`div`,{className:`compact-meta-row`,children:[(0,N.jsxs)(`div`,{className:`compact-reset-list`,children:[(0,N.jsxs)(`div`,{className:`compact-meta-item`,children:[(0,N.jsx)(`label`,{children:s(n,`primary`)}),(0,N.jsx)(`strong`,{children:C(n,`primary`)})]}),(0,N.jsxs)(`div`,{className:`compact-meta-item`,children:[(0,N.jsx)(`label`,{children:s(n,`secondary`)}),(0,N.jsx)(`strong`,{children:C(n,`secondary`)})]})]}),(0,N.jsx)(`div`,{className:`compact-meta-actions`,children:(0,N.jsxs)(`button`,{className:`details-toggle ${y?`is-expanded`:``}`,type:`button`,onClick:()=>e.onToggle(n.profileId),children:[(0,N.jsx)(`span`,{children:y?`收起详情`:`查看详情`}),(0,N.jsx)(I,{})]})})]}),y&&(0,N.jsxs)(`div`,{className:`meta-grid`,children:[(0,N.jsx)(j,{label:`套餐`,value:m(n)}),(0,N.jsx)(j,{label:`Account ID`,value:(e.showEmails,n.accountId),code:!0}),(0,N.jsx)(j,{label:`Profile ID`,value:(e.showEmails,n.profileId),code:!0}),(0,N.jsx)(j,{label:`认证状态`,value:x(n)}),(0,N.jsx)(j,{label:`生图能力`,value:D.ok?`gpt-image-2 可用`:D.detail}),(0,N.jsx)(j,{label:`导出记录`,value:F(O)}),(0,N.jsx)(j,{label:`过期时间`,value:n.expiresAt?new Date(n.expiresAt).toLocaleString(`zh-CN`):`-`}),(0,N.jsx)(j,{label:`额度快照`,value:n.quota?.capturedAt?new Date(n.quota.capturedAt).toLocaleString(`zh-CN`):`-`})]}),(0,N.jsxs)(`div`,{className:`account-actions`,children:[(0,N.jsx)(`button`,{className:`btn-secondary ${n.isActive?`is-current`:``}`,type:`button`,onClick:()=>e.onAction(`activate`,n),disabled:n.isActive||M||w,children:w?`网关不可用`:n.isActive?`网关使用中`:`应用网关`}),(0,N.jsx)(`button`,{className:`btn-secondary ${b?`is-current codex`:``}`,type:`button`,onClick:()=>e.onAction(`apply-codex`,n),disabled:b||M||w,children:w?`Codex 不可用`:b?`Codex 使用中`:`应用 Codex`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:()=>e.onAction(`export`,n),disabled:M,children:`导出`}),(0,N.jsx)(`button`,{className:`btn-danger`,type:`button`,onClick:()=>e.onAction(`remove`,n),disabled:M,children:`删除`})]})]},n.profileId)})})]})}function F(e){if(!e?.exported)return`未导出`;let t=e.lastExportKind===`single`?`单账号导出`:e.lastExportKind===`batch`?`批量导出`:`全部导出`;return`${e.count} 次,最近 ${o(e.lastExportedAt)},方式 ${t}`}function I(){return(0,N.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,"aria-hidden":`true`,children:(0,N.jsx)(`path`,{d:`m6 9 6 6 6-6`})})}function L(e){return(0,N.jsxs)(`div`,{className:`quota-row`,children:[(0,N.jsxs)(`div`,{className:`quota-line`,children:[(0,N.jsxs)(`span`,{children:[e.label,` · 已用 `,e.value,`% / 剩余 `,100-e.value,`%`]}),(0,N.jsxs)(`strong`,{children:[`剩余 `,100-e.value,`%`]})]}),(0,N.jsx)(`div`,{className:`progress-track`,children:(0,N.jsx)(`div`,{className:`progress-bar ${e.tone}`,style:{width:`${e.value}%`}})})]})}function R(e){let[t,n]=(0,M.useState)({}),[r,i]=(0,M.useState)({}),[a,o]=(0,M.useState)({search:``,status:`all`,sort:`quota-desc`}),s=(0,M.useMemo)(()=>{let t=e.config?.profiles?[...e.config.profiles]:[],n=new Set(e.config?.settings.autoSwitch.excludedProfileIds||[]),r=a.search.trim().toLowerCase(),i=t.filter(t=>{let i=[h(t,!0).toLowerCase(),t.accountId,t.profileId,t.email||``].join(` `).toLowerCase(),o=d(t),s=!!(e.codexAccountId&&t.accountId===e.codexAccountId),c=g(t);return r&&!i.includes(r)?!1:a.status===`active`?t.isActive||s:a.status===`healthy`?o.key===`healthy`:a.status===`warning`?o.key===`warning`:a.status===`exhausted`?o.key===`exhausted`:a.status===`expired`?o.key===`expired`:a.status===`invalid`?o.key===`invalid`:a.status===`login-invalid`?t.authStatus?.state===`token_invalidated`:a.status===`auth-error`?t.authStatus?.state===`auth_error`:a.status===`available`?o.key===`healthy`||o.key===`warning`:a.status===`unavailable`?o.key===`invalid`||o.key===`expired`||o.key===`exhausted`:a.status===`free`?c===`free`:a.status===`plus`?c===`plus`:a.status===`pro-team`?c===`pro`||c===`team`||c===`enterprise`||c===`premium`:a.status===`api-active`?t.isActive:a.status===`codex-active`?s:a.status===`auto-included`?!n.has(t.profileId):a.status===`auto-excluded`?n.has(t.profileId):!0});return i.sort((t,n)=>{let r=p(t,e.codexAccountId)-p(n,e.codexAccountId);if(r!==0)return r;let i=y(n)-y(t);if(i!==0)return i;let o=S(n)-S(t);return o===0?a.sort===`latency-asc`?(n.quota?.capturedAt||0)-(t.quota?.capturedAt||0):a.sort===`expiry-asc`?(t.expiresAt||2**53-1)-(n.expiresAt||2**53-1):a.sort===`name-asc`?h(t,!0).localeCompare(h(n,!0),`zh-CN`):a.sort===`quota-asc`?100-u(n)-(100-u(t)):a.sort===`plan-desc`?y(n)-y(t):a.sort===`email-asc`?h(t,!0).localeCompare(h(n,!0)):u(n)-u(t):o}),i},[a,e.codexAccountId,e.config?.profiles,e.config?.settings.autoSwitch.excludedProfileIds]),c=(0,M.useMemo)(()=>{let t=e.config?.profiles||[],n=new Set(e.config?.settings.autoSwitch.excludedProfileIds||[]),r=e=>t.filter(e).length,i=r(t=>!!(e.codexAccountId&&t.accountId===e.codexAccountId));return[{key:`all`,label:`总账号`,value:t.length,tone:`blue`},{key:`available`,label:`可用`,value:r(e=>[`healthy`,`warning`].includes(d(e).key)),tone:`green`},{key:`unavailable`,label:`不可用`,value:r(e=>[`invalid`,`expired`,`exhausted`].includes(d(e).key)),tone:`red`},{key:`login-invalid`,label:`登录失效`,value:r(e=>e.authStatus?.state===`token_invalidated`),tone:`red`},{key:`auth-error`,label:`认证异常`,value:r(e=>e.authStatus?.state===`auth_error`),tone:`red`},{key:`exhausted`,label:`额度耗尽`,value:r(e=>d(e).key===`exhausted`),tone:`orange`},{key:`free`,label:`Free`,value:r(e=>g(e)===`free`),tone:`muted`},{key:`plus`,label:`Plus`,value:r(e=>g(e)===`plus`),tone:`brand`},{key:`pro-team`,label:`Pro/Team`,value:r(e=>[`pro`,`team`,`enterprise`,`premium`].includes(g(e))),tone:`blue`},{key:`api-active`,label:`API 使用中`,value:r(e=>e.isActive),tone:`green`},{key:`codex-active`,label:`Codex 使用中`,value:i,tone:`green`},{key:`auto-included`,label:`参与轮换`,value:r(e=>!n.has(e.profileId)),tone:`blue`},{key:`auto-excluded`,label:`排除轮换`,value:r(e=>n.has(e.profileId)),tone:`orange`}]},[e.codexAccountId,e.config?.profiles,e.config?.settings.autoSwitch.excludedProfileIds]),l=Object.values(t).filter(Boolean).length,f=Object.keys(t).filter(e=>t[e]),m=(0,M.useMemo)(()=>s.map(e=>e.profileId),[s]);async function v(t,n){let r=await O(`/_gateway/admin/profiles/export`,{method:`POST`,headers:{"Content-Type":`application/json`},body:w(n?{profileIds:n}:{profileId:t})});D(`ai-zero-token-${n?`profiles-${n.length}`:t||`active`}.json`,r.profile),r.config?e.setConfig(r.config):await e.refreshConfig({silent:!0}),e.setStatus(n?`已导出 ${n.length} 个账号。`:`账号配置已导出。`)}async function x(t,n){if(!(t===`remove`&&!window.confirm(`确认删除 ${h(n,e.showEmails)}?`))){if((t===`activate`||t===`apply-codex`)&&_(n)){e.setStatus(`${h(n,e.showEmails)} 登录已失效,不能应用到${t===`activate`?`网关`:`Codex`}。`);return}if((t===`activate`||t===`apply-codex`)&&b(n)){let r=t===`activate`?`网关`:`Codex`;if(!window.confirm(`${h(n,e.showEmails)} 的额度看起来已耗尽,仍要应用到${r}吗?`))return}if(t===`export`){await v(n.profileId);return}e.setBusy(`profile:${t}:${n.profileId}`);try{let r=await O({activate:`/_gateway/admin/profiles/activate`,"apply-codex":`/_gateway/admin/codex/apply`,"sync-quota":`/_gateway/admin/profiles/sync-quota`,remove:`/_gateway/admin/profiles/remove`}[t],{method:`POST`,headers:{"Content-Type":`application/json`},body:w({profileId:n.profileId})}),i=`config`in r?r.config:r;if(e.setConfig(i),e.setStatus(t===`activate`?`已应用到网关。`:t===`apply-codex`?`已应用到本机 Codex。`:t===`sync-quota`?`额度信息已同步。`:`账号已删除。`),t===`apply-codex`)if(i.codexRestartSupported&&window.confirm(`Codex 账号已切换,是否现在重启 Codex 客户端?
|
|
2
|
-
|
|
3
|
-
Codex 通常在启动时读取本机 auth.json,重启后新账号会立即生效。`))try{await O(`/_gateway/admin/desktop/restart-codex`,{method:`POST`}),e.setStatus(`已应用到本机 Codex,并已重启 Codex 客户端。`)}catch(t){e.setStatus(`已应用到本机 Codex,但重启 Codex 失败: ${k(t)}`)}else e.setStatus(`已应用到本机 Codex,重启 Codex 客户端后生效。`)}catch(t){e.setStatus(k(t))}finally{e.setBusy(null)}}}async function C(){let t=f;if(t.length===0){e.setStatus(`请先勾选要删除的账号。`);return}let r=e.config?.profiles.filter(e=>t.includes(e.profileId)).slice(0,3).map(t=>h(t,e.showEmails)),i=r?.length?`\n\n${r.join(`
|
|
4
|
-
`)}${t.length>r.length?`\n等 ${t.length} 个账号`:``}`:``;if(window.confirm(`确认删除所选 ${t.length} 个账号?此操作不可撤销。${i}`)){e.setBusy(`bulk-remove`),e.setStatus(`正在删除 ${t.length} 个账号...`);try{let r=await O(`/_gateway/admin/profiles/remove-batch`,{method:`POST`,headers:{"Content-Type":`application/json`},body:w({profileIds:t})});e.setConfig(r),n({}),e.setStatus(`已删除 ${r.removedProfileCount??t.length} 个账号。`)}catch(t){e.setStatus(`删除所选失败: ${k(t)}`)}finally{e.setBusy(null)}}}function T(t,r){if(t.length===0){e.setStatus(`没有可选择的账号。`);return}n(e=>{let n={...e};for(let e of t)n[e]=!0;return n}),e.setStatus(r)}return(0,N.jsx)(P,{config:e.config,profiles:s,accountStats:c,showEmails:e.showEmails,filter:a,selectedProfiles:t,expandedProfiles:r,selectedCount:l,visibleCount:m.length,busy:e.busy,onFilter:o,onSelect:(e,t)=>n(n=>({...n,[e]:t})),onSelectVisible:()=>T(m,`已选择当前筛选结果 ${m.length} 个账号。`),onClearSelected:()=>{n({}),e.setStatus(`已取消选择。`)},onToggle:e=>i(t=>({...t,[e]:!t[e]})),onAction:x,onLocate:()=>e.activeProfile&&document.querySelector(`[data-profile-card="${e.activeProfile.profileId}"]`)?.scrollIntoView({behavior:`smooth`,block:`center`}),onExportSelected:()=>{let t=f;if(t.length===0){e.setStatus(`请先勾选要导出的账号。`);return}v(void 0,t).catch(t=>e.setStatus(t instanceof Error?t.message:String(t)))},onRemoveSelected:()=>void C(),onAddAccount:()=>e.setAccountModalOpen(!0),onRefreshStatus:()=>e.refreshConfig({runtime:!0}),onClearAccounts:()=>e.logout()})}export{R as AccountsPage};
|