codex-multi-auth 1.3.2 → 2.0.1

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 (132) hide show
  1. package/README.md +40 -26
  2. package/config/README.md +4 -4
  3. package/config/codex-legacy.json +571 -646
  4. package/config/codex-modern.json +258 -274
  5. package/config/minimal-codex.json +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +85 -14
  9. package/dist/index.js.map +1 -1
  10. package/dist/lib/auto-update-checker.d.ts +23 -1
  11. package/dist/lib/auto-update-checker.d.ts.map +1 -1
  12. package/dist/lib/auto-update-checker.js +481 -8
  13. package/dist/lib/auto-update-checker.js.map +1 -1
  14. package/dist/lib/cli.d.ts +5 -0
  15. package/dist/lib/cli.d.ts.map +1 -1
  16. package/dist/lib/cli.js.map +1 -1
  17. package/dist/lib/codex-manager/commands/best.js +1 -1
  18. package/dist/lib/codex-manager/commands/best.js.map +1 -1
  19. package/dist/lib/codex-manager/commands/forecast.js +3 -3
  20. package/dist/lib/codex-manager/commands/forecast.js.map +1 -1
  21. package/dist/lib/codex-manager/commands/report.js +3 -3
  22. package/dist/lib/codex-manager/commands/report.js.map +1 -1
  23. package/dist/lib/codex-manager/commands/rotation.d.ts +26 -0
  24. package/dist/lib/codex-manager/commands/rotation.d.ts.map +1 -0
  25. package/dist/lib/codex-manager/commands/rotation.js +313 -0
  26. package/dist/lib/codex-manager/commands/rotation.js.map +1 -0
  27. package/dist/lib/codex-manager/commands/status.d.ts +6 -0
  28. package/dist/lib/codex-manager/commands/status.d.ts.map +1 -1
  29. package/dist/lib/codex-manager/commands/status.js +64 -10
  30. package/dist/lib/codex-manager/commands/status.js.map +1 -1
  31. package/dist/lib/codex-manager/forecast-report-commands.js +4 -4
  32. package/dist/lib/codex-manager/forecast-report-commands.js.map +1 -1
  33. package/dist/lib/codex-manager/help.d.ts.map +1 -1
  34. package/dist/lib/codex-manager/help.js +3 -2
  35. package/dist/lib/codex-manager/help.js.map +1 -1
  36. package/dist/lib/codex-manager/rate-limit-markers.d.ts +2 -0
  37. package/dist/lib/codex-manager/rate-limit-markers.d.ts.map +1 -0
  38. package/dist/lib/codex-manager/rate-limit-markers.js +4 -0
  39. package/dist/lib/codex-manager/rate-limit-markers.js.map +1 -0
  40. package/dist/lib/codex-manager/repair-commands.js +3 -3
  41. package/dist/lib/codex-manager/repair-commands.js.map +1 -1
  42. package/dist/lib/codex-manager.d.ts.map +1 -1
  43. package/dist/lib/codex-manager.js +115 -87
  44. package/dist/lib/codex-manager.js.map +1 -1
  45. package/dist/lib/config.d.ts +1 -0
  46. package/dist/lib/config.d.ts.map +1 -1
  47. package/dist/lib/config.js +9 -0
  48. package/dist/lib/config.js.map +1 -1
  49. package/dist/lib/constants.d.ts +2 -0
  50. package/dist/lib/constants.d.ts.map +1 -1
  51. package/dist/lib/constants.js +2 -0
  52. package/dist/lib/constants.js.map +1 -1
  53. package/dist/lib/fs-retry.d.ts +7 -0
  54. package/dist/lib/fs-retry.d.ts.map +1 -0
  55. package/dist/lib/fs-retry.js +35 -0
  56. package/dist/lib/fs-retry.js.map +1 -0
  57. package/dist/lib/index.d.ts +1 -0
  58. package/dist/lib/index.d.ts.map +1 -1
  59. package/dist/lib/index.js +1 -0
  60. package/dist/lib/index.js.map +1 -1
  61. package/dist/lib/logger.d.ts.map +1 -1
  62. package/dist/lib/logger.js +1 -0
  63. package/dist/lib/logger.js.map +1 -1
  64. package/dist/lib/prompts/codex.d.ts +1 -1
  65. package/dist/lib/prompts/codex.d.ts.map +1 -1
  66. package/dist/lib/prompts/codex.js +3 -3
  67. package/dist/lib/prompts/codex.js.map +1 -1
  68. package/dist/lib/quota-probe.js +1 -1
  69. package/dist/lib/quota-probe.js.map +1 -1
  70. package/dist/lib/quota-readiness.d.ts +16 -0
  71. package/dist/lib/quota-readiness.d.ts.map +1 -0
  72. package/dist/lib/quota-readiness.js +82 -0
  73. package/dist/lib/quota-readiness.js.map +1 -0
  74. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  75. package/dist/lib/request/fetch-helpers.js +19 -8
  76. package/dist/lib/request/fetch-helpers.js.map +1 -1
  77. package/dist/lib/request/helpers/model-map.d.ts +2 -1
  78. package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
  79. package/dist/lib/request/helpers/model-map.js +52 -60
  80. package/dist/lib/request/helpers/model-map.js.map +1 -1
  81. package/dist/lib/request/request-transformer.d.ts +2 -2
  82. package/dist/lib/request/request-transformer.js +4 -3
  83. package/dist/lib/request/request-transformer.js.map +1 -1
  84. package/dist/lib/runtime/app-bind.d.ts +74 -0
  85. package/dist/lib/runtime/app-bind.d.ts.map +1 -0
  86. package/dist/lib/runtime/app-bind.js +620 -0
  87. package/dist/lib/runtime/app-bind.js.map +1 -0
  88. package/dist/lib/runtime/config-toml.d.ts +10 -0
  89. package/dist/lib/runtime/config-toml.d.ts.map +1 -0
  90. package/dist/lib/runtime/config-toml.js +128 -0
  91. package/dist/lib/runtime/config-toml.js.map +1 -0
  92. package/dist/lib/runtime/login-menu-accounts.d.ts +5 -0
  93. package/dist/lib/runtime/login-menu-accounts.d.ts.map +1 -1
  94. package/dist/lib/runtime/login-menu-accounts.js +7 -2
  95. package/dist/lib/runtime/login-menu-accounts.js.map +1 -1
  96. package/dist/lib/runtime/quota-probe.js +1 -1
  97. package/dist/lib/runtime/quota-probe.js.map +1 -1
  98. package/dist/lib/runtime/runtime-current-account.d.ts +52 -0
  99. package/dist/lib/runtime/runtime-current-account.d.ts.map +1 -0
  100. package/dist/lib/runtime/runtime-current-account.js +252 -0
  101. package/dist/lib/runtime/runtime-current-account.js.map +1 -0
  102. package/dist/lib/runtime/runtime-observability.d.ts +5 -0
  103. package/dist/lib/runtime/runtime-observability.d.ts.map +1 -1
  104. package/dist/lib/runtime/runtime-observability.js +5 -0
  105. package/dist/lib/runtime/runtime-observability.js.map +1 -1
  106. package/dist/lib/runtime-constants.d.ts +3 -0
  107. package/dist/lib/runtime-constants.d.ts.map +1 -0
  108. package/dist/lib/runtime-constants.js +3 -0
  109. package/dist/lib/runtime-constants.js.map +1 -0
  110. package/dist/lib/runtime-rotation-proxy.d.ts +36 -0
  111. package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -0
  112. package/dist/lib/runtime-rotation-proxy.js +828 -0
  113. package/dist/lib/runtime-rotation-proxy.js.map +1 -0
  114. package/dist/lib/schemas.d.ts +1 -0
  115. package/dist/lib/schemas.d.ts.map +1 -1
  116. package/dist/lib/schemas.js +1 -0
  117. package/dist/lib/schemas.js.map +1 -1
  118. package/dist/lib/ui/auth-menu.d.ts +6 -1
  119. package/dist/lib/ui/auth-menu.d.ts.map +1 -1
  120. package/dist/lib/ui/auth-menu.js +22 -2
  121. package/dist/lib/ui/auth-menu.js.map +1 -1
  122. package/package.json +13 -11
  123. package/scripts/bench-format/codex-host.mjs +142 -73
  124. package/scripts/bench-format/models.mjs +23 -24
  125. package/scripts/benchmark-edit-formats.mjs +9 -7
  126. package/scripts/check-pack-budget-lib.js +49 -6
  127. package/scripts/codex-app-launcher.js +653 -0
  128. package/scripts/codex-app-router.js +322 -0
  129. package/scripts/codex-multi-auth.js +3 -1
  130. package/scripts/codex-routing.js +6 -0
  131. package/scripts/codex.js +2428 -445
  132. package/scripts/postinstall.js +343 -0
@@ -1,20 +1,39 @@
1
- import { spawnSync } from "node:child_process";
2
- import { dirname, resolve } from "node:path";
3
- import { fileURLToPath } from "node:url";
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
4
5
 
5
6
  const scriptDir = dirname(fileURLToPath(import.meta.url));
6
7
  const repoRoot = resolve(scriptDir, "..", "..");
7
8
 
8
- export function getRepoRoot() {
9
- return repoRoot;
10
- }
11
-
12
- export function resolveCodexExecutable() {
13
- const envOverride = process.env.CODEX_BIN;
14
- if (envOverride && envOverride.trim().length > 0) {
15
- const command = envOverride.trim();
16
- return { command, shell: /\.cmd$/i.test(command) };
17
- }
9
+ export function getRepoRoot() {
10
+ return repoRoot;
11
+ }
12
+
13
+ function resolveWindowsNpmShim(command) {
14
+ if (!/\.(cmd|bat)$/i.test(command)) {
15
+ return { command, shell: false };
16
+ }
17
+
18
+ const codexScript = join(dirname(command), "node_modules", "codex-multi-auth", "scripts", "codex.js");
19
+ if (existsSync(codexScript)) {
20
+ return {
21
+ command: process.execPath,
22
+ shell: false,
23
+ argsPrefix: [codexScript],
24
+ shimCommand: command,
25
+ };
26
+ }
27
+
28
+ return { command, shell: true };
29
+ }
30
+
31
+ export function resolveCodexExecutable() {
32
+ const envOverride = process.env.CODEX_BIN;
33
+ if (envOverride && envOverride.trim().length > 0) {
34
+ const command = envOverride.trim();
35
+ return process.platform === "win32" ? resolveWindowsNpmShim(command) : { command, shell: false };
36
+ }
18
37
 
19
38
  if (process.platform !== "win32") {
20
39
  return { command: "codex", shell: false };
@@ -38,15 +57,15 @@ export function resolveCodexExecutable() {
38
57
  return { command: exactExe, shell: false };
39
58
  }
40
59
 
41
- const exactCmd = candidates.find((candidate) => /npm\\Codex\.cmd$/i.test(candidate));
42
- if (exactCmd) {
43
- return { command: exactCmd, shell: true };
44
- }
45
-
46
- const anyCmd = candidates.find((candidate) => /\.cmd$/i.test(candidate));
47
- if (anyCmd) {
48
- return { command: anyCmd, shell: true };
49
- }
60
+ const exactCmd = candidates.find((candidate) => /npm\\Codex\.cmd$/i.test(candidate));
61
+ if (exactCmd) {
62
+ return resolveWindowsNpmShim(exactCmd);
63
+ }
64
+
65
+ const anyCmd = candidates.find((candidate) => /\.cmd$/i.test(candidate));
66
+ if (anyCmd) {
67
+ return resolveWindowsNpmShim(anyCmd);
68
+ }
50
69
 
51
70
  return { command: candidates[0], shell: false };
52
71
  }
@@ -67,23 +86,35 @@ export function parseNdjson(text) {
67
86
  return events;
68
87
  }
69
88
 
70
- export function getToolEvents(events) {
71
- return events
72
- .filter((event) => event?.type === "tool_use" && event?.part?.type === "tool")
73
- .map((event) => ({
74
- tool: event.part.tool,
75
- input: event.part.state?.input ?? {},
76
- output: event.part.state?.output,
89
+ export function getToolEvents(events) {
90
+ const legacyToolEvents = events
91
+ .filter((event) => event?.type === "tool_use" && event?.part?.type === "tool")
92
+ .map((event) => ({
93
+ tool: event.part.tool,
94
+ input: event.part.state?.input ?? {},
95
+ output: event.part.state?.output,
77
96
  status: event.part.state?.status,
78
97
  start: event.part.state?.time?.start,
79
98
  end: event.part.state?.time?.end,
80
99
  durationMs:
81
100
  typeof event.part.state?.time?.start === "number" &&
82
101
  typeof event.part.state?.time?.end === "number"
83
- ? event.part.state.time.end - event.part.state.time.start
84
- : null,
85
- }));
86
- }
102
+ ? event.part.state.time.end - event.part.state.time.start
103
+ : null,
104
+ }));
105
+ const execToolEvents = events
106
+ .filter((event) => event?.type === "item.completed" && event?.item?.type === "command_execution")
107
+ .map((event) => ({
108
+ tool: "command_execution",
109
+ input: { command: event.item.command },
110
+ output: event.item.aggregated_output,
111
+ status: event.item.status,
112
+ start: null,
113
+ end: null,
114
+ durationMs: null,
115
+ }));
116
+ return [...legacyToolEvents, ...execToolEvents];
117
+ }
87
118
 
88
119
  export function getSessionDuration(events) {
89
120
  const starts = events
@@ -98,11 +129,12 @@ export function getSessionDuration(events) {
98
129
  return Math.max(...finishes) - Math.min(...starts);
99
130
  }
100
131
 
101
- export function getTokenTotals(events) {
102
- const stepFinishes = events.filter((event) => event?.type === "step_finish" && event?.part?.tokens);
103
- if (stepFinishes.length === 0) {
104
- return null;
105
- }
132
+ export function getTokenTotals(events) {
133
+ const stepFinishes = events.filter((event) => event?.type === "step_finish" && event?.part?.tokens);
134
+ const turnCompletions = events.filter((event) => event?.type === "turn.completed" && event?.usage);
135
+ if (stepFinishes.length === 0 && turnCompletions.length === 0) {
136
+ return null;
137
+ }
106
138
  const total = {
107
139
  total: 0,
108
140
  input: 0,
@@ -121,29 +153,49 @@ export function getTokenTotals(events) {
121
153
  total.input += input;
122
154
  total.output += output;
123
155
  total.reasoning += reasoning;
124
- total.cacheRead += Number(tokens.cache?.read ?? 0);
125
- total.cacheWrite += Number(tokens.cache?.write ?? 0);
126
- }
127
- return total;
128
- }
156
+ total.cacheRead += Number(tokens.cache?.read ?? 0);
157
+ total.cacheWrite += Number(tokens.cache?.write ?? 0);
158
+ }
159
+ for (const event of turnCompletions) {
160
+ const usage = event.usage ?? {};
161
+ const input = Number(usage.input_tokens ?? 0);
162
+ const output = Number(usage.output_tokens ?? 0);
163
+ const reasoning = Number(usage.reasoning_output_tokens ?? 0);
164
+ total.total += input + output;
165
+ total.input += input;
166
+ total.output += output;
167
+ total.reasoning += reasoning;
168
+ total.cacheRead += Number(usage.cached_input_tokens ?? 0);
169
+ }
170
+ return total;
171
+ }
129
172
 
130
- export function getTextOutput(events) {
131
- return events
132
- .filter((event) => event?.type === "text" && typeof event?.part?.text === "string")
133
- .map((event) => event.part.text)
134
- .join("\n");
135
- }
136
-
137
- export function getEventError(events) {
138
- const errorEvent = events.find((event) => event?.type === "error");
139
- if (!errorEvent) {
140
- return null;
141
- }
142
- return {
143
- name: errorEvent.error?.name ?? "UnknownError",
144
- message: errorEvent.error?.data?.message ?? errorEvent.error?.message ?? "Unknown error",
145
- };
146
- }
173
+ export function getTextOutput(events) {
174
+ const legacyText = events
175
+ .filter((event) => event?.type === "text" && typeof event?.part?.text === "string")
176
+ .map((event) => event.part.text)
177
+ .join("\n");
178
+ const execText = events
179
+ .filter((event) => event?.type === "item.completed" && event?.item?.type === "agent_message" && typeof event.item.text === "string")
180
+ .map((event) => event.item.text)
181
+ .join("\n");
182
+ return [legacyText, execText].filter(Boolean).join("\n");
183
+ }
184
+
185
+ export function getEventError(events) {
186
+ const lastCompletedIndex = events.findLastIndex((event) => event?.type === "turn.completed");
187
+ const failureIndex = events.findLastIndex(
188
+ (event, index) => index > lastCompletedIndex && (event?.type === "error" || event?.type === "turn.failed"),
189
+ );
190
+ if (failureIndex < 0) {
191
+ return null;
192
+ }
193
+ const errorEvent = events[failureIndex];
194
+ return {
195
+ name: errorEvent.error?.name ?? errorEvent.type ?? "UnknownError",
196
+ message: errorEvent.error?.data?.message ?? errorEvent.error?.message ?? errorEvent.message ?? "Unknown error",
197
+ };
198
+ }
147
199
 
148
200
  export function runCodexJson({
149
201
  executable,
@@ -155,20 +207,33 @@ export function runCodexJson({
155
207
  homeDir,
156
208
  timeoutMs,
157
209
  extraEnv,
158
- }) {
159
- const startWall = Date.now();
160
- const args = ["run", "--format", "json", "--agent", agent, "--model", model];
161
- if (variant) {
162
- args.push("--variant", variant);
163
- }
164
- args.push(prompt);
165
-
166
- const child = spawnSync(executable.command, args, {
210
+ }) {
211
+ const startWall = Date.now();
212
+ const args = [
213
+ "exec",
214
+ "--json",
215
+ "--model",
216
+ model,
217
+ "--skip-git-repo-check",
218
+ "--sandbox",
219
+ "danger-full-access",
220
+ "-c",
221
+ "approval_policy='never'",
222
+ ];
223
+ if (variant) {
224
+ args.push("-c", `model_reasoning_effort='${variant}'`);
225
+ }
226
+ void agent;
227
+ args.push(prompt);
228
+ const childArgs = [...(executable.argsPrefix ?? []), ...args];
229
+
230
+ const child = spawnSync(executable.command, childArgs, {
167
231
  cwd: cwd ?? repoRoot,
168
232
  encoding: "utf8",
169
- windowsHide: true,
170
- shell: executable.shell,
171
- timeout: timeoutMs,
233
+ windowsHide: true,
234
+ shell: executable.shell,
235
+ stdio: ["ignore", "pipe", "pipe"],
236
+ timeout: timeoutMs,
172
237
  maxBuffer: 30 * 1024 * 1024,
173
238
  env: {
174
239
  ...process.env,
@@ -189,7 +254,11 @@ export function runCodexJson({
189
254
  if (child.error && !timedOut) {
190
255
  throw child.error;
191
256
  }
192
- const modelNotFound = /Model not found|ProviderModelNotFoundError/i.test(`${stdout}\n${stderr}`) || /Model not found/i.test(eventError?.message ?? "");
257
+ const failed = (child.status ?? 1) !== 0 || eventError !== null;
258
+ const modelNotFound =
259
+ failed &&
260
+ (/Model not found|ProviderModelNotFoundError|model is not supported|not supported when using Codex/i.test(`${stdout}\n${stderr}`) ||
261
+ /Model not found|model is not supported|not supported when using Codex/i.test(eventError?.message ?? ""));
193
262
 
194
263
  return {
195
264
  status: child.status ?? 1,
@@ -1,14 +1,12 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { resolveCodexExecutable } from "./codex-host.mjs";
3
3
 
4
- const FALLBACK_OPENAI_CODEX_STABLE = [
5
- "openai/gpt-5-codex",
6
- "openai/gpt-5.1-codex",
7
- "openai/gpt-5.1-codex-mini",
8
- "openai/gpt-5.1-codex-max",
9
- "openai/gpt-5.2-codex",
10
- "openai/gpt-5.3-codex",
11
- ];
4
+ const FALLBACK_OPENAI_CODEX_STABLE = [
5
+ "openai/gpt-5.3-codex",
6
+ "openai/gpt-5.4",
7
+ "openai/gpt-5.4-mini",
8
+ "openai/gpt-5.5",
9
+ ];
12
10
 
13
11
  const OPENAI_CODEX_PREFIXES = ["openai/", "openai-multi/"];
14
12
 
@@ -25,14 +23,14 @@ export function canonicalCodexModelId(modelId) {
25
23
  return `openai/${codexTail(modelId)}`;
26
24
  }
27
25
 
28
- export function aliasCandidatesForCodexModel(modelId) {
29
- if (!isOpenAiCodexProviderPrefix(modelId)) {
30
- return [modelId];
31
- }
32
- const tail = codexTail(modelId);
33
- const candidates = [`openai/${tail}`, `openai-multi/${tail}`];
34
- return [...new Set([modelId, ...candidates])];
35
- }
26
+ export function aliasCandidatesForCodexModel(modelId) {
27
+ if (!isOpenAiCodexProviderPrefix(modelId)) {
28
+ return [modelId];
29
+ }
30
+ const tail = codexTail(modelId);
31
+ const candidates = [`openai/${tail}`, `openai-multi/${tail}`];
32
+ return [...new Set([tail, modelId, ...candidates])];
33
+ }
36
34
 
37
35
  export function isStableOpenAiCodexModel(modelId) {
38
36
  if (!isOpenAiCodexProviderPrefix(modelId)) {
@@ -47,14 +45,15 @@ export function isStableOpenAiCodexModel(modelId) {
47
45
  return true;
48
46
  }
49
47
 
50
- export function listCodexModels() {
51
- const executable = resolveCodexExecutable();
52
- const child = spawnSync(executable.command, ["models"], {
53
- encoding: "utf8",
54
- windowsHide: true,
55
- shell: executable.shell,
56
- maxBuffer: 10 * 1024 * 1024,
57
- timeout: Number.parseInt(process.env.CODEX_MODELS_TIMEOUT_MS ?? "30000", 10),
48
+ export function listCodexModels() {
49
+ const executable = resolveCodexExecutable();
50
+ const child = spawnSync(executable.command, [...(executable.argsPrefix ?? []), "models"], {
51
+ encoding: "utf8",
52
+ windowsHide: true,
53
+ shell: executable.shell,
54
+ stdio: ["ignore", "pipe", "pipe"],
55
+ maxBuffer: 10 * 1024 * 1024,
56
+ timeout: Number.parseInt(process.env.CODEX_MODELS_TIMEOUT_MS ?? "30000", 10),
58
57
  killSignal: "SIGKILL",
59
58
  });
60
59
  if (child.error && child.error.code === "ETIMEDOUT") {
@@ -63,7 +63,7 @@ function printUsage() {
63
63
  " --help Show this help",
64
64
  "",
65
65
  "Examples:",
66
- " node scripts/benchmark-edit-formats.mjs --smoke --models=openai/gpt-5-codex",
66
+ " node scripts/benchmark-edit-formats.mjs --smoke --models=openai/gpt-5.3-codex",
67
67
  " node scripts/benchmark-edit-formats.mjs --preset=codex-core --warmup-runs=1 --measured-runs=5",
68
68
  ].join("\n"));
69
69
  }
@@ -588,12 +588,14 @@ function aggregateSummary({ options, runRecords, failures, startTime, endTime, e
588
588
  warmupRunsPerTask: options.warmupRuns,
589
589
  measuredRunsPerTask: options.measuredRuns,
590
590
  timeoutMs: options.timeoutMs,
591
- outputRoot: options.outputRoot,
592
- runDir: options.runDir,
593
- homeDir: options.homeDir || null,
594
- CodexCommand: executable.command,
595
- CodexUsesShell: executable.shell,
596
- keepRawLogs: options.keepRawLogs,
591
+ outputRoot: options.outputRoot,
592
+ runDir: options.runDir,
593
+ homeDir: options.homeDir || null,
594
+ CodexCommand: executable.command,
595
+ CodexArgsPrefix: executable.argsPrefix ?? [],
596
+ CodexShimCommand: executable.shimCommand ?? null,
597
+ CodexUsesShell: executable.shell,
598
+ keepRawLogs: options.keepRawLogs,
597
599
  runCount: measuredRuns.length,
598
600
  warmupCount: warmupRuns.length,
599
601
  },
@@ -1,6 +1,15 @@
1
1
  import { exec } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
3
 
4
+ /**
5
+ * @typedef {{ packageSize: number, paths: string[] }} ParsedPackMetadata
6
+ * @typedef {{ windowsHide: boolean, maxBuffer: number }} ExecOptions
7
+ * @typedef {{ stdout: string | Buffer, stderr?: string | Buffer }} ExecResult
8
+ * @typedef {(command: string, options: ExecOptions) => Promise<ExecResult>} ExecAsync
9
+ * @typedef {{ execAsync?: ExecAsync, log?: (message: string) => void }} RunPackBudgetDeps
10
+ */
11
+
12
+ /** @type {ExecAsync} */
4
13
  const execAsync = promisify(exec);
5
14
 
6
15
  export const MAX_PACKAGE_SIZE = 8 * 1024 * 1024;
@@ -25,18 +34,35 @@ export const FORBIDDEN_PREFIXES = [
25
34
  ".codex/",
26
35
  ];
27
36
 
37
+ /**
38
+ * @param {unknown} value
39
+ * @returns {value is Record<string, unknown>}
40
+ */
41
+ function isRecord(value) {
42
+ return typeof value === "object" && value !== null && !Array.isArray(value);
43
+ }
44
+
45
+ /**
46
+ * @param {string} filePath
47
+ * @returns {string}
48
+ */
28
49
  export function normalizePackPath(filePath) {
29
50
  return filePath.replaceAll("\\", "/");
30
51
  }
31
52
 
53
+ /**
54
+ * @param {string} stdout
55
+ * @returns {ParsedPackMetadata}
56
+ */
32
57
  export function parsePackMetadata(stdout) {
58
+ /** @type {unknown} */
33
59
  const packs = JSON.parse(stdout);
34
60
  if (!Array.isArray(packs) || packs.length === 0) {
35
61
  throw new Error("npm pack --dry-run --json returned no package metadata");
36
62
  }
37
63
 
38
64
  const pack = packs[0];
39
- if (!pack || !Array.isArray(pack.files)) {
65
+ if (!isRecord(pack) || !Array.isArray(pack.files)) {
40
66
  throw new Error("npm pack metadata did not include file list");
41
67
  }
42
68
 
@@ -46,13 +72,17 @@ export function parsePackMetadata(stdout) {
46
72
  }
47
73
 
48
74
  const paths = pack.files
49
- .map((file) => file?.path)
75
+ .map((file) => (isRecord(file) ? file.path : undefined))
50
76
  .filter((value) => typeof value === "string")
51
77
  .map((value) => normalizePackPath(value));
52
78
 
53
79
  return { packageSize, paths };
54
80
  }
55
81
 
82
+ /**
83
+ * @param {ParsedPackMetadata} metadata
84
+ * @returns {string}
85
+ */
56
86
  export function validatePackMetadata({ packageSize, paths }) {
57
87
  if (packageSize > MAX_PACKAGE_SIZE) {
58
88
  throw new Error(
@@ -83,20 +113,33 @@ export function validatePackMetadata({ packageSize, paths }) {
83
113
  return `Pack budget ok: ${packageSize} bytes across ${paths.length} files`;
84
114
  }
85
115
 
116
+ /**
117
+ * @param {RunPackBudgetDeps} [deps]
118
+ * @returns {Promise<string>}
119
+ */
86
120
  export async function runPackBudgetCheck(deps = {}) {
87
121
  const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
88
122
  const runExec = deps.execAsync ?? execAsync;
89
123
  const log = deps.log ?? console.log;
90
124
  let stdout = "";
91
125
  try {
92
- ({ stdout } = await runExec(`${npmCommand} pack --dry-run --json`, {
126
+ const result = await runExec(`${npmCommand} pack --dry-run --json`, {
93
127
  windowsHide: true,
94
128
  maxBuffer: 10 * 1024 * 1024,
95
- }));
129
+ });
130
+ if (result.stdout === null || result.stdout === undefined) {
131
+ throw new Error("npm pack --dry-run --json returned no stdout");
132
+ }
133
+ stdout =
134
+ typeof result.stdout === "string"
135
+ ? result.stdout
136
+ : result.stdout.toString("utf8");
96
137
  } catch (error) {
97
138
  const message = error instanceof Error ? error.message : String(error);
98
- const stdoutText = typeof error === "object" && error && "stdout" in error ? String(error.stdout ?? "") : "";
99
- const stderrText = typeof error === "object" && error && "stderr" in error ? String(error.stderr ?? "") : "";
139
+ const stdoutText =
140
+ isRecord(error) && "stdout" in error ? String(error.stdout ?? "") : "";
141
+ const stderrText =
142
+ isRecord(error) && "stderr" in error ? String(error.stderr ?? "") : "";
100
143
  throw new Error(`npm pack --dry-run --json failed via ${npmCommand}: ${message}${stdoutText ? `
101
144
  stdout: ${stdoutText.slice(0, 500)}` : ""}${stderrText ? `
102
145
  stderr: ${stderrText.slice(0, 500)}` : ""}`);