ai-zero-token 1.0.1 → 1.0.3

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 (70) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +235 -69
  3. package/dist/api.js +0 -1
  4. package/dist/cli/commands/ask.js +131 -5
  5. package/dist/cli/commands/clear.js +0 -1
  6. package/dist/cli/commands/help.js +17 -11
  7. package/dist/cli/commands/login.js +0 -1
  8. package/dist/cli/commands/models.js +14 -4
  9. package/dist/cli/commands/serve.js +41 -4
  10. package/dist/cli/commands/start.js +10 -0
  11. package/dist/cli/commands/status.js +1 -1
  12. package/dist/cli/index.js +5 -2
  13. package/dist/cli/shared.js +57 -6
  14. package/dist/cli.js +0 -1
  15. package/dist/core/context.js +10 -2
  16. package/dist/core/models/openai-codex-models.js +89 -1
  17. package/dist/core/providers/http-client.js +137 -14
  18. package/dist/core/providers/openai-codex/chat.js +217 -24
  19. package/dist/core/providers/openai-codex/oauth.js +15 -4
  20. package/dist/core/providers/openai-codex/pkce.js +0 -1
  21. package/dist/core/services/auth-service.js +125 -16
  22. package/dist/core/services/chat-service.js +24 -14
  23. package/dist/core/services/config-service.js +4 -5
  24. package/dist/core/services/image-service.js +405 -0
  25. package/dist/core/services/model-service.js +35 -8
  26. package/dist/core/services/version-service.js +97 -0
  27. package/dist/core/store/profile-store.js +79 -6
  28. package/dist/core/store/settings-store.js +1 -2
  29. package/dist/core/types.js +0 -1
  30. package/dist/http.js +0 -1
  31. package/dist/models.js +0 -1
  32. package/dist/oauth.js +0 -1
  33. package/dist/pkce.js +0 -1
  34. package/dist/server/admin-page.js +3165 -0
  35. package/dist/server/app.js +599 -40
  36. package/dist/server/index.js +0 -1
  37. package/dist/store.js +0 -1
  38. package/docs/API_USAGE.md +120 -0
  39. package/package.json +14 -3
  40. package/dist/api.js.map +0 -1
  41. package/dist/cli/commands/ask.js.map +0 -1
  42. package/dist/cli/commands/clear.js.map +0 -1
  43. package/dist/cli/commands/help.js.map +0 -1
  44. package/dist/cli/commands/login.js.map +0 -1
  45. package/dist/cli/commands/models.js.map +0 -1
  46. package/dist/cli/commands/serve.js.map +0 -1
  47. package/dist/cli/commands/status.js.map +0 -1
  48. package/dist/cli/index.js.map +0 -1
  49. package/dist/cli/shared.js.map +0 -1
  50. package/dist/cli.js.map +0 -1
  51. package/dist/core/context.js.map +0 -1
  52. package/dist/core/models/openai-codex-models.js.map +0 -1
  53. package/dist/core/providers/http-client.js.map +0 -1
  54. package/dist/core/providers/openai-codex/chat.js.map +0 -1
  55. package/dist/core/providers/openai-codex/oauth.js.map +0 -1
  56. package/dist/core/providers/openai-codex/pkce.js.map +0 -1
  57. package/dist/core/services/auth-service.js.map +0 -1
  58. package/dist/core/services/chat-service.js.map +0 -1
  59. package/dist/core/services/config-service.js.map +0 -1
  60. package/dist/core/services/model-service.js.map +0 -1
  61. package/dist/core/store/profile-store.js.map +0 -1
  62. package/dist/core/store/settings-store.js.map +0 -1
  63. package/dist/core/types.js.map +0 -1
  64. package/dist/http.js.map +0 -1
  65. package/dist/models.js.map +0 -1
  66. package/dist/oauth.js.map +0 -1
  67. package/dist/pkce.js.map +0 -1
  68. package/dist/server/app.js.map +0 -1
  69. package/dist/server/index.js.map +0 -1
  70. package/dist/store.js.map +0 -1
@@ -1,9 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import { createGatewayContext } from "../../core/context.js";
3
- async function runModelsCommand() {
3
+ async function runModelsCommand(args = []) {
4
4
  const ctx = createGatewayContext();
5
- console.log("\u5F53\u524D demo \u5185\u7F6E\u652F\u6301\u7684\u6A21\u578B:");
6
- for (const model of await ctx.modelService.listModels()) {
5
+ const refresh = args.includes("--refresh");
6
+ const result = refresh ? await ctx.modelService.refreshModels() : {
7
+ models: await ctx.modelService.listModels(),
8
+ catalog: await ctx.modelService.getCatalog()
9
+ };
10
+ console.log(refresh ? "\u5DF2\u91CD\u65B0\u8BFB\u53D6 Codex \u6A21\u578B\u5217\u8868:" : "\u5F53\u524D demo \u53EF\u7528\u6A21\u578B\u5217\u8868:");
11
+ console.log(`- \u6765\u6E90: ${result.catalog.source === "codex-cache" ? "Codex \u672C\u5730\u7F13\u5B58" : "\u9879\u76EE\u5185\u7F6E\u56DE\u9000\u5217\u8868"}`);
12
+ console.log(`- \u8DEF\u5F84: ${result.catalog.cachePath}`);
13
+ if (result.catalog.fetchedAt) {
14
+ console.log(`- Codex \u66F4\u65B0\u65F6\u95F4: ${result.catalog.fetchedAt}`);
15
+ }
16
+ console.log(`- \u6570\u91CF: ${result.catalog.modelCount}`);
17
+ for (const model of result.models) {
7
18
  const suffix = model.isDefault ? " (\u9ED8\u8BA4)" : "";
8
19
  console.log(`- ${model.id}${suffix}`);
9
20
  }
@@ -11,4 +22,3 @@ async function runModelsCommand() {
11
22
  export {
12
23
  runModelsCommand
13
24
  };
14
- //# sourceMappingURL=models.js.map
@@ -2,18 +2,55 @@
2
2
  import { createGatewayContext } from "../../core/context.js";
3
3
  import { startServer } from "../../server/index.js";
4
4
  import { parseServeArgs } from "../shared.js";
5
- async function runServeCommand(args) {
6
- const { host, port } = parseServeArgs(args);
5
+ import { spawn } from "node:child_process";
6
+ function createBrowserUrl(host, port) {
7
+ if (host === "0.0.0.0" || host === "::") {
8
+ return `http://127.0.0.1:${port}`;
9
+ }
10
+ return `http://${host}:${port}`;
11
+ }
12
+ function createListenUrl(host, port) {
13
+ return `http://${host}:${port}`;
14
+ }
15
+ function tryOpenBrowser(url) {
16
+ try {
17
+ if (process.platform === "darwin") {
18
+ const child2 = spawn("open", [url], { stdio: "ignore", detached: true });
19
+ child2.unref();
20
+ return true;
21
+ }
22
+ if (process.platform === "win32") {
23
+ const child2 = spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true });
24
+ child2.unref();
25
+ return true;
26
+ }
27
+ const child = spawn("xdg-open", [url], { stdio: "ignore", detached: true });
28
+ child.unref();
29
+ return true;
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+ async function runServeCommand(args, options) {
35
+ const { host, port, openBrowser } = parseServeArgs(args);
7
36
  const ctx = createGatewayContext();
8
37
  const status = await ctx.authService.getStatus();
9
38
  const server = await startServer({ host, port });
39
+ const adminUrl = createBrowserUrl(server.host, server.port);
40
+ const listenUrl = createListenUrl(server.host, server.port);
41
+ const shouldOpenBrowser = openBrowser ?? options?.openBrowserByDefault ?? false;
10
42
  console.log("\u672C\u5730\u7F51\u5173\u5DF2\u542F\u52A8\u3002");
11
- console.log(`url: http://${server.host}:${server.port}`);
43
+ console.log(`url: ${listenUrl}`);
44
+ console.log(`admin: ${adminUrl}`);
45
+ console.log(`apiBase: ${adminUrl}/v1`);
12
46
  console.log(`corsOrigin: ${server.corsOrigin}`);
13
47
  console.log(`activeProvider: ${status.activeProvider ?? "none"}`);
14
48
  console.log(`defaultModel: ${status.defaultModel}`);
49
+ if (shouldOpenBrowser) {
50
+ const opened = tryOpenBrowser(adminUrl);
51
+ console.log(opened ? "\u5DF2\u5C1D\u8BD5\u6253\u5F00\u7BA1\u7406\u9875\u9762\u3002" : "\u672A\u80FD\u81EA\u52A8\u6253\u5F00\u7BA1\u7406\u9875\u9762\uFF0C\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684 admin \u5730\u5740\u3002");
52
+ }
15
53
  }
16
54
  export {
17
55
  runServeCommand
18
56
  };
19
- //# sourceMappingURL=serve.js.map
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { runServeCommand } from "./serve.js";
3
+ async function runStartCommand(args) {
4
+ await runServeCommand(args, {
5
+ openBrowserByDefault: true
6
+ });
7
+ }
8
+ export {
9
+ runStartCommand
10
+ };
@@ -15,6 +15,7 @@ async function runStatusCommand() {
15
15
  console.log("\u5F53\u524D\u767B\u5F55\u72B6\u6001:");
16
16
  console.log(`provider: ${status.activeProvider}`);
17
17
  console.log(`profileId: ${status.activeProfileId}`);
18
+ console.log(`profileCount: ${status.profileCount}`);
18
19
  console.log(`defaultModel: ${status.defaultModel}`);
19
20
  console.log(`serverHost: ${status.serverHost}`);
20
21
  console.log(`serverPort: ${status.serverPort}`);
@@ -29,4 +30,3 @@ async function runStatusCommand() {
29
30
  export {
30
31
  runStatusCommand
31
32
  };
32
- //# sourceMappingURL=status.js.map
package/dist/cli/index.js CHANGED
@@ -5,6 +5,7 @@ import { printHelp } from "./commands/help.js";
5
5
  import { runLoginCommand } from "./commands/login.js";
6
6
  import { runModelsCommand } from "./commands/models.js";
7
7
  import { runServeCommand } from "./commands/serve.js";
8
+ import { runStartCommand } from "./commands/start.js";
8
9
  import { runStatusCommand } from "./commands/status.js";
9
10
  async function runCli(argv = process.argv.slice(2)) {
10
11
  const [command, ...rest] = argv;
@@ -16,7 +17,7 @@ async function runCli(argv = process.argv.slice(2)) {
16
17
  await runStatusCommand();
17
18
  return;
18
19
  case "models":
19
- await runModelsCommand();
20
+ await runModelsCommand(rest);
20
21
  return;
21
22
  case "ask":
22
23
  await runAskCommand(rest);
@@ -24,6 +25,9 @@ async function runCli(argv = process.argv.slice(2)) {
24
25
  case "serve":
25
26
  await runServeCommand(rest);
26
27
  return;
28
+ case "start":
29
+ await runStartCommand(rest);
30
+ return;
27
31
  case "clear":
28
32
  await runClearCommand();
29
33
  return;
@@ -40,4 +44,3 @@ async function runCli(argv = process.argv.slice(2)) {
40
44
  export {
41
45
  runCli
42
46
  };
43
- //# sourceMappingURL=index.js.map
@@ -7,16 +7,55 @@ function formatExpiry(expires) {
7
7
  function parseAskArgs(args) {
8
8
  const rest = [...args];
9
9
  let model;
10
+ let payloadFile;
11
+ let dumpRawFile;
12
+ let writeArtifactsDir;
13
+ let printRaw = false;
14
+ let allowUnknownModel = false;
10
15
  for (let index = 0; index < rest.length; index += 1) {
11
- if (rest[index] !== "--model") {
16
+ if (rest[index] === "--model") {
17
+ model = rest[index + 1];
18
+ rest.splice(index, 2);
19
+ index -= 1;
12
20
  continue;
13
21
  }
14
- model = rest[index + 1];
15
- rest.splice(index, 2);
16
- break;
22
+ if (rest[index] === "--payload-file") {
23
+ payloadFile = rest[index + 1];
24
+ rest.splice(index, 2);
25
+ index -= 1;
26
+ continue;
27
+ }
28
+ if (rest[index] === "--dump-raw") {
29
+ dumpRawFile = rest[index + 1];
30
+ rest.splice(index, 2);
31
+ index -= 1;
32
+ continue;
33
+ }
34
+ if (rest[index] === "--write-artifacts-dir") {
35
+ writeArtifactsDir = rest[index + 1];
36
+ rest.splice(index, 2);
37
+ index -= 1;
38
+ continue;
39
+ }
40
+ if (rest[index] === "--print-raw") {
41
+ printRaw = true;
42
+ rest.splice(index, 1);
43
+ index -= 1;
44
+ continue;
45
+ }
46
+ if (rest[index] === "--allow-unknown-model") {
47
+ allowUnknownModel = true;
48
+ rest.splice(index, 1);
49
+ index -= 1;
50
+ }
17
51
  }
18
52
  return {
19
53
  model,
54
+ payloadFile,
55
+ dumpRawFile,
56
+ writeArtifactsDir,
57
+ printRaw,
58
+ allowUnknownModel,
20
59
  prompt: rest.join(" ").trim()
21
60
  };
22
61
  }
@@ -24,6 +63,7 @@ function parseServeArgs(args) {
24
63
  const rest = [...args];
25
64
  let host;
26
65
  let port;
66
+ let openBrowser;
27
67
  for (let index = 0; index < rest.length; index += 1) {
28
68
  if (rest[index] === "--host") {
29
69
  host = rest[index + 1];
@@ -38,13 +78,24 @@ function parseServeArgs(args) {
38
78
  }
39
79
  rest.splice(index, 2);
40
80
  index -= 1;
81
+ continue;
82
+ }
83
+ if (rest[index] === "--open") {
84
+ openBrowser = true;
85
+ rest.splice(index, 1);
86
+ index -= 1;
87
+ continue;
88
+ }
89
+ if (rest[index] === "--no-open") {
90
+ openBrowser = false;
91
+ rest.splice(index, 1);
92
+ index -= 1;
41
93
  }
42
94
  }
43
- return { host, port };
95
+ return { host, port, openBrowser };
44
96
  }
45
97
  export {
46
98
  formatExpiry,
47
99
  parseAskArgs,
48
100
  parseServeArgs
49
101
  };
50
- //# sourceMappingURL=shared.js.map
package/dist/cli.js CHANGED
@@ -5,4 +5,3 @@ runCli().catch((error) => {
5
5
  console.error(`\u9519\u8BEF: ${message}`);
6
6
  process.exitCode = 1;
7
7
  });
8
- //# sourceMappingURL=cli.js.map
@@ -2,23 +2,31 @@
2
2
  import { ConfigService } from "./services/config-service.js";
3
3
  import { AuthService } from "./services/auth-service.js";
4
4
  import { ChatService } from "./services/chat-service.js";
5
+ import { ImageService } from "./services/image-service.js";
5
6
  import { ModelService } from "./services/model-service.js";
7
+ import { VersionService } from "./services/version-service.js";
6
8
  function createGatewayContext() {
7
9
  const configService = new ConfigService();
8
10
  const authService = new AuthService(configService);
9
11
  const modelService = new ModelService(configService);
12
+ const versionService = new VersionService();
10
13
  const chatService = new ChatService({
11
14
  authService,
12
15
  modelService
13
16
  });
17
+ const imageService = new ImageService({
18
+ authService,
19
+ configService
20
+ });
14
21
  return {
15
22
  configService,
16
23
  authService,
17
24
  modelService,
18
- chatService
25
+ versionService,
26
+ chatService,
27
+ imageService
19
28
  };
20
29
  }
21
30
  export {
22
31
  createGatewayContext
23
32
  };
24
- //# sourceMappingURL=context.js.map
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
2
5
  const DEFAULT_CODEX_MODEL = "gpt-5.4";
3
6
  const CODEX_MODEL_INFOS = [
4
7
  { provider: "openai-codex", id: "gpt-5.4", name: "GPT-5.4", input: ["text", "image"], source: "static" },
@@ -22,6 +25,88 @@ const SUPPORTED_CODEX_MODELS = [
22
25
  "gpt-5.1-codex-mini",
23
26
  "gpt-5.1-codex-max"
24
27
  ];
28
+ function getCodexModelsCachePath() {
29
+ return process.env.CODEX_MODELS_CACHE_PATH || path.join(os.homedir(), ".codex", "models_cache.json");
30
+ }
31
+ function normalizeInputModalities(input) {
32
+ const rawValues = Array.isArray(input) ? input : [];
33
+ const values = /* @__PURE__ */ new Set();
34
+ for (const item of rawValues) {
35
+ if (item === "text" || item === "image") {
36
+ values.add(item);
37
+ }
38
+ }
39
+ if (values.size === 0) {
40
+ values.add("text");
41
+ }
42
+ return Array.from(values);
43
+ }
44
+ function normalizeCodexCacheEntry(entry) {
45
+ if (!entry || typeof entry.slug !== "string" || !entry.slug) {
46
+ return null;
47
+ }
48
+ if (typeof entry.visibility === "string" && entry.visibility !== "list") {
49
+ return null;
50
+ }
51
+ return {
52
+ provider: "openai-codex",
53
+ id: entry.slug,
54
+ name: typeof entry.display_name === "string" && entry.display_name ? entry.display_name : entry.slug,
55
+ input: normalizeInputModalities(entry.input_modalities),
56
+ source: "codex-cache"
57
+ };
58
+ }
59
+ function dedupeModels(models) {
60
+ const seen = /* @__PURE__ */ new Set();
61
+ const next = [];
62
+ for (const model of models) {
63
+ if (seen.has(model.id)) {
64
+ continue;
65
+ }
66
+ seen.add(model.id);
67
+ next.push(model);
68
+ }
69
+ return next;
70
+ }
71
+ async function getCodexModelCatalog() {
72
+ const cachePath = getCodexModelsCachePath();
73
+ try {
74
+ const raw = await fs.readFile(cachePath, "utf8");
75
+ const parsed = JSON.parse(raw);
76
+ const models = dedupeModels((parsed.models ?? []).map(normalizeCodexCacheEntry).filter(Boolean));
77
+ if (models.length > 0) {
78
+ return {
79
+ models,
80
+ catalog: {
81
+ source: "codex-cache",
82
+ cachePath,
83
+ fetchedAt: parsed.fetched_at,
84
+ modelCount: models.length
85
+ }
86
+ };
87
+ }
88
+ } catch {
89
+ }
90
+ return {
91
+ models: CODEX_MODEL_INFOS,
92
+ catalog: {
93
+ source: "static-fallback",
94
+ cachePath,
95
+ modelCount: CODEX_MODEL_INFOS.length
96
+ }
97
+ };
98
+ }
99
+ async function hasCodexModel(model) {
100
+ const { models } = await getCodexModelCatalog();
101
+ return models.some((item) => item.id === model);
102
+ }
103
+ async function getPreferredCodexModel() {
104
+ const { models } = await getCodexModelCatalog();
105
+ if (models.some((item) => item.id === DEFAULT_CODEX_MODEL)) {
106
+ return DEFAULT_CODEX_MODEL;
107
+ }
108
+ return models[0]?.id ?? DEFAULT_CODEX_MODEL;
109
+ }
25
110
  function isSupportedCodexModel(model) {
26
111
  return SUPPORTED_CODEX_MODELS.includes(model);
27
112
  }
@@ -29,6 +114,9 @@ export {
29
114
  CODEX_MODEL_INFOS,
30
115
  DEFAULT_CODEX_MODEL,
31
116
  SUPPORTED_CODEX_MODELS,
117
+ getCodexModelCatalog,
118
+ getCodexModelsCachePath,
119
+ getPreferredCodexModel,
120
+ hasCodexModel,
32
121
  isSupportedCodexModel
33
122
  };
34
- //# sourceMappingURL=openai-codex-models.js.map
@@ -1,7 +1,63 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from "node:child_process";
3
3
  const CURL_STATUS_MARKER = "\n__CURL_STATUS__:";
4
- async function runCurlRequest(init) {
4
+ const CURL_HEADERS_MARKER = "\n__CURL_HEADERS__:";
5
+ let requestSequence = 0;
6
+ function nextRequestId() {
7
+ requestSequence += 1;
8
+ return `http-${String(requestSequence).padStart(4, "0")}`;
9
+ }
10
+ function roundMs(value) {
11
+ return Math.round(value * 100) / 100;
12
+ }
13
+ function finalizeTiming(startedAt, phases) {
14
+ return {
15
+ phasesMs: Object.fromEntries(
16
+ Object.entries(phases).map(([key, value]) => [key, roundMs(value)])
17
+ ),
18
+ totalMs: roundMs(performance.now() - startedAt)
19
+ };
20
+ }
21
+ function logHttpTiming(params) {
22
+ console.info("[http] request timing", {
23
+ requestId: params.requestId,
24
+ method: params.method,
25
+ url: params.url,
26
+ transport: params.transport,
27
+ status: params.status,
28
+ bodyLength: params.bodyLength,
29
+ fallbackFrom: params.fallbackFrom,
30
+ phasesMs: params.timing.phasesMs,
31
+ totalMs: params.timing.totalMs
32
+ });
33
+ }
34
+ function normalizeHeaders(headers) {
35
+ const normalized = {};
36
+ headers.forEach((value, key) => {
37
+ normalized[key.toLowerCase()] = value;
38
+ });
39
+ return normalized;
40
+ }
41
+ function normalizeCurlHeaders(value) {
42
+ if (!value || typeof value !== "object") {
43
+ return {};
44
+ }
45
+ return Object.fromEntries(
46
+ Object.entries(value).flatMap(([key, rawValue]) => {
47
+ if (typeof rawValue === "string" && rawValue.trim()) {
48
+ return [[key.toLowerCase(), rawValue.trim()]];
49
+ }
50
+ if (Array.isArray(rawValue)) {
51
+ const joined = rawValue.filter((item) => typeof item === "string" && item.trim()).join(", ");
52
+ return joined ? [[key.toLowerCase(), joined]] : [];
53
+ }
54
+ return [];
55
+ })
56
+ );
57
+ }
58
+ async function runCurlRequest(init, params) {
59
+ const requestId = params?.requestId ?? nextRequestId();
60
+ const startedAt = performance.now();
5
61
  const args = [
6
62
  "--silent",
7
63
  "--show-error",
@@ -10,7 +66,7 @@ async function runCurlRequest(init) {
10
66
  init.method,
11
67
  init.url,
12
68
  "--write-out",
13
- `${CURL_STATUS_MARKER}%{http_code}`
69
+ `${CURL_STATUS_MARKER}%{http_code}${CURL_HEADERS_MARKER}%{header_json}`
14
70
  ];
15
71
  for (const [key, value] of Object.entries(init.headers ?? {})) {
16
72
  args.push("--header", `${key}: ${value}`);
@@ -22,6 +78,9 @@ async function runCurlRequest(init) {
22
78
  env: process.env,
23
79
  stdio: ["ignore", "pipe", "pipe"]
24
80
  });
81
+ const phases = {
82
+ spawnCurlMs: performance.now() - startedAt
83
+ };
25
84
  let stdout = "";
26
85
  let stderr = "";
27
86
  child.stdout.setEncoding("utf8");
@@ -36,49 +95,113 @@ async function runCurlRequest(init) {
36
95
  child.on("error", reject);
37
96
  child.on("close", (code) => resolve(code ?? 1));
38
97
  });
98
+ phases.waitForCurlMs = performance.now() - startedAt - phases.spawnCurlMs;
39
99
  if (exitCode !== 0) {
40
100
  throw new Error(stderr.trim() || `curl \u8BF7\u6C42\u5931\u8D25\uFF0C\u9000\u51FA\u7801 ${exitCode}`);
41
101
  }
42
- const markerIndex = stdout.lastIndexOf(CURL_STATUS_MARKER);
43
- if (markerIndex === -1) {
102
+ const parseStartedAt = performance.now();
103
+ const statusMarkerIndex = stdout.lastIndexOf(CURL_STATUS_MARKER);
104
+ const headersMarkerIndex = stdout.lastIndexOf(CURL_HEADERS_MARKER);
105
+ if (statusMarkerIndex === -1) {
44
106
  throw new Error("curl \u54CD\u5E94\u7F3A\u5C11\u72B6\u6001\u7801\u6807\u8BB0\u3002");
45
107
  }
46
- const body = stdout.slice(0, markerIndex);
47
- const statusText = stdout.slice(markerIndex + CURL_STATUS_MARKER.length).trim();
108
+ if (headersMarkerIndex === -1 || headersMarkerIndex < statusMarkerIndex) {
109
+ throw new Error("curl \u54CD\u5E94\u7F3A\u5C11\u54CD\u5E94\u5934\u6807\u8BB0\u3002");
110
+ }
111
+ const body = stdout.slice(0, statusMarkerIndex);
112
+ const statusText = stdout.slice(statusMarkerIndex + CURL_STATUS_MARKER.length, headersMarkerIndex).trim();
48
113
  const status = Number.parseInt(statusText, 10);
49
114
  if (!Number.isFinite(status)) {
50
115
  throw new Error(`\u65E0\u6CD5\u89E3\u6790 curl \u72B6\u6001\u7801: ${statusText}`);
51
116
  }
117
+ const headersText = stdout.slice(headersMarkerIndex + CURL_HEADERS_MARKER.length).trim();
118
+ let headers = {};
119
+ if (headersText) {
120
+ try {
121
+ headers = normalizeCurlHeaders(JSON.parse(headersText));
122
+ } catch (error) {
123
+ console.warn("[http] failed to parse curl response headers", {
124
+ requestId,
125
+ url: init.url,
126
+ error: error instanceof Error ? error.message : String(error)
127
+ });
128
+ }
129
+ }
130
+ phases.parseResponseMs = performance.now() - parseStartedAt;
131
+ const timing = finalizeTiming(startedAt, phases);
132
+ logHttpTiming({
133
+ requestId,
134
+ method: init.method,
135
+ url: init.url,
136
+ status,
137
+ transport: "curl",
138
+ timing,
139
+ bodyLength: body.length,
140
+ fallbackFrom: params?.fallbackFrom
141
+ });
52
142
  return {
53
143
  body,
54
144
  status,
55
- transport: "curl"
145
+ transport: "curl",
146
+ timing,
147
+ requestId,
148
+ headers
56
149
  };
57
150
  }
58
151
  async function requestText(init) {
152
+ const requestId = nextRequestId();
59
153
  const useCurlOnly = process.env.OAUTH_DEMO_USE_CURL === "1";
60
- const timeoutMs = init.timeoutMs ?? 2e4;
154
+ const timeoutMs = init.timeoutMs;
155
+ const signal = typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : void 0;
61
156
  if (!useCurlOnly) {
157
+ const startedAt = performance.now();
158
+ const phases = {};
62
159
  try {
160
+ const fetchStartedAt = performance.now();
63
161
  const response = await fetch(init.url, {
64
162
  method: init.method,
65
163
  headers: init.headers,
66
164
  body: init.body,
67
- signal: AbortSignal.timeout(timeoutMs)
165
+ signal
166
+ });
167
+ phases.waitForHeadersMs = performance.now() - fetchStartedAt;
168
+ const readBodyStartedAt = performance.now();
169
+ const body = await response.text();
170
+ phases.readBodyMs = performance.now() - readBodyStartedAt;
171
+ const timing = finalizeTiming(startedAt, phases);
172
+ logHttpTiming({
173
+ requestId,
174
+ method: init.method,
175
+ url: init.url,
176
+ status: response.status,
177
+ transport: "fetch",
178
+ timing,
179
+ bodyLength: body.length
68
180
  });
69
181
  return {
70
- body: await response.text(),
182
+ body,
71
183
  status: response.status,
72
- transport: "fetch"
184
+ transport: "fetch",
185
+ timing,
186
+ requestId,
187
+ headers: normalizeHeaders(response.headers)
73
188
  };
74
189
  } catch (error) {
75
190
  const message = error instanceof Error ? error.message : String(error);
76
- console.log(`fetch \u8BF7\u6C42\u5931\u8D25\uFF0C\u51C6\u5907\u56DE\u9000\u5230 curl: ${message}`);
191
+ console.warn("[http] fetch attempt failed", {
192
+ requestId,
193
+ method: init.method,
194
+ url: init.url,
195
+ elapsedMs: roundMs(performance.now() - startedAt),
196
+ error: message
197
+ });
77
198
  }
78
199
  }
79
- return runCurlRequest(init);
200
+ return runCurlRequest(init, {
201
+ requestId,
202
+ fallbackFrom: useCurlOnly ? void 0 : "fetch"
203
+ });
80
204
  }
81
205
  export {
82
206
  requestText
83
207
  };
84
- //# sourceMappingURL=http-client.js.map