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.
Files changed (30) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/README.md +5 -5
  3. package/README.zh-CN.md +5 -5
  4. package/admin-ui/dist/assets/accounts-D3tsDc3k.js +4 -0
  5. package/admin-ui/dist/assets/{docs--eK_2fzC.js → docs-BO-aSEzh.js} +1 -1
  6. package/admin-ui/dist/assets/{image-bed-7wBZ1GhS.js → image-bed-Dql7Vqd9.js} +1 -1
  7. package/admin-ui/dist/assets/{index-CdFYy5j6.js → index-CCiBaGwU.js} +3 -3
  8. package/admin-ui/dist/assets/{launch-BiD1Khtg.js → launch-DXLo-NIM.js} +1 -1
  9. package/admin-ui/dist/assets/{logs-BdoKDqh2.js → logs-Cwn8-rDu.js} +1 -1
  10. package/admin-ui/dist/assets/{network-detect-BvKns5nQ.js → network-detect-vzWfL-Tz.js} +1 -1
  11. package/admin-ui/dist/assets/{overview-wm6M45fu.js → overview-B_yad8ge.js} +1 -1
  12. package/admin-ui/dist/assets/{profiles-DMOjJORP.js → profiles-C5SmQvju.js} +1 -1
  13. package/admin-ui/dist/assets/{settings-DOOu7Kd8.js → settings-BdRWcKJb.js} +1 -1
  14. package/admin-ui/dist/assets/{tester-NrARmlis.js → tester-BKoMSoCz.js} +1 -1
  15. package/admin-ui/dist/assets/{usage-CdWRVMDV.js → usage-B-qQxXzQ.js} +1 -1
  16. package/admin-ui/dist/index.html +2 -2
  17. package/dist/cli/commands/help.js +1 -1
  18. package/dist/cli/commands/models.js +3 -2
  19. package/dist/core/context.js +1 -1
  20. package/dist/core/models/openai-codex-models.js +106 -1
  21. package/dist/core/providers/http-client.js +142 -12
  22. package/dist/core/services/auth-service.js +104 -7
  23. package/dist/core/services/chat-service.js +16 -18
  24. package/dist/core/services/model-service.js +22 -8
  25. package/dist/server/app.js +179 -23
  26. package/dist/server/index.js +1 -1
  27. package/docs/API_USAGE.md +1 -1
  28. package/docs/DESKTOP_RELEASE.md +12 -1
  29. package/package.json +1 -1
  30. package/admin-ui/dist/assets/accounts-bCDKXGg9.js +0 -4
@@ -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 isQuotaLimitError(error) {
1143
- const normalized = normalizeError(error);
1144
- const marker = `${normalized.upstreamErrorCode ?? ""} ${normalized.upstreamErrorType ?? ""} ${normalized.message}`.toLowerCase();
1145
- return normalized.upstreamStatus === 429 || marker.includes("usage_limit_reached");
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: params?.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: normalized.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: normalized.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
- const codexBody = createCodexPassthroughBody(data, model);
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
- const switchedProfile = await ctx.authService.recordProfileRequestFailure(profile.profileId, error, quota, "openai-codex");
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 && isQuotaLimitError(error) && switchedProfile && switchedProfile.profileId !== profile.profileId && !abortController.signal.aborted) {
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: {
@@ -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 = 32;
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
- Refresh the local Codex model list:
113
+ Sync the Codex model list from the Codex backend into the local cache:
114
114
 
115
115
  ```bash
116
116
  azt models --refresh
@@ -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.5`, replace `{version}` with `2.0.5`.
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.6",
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};