ai-zero-token 2.0.5 → 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 (44) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +20 -17
  3. package/README.zh-CN.md +20 -17
  4. package/admin-ui/dist/assets/StatCard-7TEzqn2i.js +1 -0
  5. package/admin-ui/dist/assets/accounts-D3tsDc3k.js +4 -0
  6. package/admin-ui/dist/assets/{docs-Dh0aFha_.js → docs-BO-aSEzh.js} +1 -1
  7. package/admin-ui/dist/assets/{image-bed-C1M7-0q1.js → image-bed-Dql7Vqd9.js} +1 -1
  8. package/admin-ui/dist/assets/index-C22_3Mxq.css +1 -0
  9. package/admin-ui/dist/assets/index-CCiBaGwU.js +10 -0
  10. package/admin-ui/dist/assets/{launch-pB7YlWFI.js → launch-DXLo-NIM.js} +1 -1
  11. package/admin-ui/dist/assets/{logs-B7McijSi.js → logs-Cwn8-rDu.js} +1 -1
  12. package/admin-ui/dist/assets/{network-detect-Bx3XmXPk.js → network-detect-vzWfL-Tz.js} +1 -1
  13. package/admin-ui/dist/assets/overview-B_yad8ge.js +1 -0
  14. package/admin-ui/dist/assets/{profiles-DMOjJORP.js → profiles-C5SmQvju.js} +1 -1
  15. package/admin-ui/dist/assets/settings-BdRWcKJb.js +5 -0
  16. package/admin-ui/dist/assets/{tester-BG-up8qP.js → tester-BKoMSoCz.js} +1 -1
  17. package/admin-ui/dist/assets/usage-B-qQxXzQ.js +1 -0
  18. package/admin-ui/dist/index.html +3 -3
  19. package/dist/cli/commands/help.js +1 -1
  20. package/dist/cli/commands/models.js +3 -2
  21. package/dist/core/context.js +4 -1
  22. package/dist/core/models/openai-codex-models.js +106 -1
  23. package/dist/core/providers/http-client.js +160 -11
  24. package/dist/core/providers/openai-codex/chat.js +2 -1
  25. package/dist/core/providers/openai-codex/chatgpt-web-image.js +1404 -0
  26. package/dist/core/services/auth-service.js +154 -10
  27. package/dist/core/services/chat-service.js +16 -18
  28. package/dist/core/services/config-service.js +9 -0
  29. package/dist/core/services/image-service.js +31 -1
  30. package/dist/core/services/model-service.js +22 -8
  31. package/dist/core/services/usage-service.js +349 -0
  32. package/dist/core/store/codex-auth-store.js +149 -15
  33. package/dist/core/store/settings-store.js +8 -2
  34. package/dist/core/store/state-paths.js +17 -1
  35. package/dist/server/app.js +1023 -69
  36. package/dist/server/index.js +1 -1
  37. package/docs/API_USAGE.md +34 -4
  38. package/docs/DESKTOP_RELEASE.md +12 -1
  39. package/package.json +1 -1
  40. package/admin-ui/dist/assets/accounts-ABMyXo4H.js +0 -4
  41. package/admin-ui/dist/assets/index--rNjdmzf.js +0 -10
  42. package/admin-ui/dist/assets/index-DjtN30PC.css +0 -1
  43. package/admin-ui/dist/assets/overview-CV0H2Nsq.js +0 -1
  44. package/admin-ui/dist/assets/settings-ynCIdUvZ.js +0 -7
@@ -7,6 +7,9 @@ import { PassThrough, Readable } from "node:stream";
7
7
  import { loadSettings } from "../store/settings-store.js";
8
8
  const CURL_STATUS_MARKER = "\n__CURL_STATUS__:";
9
9
  const CURL_HEADERS_MARKER = "\n__CURL_HEADERS__:";
10
+ const DEFAULT_CURL_STREAM_HEADER_TIMEOUT_MS = 18e4;
11
+ const MIN_CURL_STREAM_HEADER_TIMEOUT_MS = 3e4;
12
+ const MAX_CURL_STREAM_HEADER_TIMEOUT_MS = 6e5;
10
13
  let requestSequence = 0;
11
14
  function nextRequestId() {
12
15
  requestSequence += 1;
@@ -29,6 +32,17 @@ function safeConsole(method, message, meta) {
29
32
  } catch {
30
33
  }
31
34
  }
35
+ function getCurlStreamHeaderTimeoutMs(timeoutMs) {
36
+ if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0) {
37
+ return Math.max(1e3, timeoutMs);
38
+ }
39
+ const configured = process.env.AZT_CURL_STREAM_HEADER_TIMEOUT_MS?.trim();
40
+ const parsed = configured ? Number.parseInt(configured, 10) : NaN;
41
+ if (Number.isFinite(parsed) && parsed > 0) {
42
+ return Math.min(MAX_CURL_STREAM_HEADER_TIMEOUT_MS, Math.max(MIN_CURL_STREAM_HEADER_TIMEOUT_MS, parsed));
43
+ }
44
+ return DEFAULT_CURL_STREAM_HEADER_TIMEOUT_MS;
45
+ }
32
46
  function logHttpTiming(params) {
33
47
  safeConsole("info", "[http] request timing", {
34
48
  requestId: params.requestId,
@@ -42,6 +56,60 @@ function logHttpTiming(params) {
42
56
  totalMs: params.timing.totalMs
43
57
  });
44
58
  }
59
+ function createHttpTransportError(message, params) {
60
+ const error = new Error(message);
61
+ error.code = params.code;
62
+ error.elapsedMs = roundMs(params.elapsedMs);
63
+ error.isTransient = params.transient ?? true;
64
+ error.method = params.method;
65
+ error.requestId = params.requestId;
66
+ error.statusCode = params.statusCode;
67
+ error.transport = params.transport;
68
+ error.upstreamConnectionError = params.upstreamConnectionError ?? true;
69
+ error.url = params.url;
70
+ return error;
71
+ }
72
+ function classifyCurlRequestError(stderr, exitCode) {
73
+ const normalized = stderr.toLowerCase();
74
+ if (exitCode === 35 || normalized.includes("ssl_connect") || normalized.includes("ssl_error_syscall")) {
75
+ return {
76
+ code: "curl_request_tls_failed",
77
+ statusCode: 502
78
+ };
79
+ }
80
+ if (exitCode === 6 || normalized.includes("could not resolve host") || normalized.includes("name lookup timed out")) {
81
+ return {
82
+ code: "curl_request_dns_failed",
83
+ statusCode: 502
84
+ };
85
+ }
86
+ if (exitCode === 7 || normalized.includes("failed to connect") || normalized.includes("connection refused")) {
87
+ return {
88
+ code: "curl_request_connect_failed",
89
+ statusCode: 502
90
+ };
91
+ }
92
+ if (exitCode === 28 || normalized.includes("operation timed out") || normalized.includes("timed out")) {
93
+ return {
94
+ code: "curl_request_timeout",
95
+ statusCode: 504
96
+ };
97
+ }
98
+ return {
99
+ code: "curl_request_failed",
100
+ statusCode: 502
101
+ };
102
+ }
103
+ function isTransientHttpError(error) {
104
+ if (!error || typeof error !== "object") {
105
+ return false;
106
+ }
107
+ const details = error;
108
+ if (details.isTransient === true || details.upstreamConnectionError === true) {
109
+ return true;
110
+ }
111
+ return typeof details.code === "string" && (details.code === "curl_stream_closed_before_headers" || details.code === "curl_stream_header_timeout" || details.code === "curl_stream_body_failed" || details.code === "curl_request_tls_failed" || details.code === "curl_request_dns_failed" || details.code === "curl_request_connect_failed" || details.code === "curl_request_timeout" || details.code === "curl_request_failed");
112
+ }
45
113
  function isElectronRuntime() {
46
114
  return typeof process.versions.electron === "string";
47
115
  }
@@ -69,12 +137,7 @@ function normalizeCurlHeaders(value) {
69
137
  })
70
138
  );
71
139
  }
72
- function parseCurlHeaderDump(raw) {
73
- const blocks = raw.replace(/\r\n/g, "\n").split(/\n\n+/).map((block2) => block2.trim()).filter((block2) => /^HTTP\//i.test(block2));
74
- const block = blocks[blocks.length - 1];
75
- if (!block) {
76
- return null;
77
- }
140
+ function parseCurlHeaderBlock(block) {
78
141
  const lines = block.split("\n");
79
142
  const statusMatch = /^HTTP(?:\/\S+)?\s+(\d{3})/i.exec(lines[0]?.trim() ?? "");
80
143
  const status = statusMatch ? Number.parseInt(statusMatch[1], 10) : NaN;
@@ -96,6 +159,33 @@ function parseCurlHeaderDump(raw) {
96
159
  }
97
160
  return { status, headers };
98
161
  }
162
+ function parseCurlHeaderDump(raw) {
163
+ const normalized = raw.replace(/\r\n/g, "\n");
164
+ const blocks = [];
165
+ let blockStart = 0;
166
+ while (blockStart < normalized.length) {
167
+ const separatorIndex = normalized.indexOf("\n\n", blockStart);
168
+ if (separatorIndex === -1) {
169
+ break;
170
+ }
171
+ const block = normalized.slice(blockStart, separatorIndex).trim();
172
+ if (/^HTTP\//i.test(block)) {
173
+ blocks.push(block);
174
+ }
175
+ blockStart = separatorIndex + 2;
176
+ while (normalized[blockStart] === "\n") {
177
+ blockStart += 1;
178
+ }
179
+ }
180
+ for (let index = blocks.length - 1; index >= 0; index -= 1) {
181
+ const parsed = parseCurlHeaderBlock(blocks[index]);
182
+ if (!parsed || parsed.status >= 100 && parsed.status < 200) {
183
+ continue;
184
+ }
185
+ return parsed;
186
+ }
187
+ return null;
188
+ }
99
189
  async function runCurlRequest(init, params) {
100
190
  const requestId = params?.requestId ?? nextRequestId();
101
191
  const startedAt = performance.now();
@@ -152,7 +242,20 @@ async function runCurlRequest(init, params) {
152
242
  });
153
243
  phases.waitForCurlMs = performance.now() - startedAt - phases.spawnCurlMs;
154
244
  if (exitCode !== 0) {
155
- throw new Error(stderr.trim() || `curl \u8BF7\u6C42\u5931\u8D25\uFF0C\u9000\u51FA\u7801 ${exitCode}`);
245
+ const stderrText = stderr.trim();
246
+ const classification = classifyCurlRequestError(stderrText, exitCode);
247
+ throw createHttpTransportError(
248
+ stderrText || `curl \u8BF7\u6C42\u5931\u8D25\uFF0C\u9000\u51FA\u7801 ${exitCode}\uFF08requestId=${requestId}\uFF09\u3002`,
249
+ {
250
+ code: classification.code,
251
+ elapsedMs: performance.now() - startedAt,
252
+ method: init.method,
253
+ requestId,
254
+ statusCode: classification.statusCode,
255
+ transport: "curl",
256
+ url: init.url
257
+ }
258
+ );
156
259
  }
157
260
  const parseStartedAt = performance.now();
158
261
  const statusMarkerIndex = stdout.lastIndexOf(CURL_STATUS_MARKER);
@@ -210,16 +313,45 @@ async function waitForCurlHeaders(params) {
210
313
  const raw = await fs.readFile(params.headerPath, "utf8");
211
314
  const parsed = parseCurlHeaderDump(raw);
212
315
  if (parsed) {
316
+ if (parsed.status >= 300 && parsed.status < 400 && !params.isClosed()) {
317
+ await new Promise((resolve) => setTimeout(resolve, 25));
318
+ continue;
319
+ }
213
320
  return parsed;
214
321
  }
215
322
  } catch {
216
323
  }
217
324
  if (params.isClosed()) {
218
- throw new Error(params.stderr().trim() || "curl stream \u8BF7\u6C42\u5728\u8FD4\u56DE\u54CD\u5E94\u5934\u524D\u7ED3\u675F\u3002");
325
+ const stderr2 = params.stderr().trim();
326
+ const exitCode = params.exitCode();
327
+ throw createHttpTransportError(
328
+ stderr2 || `curl stream \u8BF7\u6C42\u5728\u8FD4\u56DE\u54CD\u5E94\u5934\u524D\u7ED3\u675F\uFF08requestId=${params.requestId}${exitCode ? `, exitCode=${exitCode}` : ""}\uFF09\u3002`,
329
+ {
330
+ code: "curl_stream_closed_before_headers",
331
+ elapsedMs: performance.now() - startedAt,
332
+ method: params.method,
333
+ requestId: params.requestId,
334
+ statusCode: 502,
335
+ transport: "curl",
336
+ url: params.url
337
+ }
338
+ );
219
339
  }
220
340
  await new Promise((resolve) => setTimeout(resolve, 25));
221
341
  }
222
- throw new Error("\u7B49\u5F85 curl stream \u54CD\u5E94\u5934\u8D85\u65F6\u3002");
342
+ const stderr = params.stderr().trim();
343
+ throw createHttpTransportError(
344
+ `\u7B49\u5F85 curl stream \u54CD\u5E94\u5934\u8D85\u65F6\uFF08${Math.round(params.timeoutMs / 1e3)} \u79D2\uFF0CrequestId=${params.requestId}\uFF09\u3002${stderr ? ` ${stderr}` : ""}`,
345
+ {
346
+ code: "curl_stream_header_timeout",
347
+ elapsedMs: performance.now() - startedAt,
348
+ method: params.method,
349
+ requestId: params.requestId,
350
+ statusCode: 504,
351
+ transport: "curl",
352
+ url: params.url
353
+ }
354
+ );
223
355
  }
224
356
  async function runCurlStream(init, params) {
225
357
  if (init.signal?.aborted) {
@@ -266,6 +398,7 @@ async function runCurlStream(init, params) {
266
398
  child.stdin.on("error", () => void 0);
267
399
  child.stdin.end(hasBody ? init.body : void 0);
268
400
  const body = new PassThrough();
401
+ body.on("error", () => void 0);
269
402
  const phases = {
270
403
  spawnCurlMs: performance.now() - startedAt
271
404
  };
@@ -292,16 +425,31 @@ async function runCurlStream(init, params) {
292
425
  init.signal?.removeEventListener("abort", abort);
293
426
  void fs.unlink(headerPath).catch(() => void 0);
294
427
  if (exitCode !== 0 && !init.signal?.aborted) {
295
- body.destroy(new Error(stderr.trim() || `curl stream \u8BF7\u6C42\u5931\u8D25\uFF0C\u9000\u51FA\u7801 ${exitCode}`));
428
+ body.destroy(createHttpTransportError(
429
+ stderr.trim() || `curl stream \u8BF7\u6C42\u5931\u8D25\uFF0C\u9000\u51FA\u7801 ${exitCode}\uFF08requestId=${requestId}\uFF09\u3002`,
430
+ {
431
+ code: "curl_stream_body_failed",
432
+ elapsedMs: performance.now() - startedAt,
433
+ method: init.method,
434
+ requestId,
435
+ statusCode: 502,
436
+ transport: "curl",
437
+ url: init.url
438
+ }
439
+ ));
296
440
  }
297
441
  });
298
442
  let parsed;
299
443
  try {
300
444
  parsed = await waitForCurlHeaders({
301
445
  headerPath,
446
+ method: init.method,
302
447
  isClosed: () => closed,
448
+ exitCode: () => exitCode,
303
449
  stderr: () => stderr,
304
- timeoutMs: typeof params?.timeoutMs === "number" ? Math.min(params.timeoutMs, 3e4) : 3e4
450
+ requestId,
451
+ timeoutMs: getCurlStreamHeaderTimeoutMs(params?.timeoutMs),
452
+ url: init.url
305
453
  });
306
454
  } catch (error) {
307
455
  child.kill("SIGTERM");
@@ -461,6 +609,7 @@ async function requestStream(init) {
461
609
  });
462
610
  }
463
611
  export {
612
+ isTransientHttpError,
464
613
  requestStream,
465
614
  requestText
466
615
  };
@@ -2,6 +2,7 @@
2
2
  import { DEFAULT_CODEX_MODEL } from "../../models/openai-codex-models.js";
3
3
  import { requestStream, requestText } from "../http-client.js";
4
4
  const CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
5
+ const CODEX_RESPONSES_COMPACT_URL = `${CODEX_RESPONSES_URL}/compact`;
5
6
  const URL_KEY_RE = /(url|uri|href|download|preview|thumbnail|image|asset|file)/i;
6
7
  const REFERENCE_KEY_RE = /(image|asset|file|media|blob|artifact|download|preview|thumbnail)/i;
7
8
  const REFERENCE_VALUE_RE = /^(file|asset|image|img|media|blob)-[\w-]+$/i;
@@ -428,7 +429,7 @@ async function streamOpenAICodex(params) {
428
429
  }
429
430
  const response = await requestStream({
430
431
  method: "POST",
431
- url: CODEX_RESPONSES_URL,
432
+ url: params.endpoint === "responses/compact" ? CODEX_RESPONSES_COMPACT_URL : CODEX_RESPONSES_URL,
432
433
  headers: buildCodexRequestHeaders(params.profile),
433
434
  body: JSON.stringify(requestBody),
434
435
  signal: params.signal