codex-multi-auth 0.1.0 → 0.1.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 (114) hide show
  1. package/README.md +222 -98
  2. package/assets/readme-hero.svg +2 -2
  3. package/config/README.md +30 -87
  4. package/config/{opencode-legacy.json → codex-legacy.json} +571 -571
  5. package/config/{opencode-modern.json → codex-modern.json} +241 -239
  6. package/config/{minimal-opencode.json → minimal-codex.json} +15 -13
  7. package/config/schema/config.schema.json +21 -0
  8. package/dist/index.d.ts +5 -5
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +23 -20
  11. package/dist/index.js.map +1 -1
  12. package/dist/lib/accounts.d.ts +1 -1
  13. package/dist/lib/accounts.d.ts.map +1 -1
  14. package/dist/lib/auth/auth.d.ts +1 -1
  15. package/dist/lib/auth/auth.js +1 -1
  16. package/dist/lib/cli.d.ts +1 -1
  17. package/dist/lib/cli.js +4 -4
  18. package/dist/lib/cli.js.map +1 -1
  19. package/dist/lib/codex-manager/settings-hub.d.ts +6 -0
  20. package/dist/lib/codex-manager/settings-hub.d.ts.map +1 -0
  21. package/dist/lib/codex-manager/settings-hub.js +1786 -0
  22. package/dist/lib/codex-manager/settings-hub.js.map +1 -0
  23. package/dist/lib/codex-manager.d.ts.map +1 -1
  24. package/dist/lib/codex-manager.js +5 -1637
  25. package/dist/lib/codex-manager.js.map +1 -1
  26. package/dist/lib/config.d.ts.map +1 -1
  27. package/dist/lib/config.js +20 -11
  28. package/dist/lib/config.js.map +1 -1
  29. package/dist/lib/context-overflow.d.ts +1 -1
  30. package/dist/lib/context-overflow.js +1 -1
  31. package/dist/lib/oauth-success.html +1 -1
  32. package/dist/lib/prompts/codex-host-bridge.d.ts +19 -0
  33. package/dist/lib/prompts/codex-host-bridge.d.ts.map +1 -0
  34. package/dist/lib/prompts/{codex-opencode-bridge.js → codex-host-bridge.js} +143 -143
  35. package/dist/lib/prompts/codex-host-bridge.js.map +1 -0
  36. package/dist/lib/prompts/codex.d.ts +2 -2
  37. package/dist/lib/prompts/codex.d.ts.map +1 -1
  38. package/dist/lib/prompts/codex.js +3 -3
  39. package/dist/lib/prompts/host-codex-prompt.d.ts +25 -0
  40. package/dist/lib/prompts/host-codex-prompt.d.ts.map +1 -0
  41. package/dist/lib/prompts/{opencode-codex.js → host-codex-prompt.js} +136 -47
  42. package/dist/lib/prompts/host-codex-prompt.js.map +1 -0
  43. package/dist/lib/recovery/storage.d.ts +2 -2
  44. package/dist/lib/recovery/storage.js +2 -2
  45. package/dist/lib/recovery/types.d.ts +1 -1
  46. package/dist/lib/recovery/types.js +1 -1
  47. package/dist/lib/recovery.d.ts +1 -1
  48. package/dist/lib/recovery.d.ts.map +1 -1
  49. package/dist/lib/recovery.js +1 -4
  50. package/dist/lib/recovery.js.map +1 -1
  51. package/dist/lib/request/fetch-helpers.d.ts +3 -3
  52. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  53. package/dist/lib/request/fetch-helpers.js +6 -2
  54. package/dist/lib/request/fetch-helpers.js.map +1 -1
  55. package/dist/lib/request/helpers/input-utils.d.ts +2 -2
  56. package/dist/lib/request/helpers/input-utils.d.ts.map +1 -1
  57. package/dist/lib/request/helpers/input-utils.js +14 -14
  58. package/dist/lib/request/helpers/input-utils.js.map +1 -1
  59. package/dist/lib/request/helpers/model-map.d.ts +2 -2
  60. package/dist/lib/request/helpers/model-map.js +2 -2
  61. package/dist/lib/request/request-transformer.d.ts +12 -12
  62. package/dist/lib/request/request-transformer.d.ts.map +1 -1
  63. package/dist/lib/request/request-transformer.js +23 -24
  64. package/dist/lib/request/request-transformer.js.map +1 -1
  65. package/dist/lib/runtime-paths.d.ts +4 -4
  66. package/dist/lib/runtime-paths.d.ts.map +1 -1
  67. package/dist/lib/runtime-paths.js +27 -9
  68. package/dist/lib/runtime-paths.js.map +1 -1
  69. package/dist/lib/storage/paths.d.ts +11 -0
  70. package/dist/lib/storage/paths.d.ts.map +1 -1
  71. package/dist/lib/storage/paths.js +146 -2
  72. package/dist/lib/storage/paths.js.map +1 -1
  73. package/dist/lib/storage.d.ts +1 -0
  74. package/dist/lib/storage.d.ts.map +1 -1
  75. package/dist/lib/storage.js +106 -32
  76. package/dist/lib/storage.js.map +1 -1
  77. package/dist/lib/tools/hashline-tools.d.ts +1 -1
  78. package/dist/lib/tools/hashline-tools.d.ts.map +1 -1
  79. package/dist/lib/tools/hashline-tools.js +1 -1
  80. package/dist/lib/tools/hashline-tools.js.map +1 -1
  81. package/dist/lib/types.d.ts +1 -1
  82. package/dist/lib/types.d.ts.map +1 -1
  83. package/dist/lib/ui/copy.d.ts +6 -6
  84. package/dist/lib/ui/copy.js +6 -6
  85. package/dist/lib/ui/copy.js.map +1 -1
  86. package/node_modules/@codex-ai/plugin/dist/index.d.ts +2 -0
  87. package/node_modules/@codex-ai/plugin/dist/index.js +2 -0
  88. package/node_modules/@codex-ai/plugin/dist/tool.d.ts +42 -0
  89. package/node_modules/@codex-ai/plugin/dist/tool.js +29 -0
  90. package/node_modules/@codex-ai/plugin/package.json +9 -0
  91. package/package.json +30 -16
  92. package/scripts/bench-format/{opencode.mjs → codex-host.mjs} +206 -205
  93. package/scripts/bench-format/models.mjs +111 -105
  94. package/scripts/benchmark-edit-formats.mjs +1162 -1161
  95. package/scripts/codex.js +40 -2
  96. package/scripts/install-codex-auth-utils.js +49 -0
  97. package/scripts/{install-opencode-codex-auth.js → install-codex-auth.js} +220 -193
  98. package/scripts/repo-hygiene.js +320 -0
  99. package/scripts/test-model-matrix.js +475 -423
  100. package/vendor/codex-ai-plugin/dist/index.d.ts +2 -0
  101. package/vendor/codex-ai-plugin/dist/index.js +2 -0
  102. package/vendor/codex-ai-plugin/dist/tool.d.ts +42 -0
  103. package/vendor/codex-ai-plugin/dist/tool.js +29 -0
  104. package/vendor/codex-ai-plugin/package.json +9 -0
  105. package/vendor/codex-ai-sdk/dist/index.d.ts +4 -0
  106. package/vendor/codex-ai-sdk/dist/index.js +2 -0
  107. package/vendor/codex-ai-sdk/package.json +8 -0
  108. package/assets/opencode-logo-ornate-dark.svg +0 -18
  109. package/dist/lib/prompts/codex-opencode-bridge.d.ts +0 -19
  110. package/dist/lib/prompts/codex-opencode-bridge.d.ts.map +0 -1
  111. package/dist/lib/prompts/codex-opencode-bridge.js.map +0 -1
  112. package/dist/lib/prompts/opencode-codex.d.ts +0 -25
  113. package/dist/lib/prompts/opencode-codex.d.ts.map +0 -1
  114. package/dist/lib/prompts/opencode-codex.js.map +0 -1
@@ -1,205 +1,206 @@
1
- import { spawnSync } from "node:child_process";
2
- import { dirname, resolve } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- const scriptDir = dirname(fileURLToPath(import.meta.url));
6
- const repoRoot = resolve(scriptDir, "..", "..");
7
-
8
- export function getRepoRoot() {
9
- return repoRoot;
10
- }
11
-
12
- export function resolveOpencodeExecutable() {
13
- const envOverride = process.env.OPENCODE_BIN;
14
- if (envOverride && envOverride.trim().length > 0) {
15
- const command = envOverride.trim();
16
- return { command, shell: /\.cmd$/i.test(command) };
17
- }
18
-
19
- if (process.platform !== "win32") {
20
- return { command: "opencode", shell: false };
21
- }
22
-
23
- const whereResult = spawnSync("where", ["opencode"], {
24
- encoding: "utf8",
25
- windowsHide: true,
26
- });
27
- const candidates = `${whereResult.stdout ?? ""}\n${whereResult.stderr ?? ""}`
28
- .split(/\r?\n/)
29
- .map((line) => line.trim())
30
- .filter(Boolean);
31
-
32
- if (candidates.length === 0) {
33
- return { command: "opencode", shell: false };
34
- }
35
-
36
- const exactExe = candidates.find((candidate) => /npm\\opencode\.exe$/i.test(candidate));
37
- if (exactExe) {
38
- return { command: exactExe, shell: false };
39
- }
40
-
41
- const exactCmd = candidates.find((candidate) => /npm\\opencode\.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
- }
50
-
51
- return { command: candidates[0], shell: false };
52
- }
53
-
54
- export function parseNdjson(text) {
55
- const events = [];
56
- for (const line of text.split(/\r?\n/)) {
57
- const trimmed = line.trim();
58
- if (!trimmed) {
59
- continue;
60
- }
61
- try {
62
- events.push(JSON.parse(trimmed));
63
- } catch {
64
- // Ignore non-JSON lines emitted by wrappers (bun install, warnings, etc.).
65
- }
66
- }
67
- return events;
68
- }
69
-
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,
77
- status: event.part.state?.status,
78
- start: event.part.state?.time?.start,
79
- end: event.part.state?.time?.end,
80
- durationMs:
81
- typeof event.part.state?.time?.start === "number" &&
82
- typeof event.part.state?.time?.end === "number"
83
- ? event.part.state.time.end - event.part.state.time.start
84
- : null,
85
- }));
86
- }
87
-
88
- export function getSessionDuration(events) {
89
- const starts = events
90
- .filter((event) => event?.type === "step_start" && typeof event.timestamp === "number")
91
- .map((event) => event.timestamp);
92
- const finishes = events
93
- .filter((event) => event?.type === "step_finish" && typeof event.timestamp === "number")
94
- .map((event) => event.timestamp);
95
- if (starts.length === 0 || finishes.length === 0) {
96
- return null;
97
- }
98
- return Math.max(...finishes) - Math.min(...starts);
99
- }
100
-
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
- }
106
- const total = {
107
- total: 0,
108
- input: 0,
109
- output: 0,
110
- reasoning: 0,
111
- cacheRead: 0,
112
- cacheWrite: 0,
113
- };
114
- for (const event of stepFinishes) {
115
- const tokens = event.part.tokens ?? {};
116
- const input = Number(tokens.input ?? 0);
117
- const output = Number(tokens.output ?? 0);
118
- const reasoning = Number(tokens.reasoning ?? 0);
119
- const explicitTotal = Number(tokens.total ?? NaN);
120
- total.total += Number.isFinite(explicitTotal) ? explicitTotal : input + output + reasoning;
121
- total.input += input;
122
- total.output += output;
123
- total.reasoning += reasoning;
124
- total.cacheRead += Number(tokens.cache?.read ?? 0);
125
- total.cacheWrite += Number(tokens.cache?.write ?? 0);
126
- }
127
- return total;
128
- }
129
-
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
- }
147
-
148
- export function runOpencodeJson({
149
- executable,
150
- prompt,
151
- model,
152
- variant,
153
- agent,
154
- cwd,
155
- homeDir,
156
- timeoutMs,
157
- 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, {
167
- cwd: cwd ?? repoRoot,
168
- encoding: "utf8",
169
- windowsHide: true,
170
- shell: executable.shell,
171
- timeout: timeoutMs,
172
- maxBuffer: 30 * 1024 * 1024,
173
- env: {
174
- ...process.env,
175
- ...(homeDir ? { HOME: homeDir, USERPROFILE: homeDir } : {}),
176
- ...extraEnv,
177
- },
178
- });
179
-
180
- const wallMs = Date.now() - startWall;
181
- const stdout = child.stdout ?? "";
182
- const stderr = child.stderr ?? "";
183
- const events = parseNdjson(stdout);
184
- const eventError = getEventError(events);
185
- const timedOut =
186
- child.error?.code === "ETIMEDOUT" ||
187
- child.signal === "SIGTERM" ||
188
- /timed out/i.test(String(child.error?.message ?? ""));
189
- if (child.error && !timedOut) {
190
- throw child.error;
191
- }
192
- const modelNotFound = /Model not found|ProviderModelNotFoundError/i.test(`${stdout}\n${stderr}`) || /Model not found/i.test(eventError?.message ?? "");
193
-
194
- return {
195
- status: child.status ?? 1,
196
- signal: child.signal ?? null,
197
- stdout,
198
- stderr,
199
- wallMs,
200
- events,
201
- eventError,
202
- timedOut,
203
- modelNotFound,
204
- };
205
- }
1
+ import { spawnSync } from "node:child_process";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
6
+ const repoRoot = resolve(scriptDir, "..", "..");
7
+
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
+ }
18
+
19
+ if (process.platform !== "win32") {
20
+ return { command: "codex", shell: false };
21
+ }
22
+
23
+ const whereResult = spawnSync("where", ["Codex"], {
24
+ encoding: "utf8",
25
+ windowsHide: true,
26
+ });
27
+ const candidates = `${whereResult.stdout ?? ""}`
28
+ .split(/\r?\n/)
29
+ .map((line) => line.trim())
30
+ .filter((line) => /^[A-Za-z]:\\.+\.(exe|cmd)$/i.test(line));
31
+
32
+ if (candidates.length === 0) {
33
+ return { command: "codex", shell: false };
34
+ }
35
+
36
+ const exactExe = candidates.find((candidate) => /npm\\Codex\.exe$/i.test(candidate));
37
+ if (exactExe) {
38
+ return { command: exactExe, shell: false };
39
+ }
40
+
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
+ }
50
+
51
+ return { command: candidates[0], shell: false };
52
+ }
53
+
54
+ export function parseNdjson(text) {
55
+ const events = [];
56
+ for (const line of text.split(/\r?\n/)) {
57
+ const trimmed = line.trim();
58
+ if (!trimmed) {
59
+ continue;
60
+ }
61
+ try {
62
+ events.push(JSON.parse(trimmed));
63
+ } catch {
64
+ // Ignore non-JSON lines emitted by wrappers (bun install, warnings, etc.).
65
+ }
66
+ }
67
+ return events;
68
+ }
69
+
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,
77
+ status: event.part.state?.status,
78
+ start: event.part.state?.time?.start,
79
+ end: event.part.state?.time?.end,
80
+ durationMs:
81
+ typeof event.part.state?.time?.start === "number" &&
82
+ typeof event.part.state?.time?.end === "number"
83
+ ? event.part.state.time.end - event.part.state.time.start
84
+ : null,
85
+ }));
86
+ }
87
+
88
+ export function getSessionDuration(events) {
89
+ const starts = events
90
+ .filter((event) => event?.type === "step_start" && typeof event.timestamp === "number")
91
+ .map((event) => event.timestamp);
92
+ const finishes = events
93
+ .filter((event) => event?.type === "step_finish" && typeof event.timestamp === "number")
94
+ .map((event) => event.timestamp);
95
+ if (starts.length === 0 || finishes.length === 0) {
96
+ return null;
97
+ }
98
+ return Math.max(...finishes) - Math.min(...starts);
99
+ }
100
+
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
+ }
106
+ const total = {
107
+ total: 0,
108
+ input: 0,
109
+ output: 0,
110
+ reasoning: 0,
111
+ cacheRead: 0,
112
+ cacheWrite: 0,
113
+ };
114
+ for (const event of stepFinishes) {
115
+ const tokens = event.part.tokens ?? {};
116
+ const input = Number(tokens.input ?? 0);
117
+ const output = Number(tokens.output ?? 0);
118
+ const reasoning = Number(tokens.reasoning ?? 0);
119
+ const explicitTotal = Number(tokens.total ?? NaN);
120
+ total.total += Number.isFinite(explicitTotal) ? explicitTotal : input + output + reasoning;
121
+ total.input += input;
122
+ total.output += output;
123
+ total.reasoning += reasoning;
124
+ total.cacheRead += Number(tokens.cache?.read ?? 0);
125
+ total.cacheWrite += Number(tokens.cache?.write ?? 0);
126
+ }
127
+ return total;
128
+ }
129
+
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
+ }
147
+
148
+ export function runCodexJson({
149
+ executable,
150
+ prompt,
151
+ model,
152
+ variant,
153
+ agent,
154
+ cwd,
155
+ homeDir,
156
+ timeoutMs,
157
+ 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, {
167
+ cwd: cwd ?? repoRoot,
168
+ encoding: "utf8",
169
+ windowsHide: true,
170
+ shell: executable.shell,
171
+ timeout: timeoutMs,
172
+ maxBuffer: 30 * 1024 * 1024,
173
+ env: {
174
+ ...process.env,
175
+ ...(homeDir ? { HOME: homeDir, USERPROFILE: homeDir } : {}),
176
+ ...extraEnv,
177
+ },
178
+ });
179
+
180
+ const wallMs = Date.now() - startWall;
181
+ const stdout = child.stdout ?? "";
182
+ const stderr = child.stderr ?? "";
183
+ const events = parseNdjson(stdout);
184
+ const eventError = getEventError(events);
185
+ const timedOut =
186
+ child.error?.code === "ETIMEDOUT" ||
187
+ child.signal === "SIGTERM" ||
188
+ /timed out/i.test(String(child.error?.message ?? ""));
189
+ if (child.error && !timedOut) {
190
+ throw child.error;
191
+ }
192
+ const modelNotFound = /Model not found|ProviderModelNotFoundError/i.test(`${stdout}\n${stderr}`) || /Model not found/i.test(eventError?.message ?? "");
193
+
194
+ return {
195
+ status: child.status ?? 1,
196
+ signal: child.signal ?? null,
197
+ stdout,
198
+ stderr,
199
+ wallMs,
200
+ events,
201
+ eventError,
202
+ timedOut,
203
+ modelNotFound,
204
+ };
205
+ }
206
+
@@ -1,105 +1,111 @@
1
- import { spawnSync } from "node:child_process";
2
- import { resolveOpencodeExecutable } from "./opencode.mjs";
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
- ];
12
-
13
- const OPENAI_CODEX_PREFIXES = ["openai/", "openai-multi/"];
14
-
15
- export function isOpenAiCodexProviderPrefix(modelId) {
16
- return OPENAI_CODEX_PREFIXES.some((prefix) => modelId.startsWith(prefix));
17
- }
18
-
19
- export function codexTail(modelId) {
20
- const slash = modelId.indexOf("/");
21
- return slash >= 0 ? modelId.slice(slash + 1) : modelId;
22
- }
23
-
24
- export function canonicalCodexModelId(modelId) {
25
- return `openai/${codexTail(modelId)}`;
26
- }
27
-
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
- }
36
-
37
- export function isStableOpenAiCodexModel(modelId) {
38
- if (!isOpenAiCodexProviderPrefix(modelId)) {
39
- return false;
40
- }
41
- if (!modelId.includes("codex")) {
42
- return false;
43
- }
44
- if (modelId.includes("-spark") || modelId.includes("-latest")) {
45
- return false;
46
- }
47
- return true;
48
- }
49
-
50
- export function listOpencodeModels() {
51
- const executable = resolveOpencodeExecutable();
52
- const child = spawnSync(executable.command, ["models"], {
53
- encoding: "utf8",
54
- windowsHide: true,
55
- shell: executable.shell,
56
- maxBuffer: 10 * 1024 * 1024,
57
- });
58
- const text = `${child.stdout ?? ""}\n${child.stderr ?? ""}`;
59
- if ((child.status ?? 1) !== 0) {
60
- throw new Error(`Failed to list OpenCode models (exit=${child.status ?? 1})`);
61
- }
62
- return text
63
- .split(/\r?\n/)
64
- .map((line) => line.trim())
65
- .filter((line) => line.length > 0)
66
- .filter((line) => !line.startsWith("Using Node "));
67
- }
68
-
69
- function dedupeCodexModels(models) {
70
- const byCanonical = new Map();
71
- for (const modelId of models) {
72
- const canonical = canonicalCodexModelId(modelId);
73
- const existing = byCanonical.get(canonical);
74
- if (!existing) {
75
- byCanonical.set(canonical, modelId);
76
- continue;
77
- }
78
- // Prefer the shorter/default provider form when both exist.
79
- if (existing.startsWith("openai-multi/") && modelId.startsWith("openai/")) {
80
- byCanonical.set(canonical, modelId);
81
- }
82
- }
83
- return [...byCanonical.values()].sort((a, b) => canonicalCodexModelId(a).localeCompare(canonicalCodexModelId(b)));
84
- }
85
-
86
- export function resolveModelPreset(presetName, explicitModels) {
87
- if (explicitModels && explicitModels.length > 0) {
88
- return explicitModels;
89
- }
90
-
91
- if (presetName !== "codex-core") {
92
- throw new Error(`Unsupported preset: ${presetName}`);
93
- }
94
-
95
- try {
96
- const models = dedupeCodexModels(listOpencodeModels().filter(isStableOpenAiCodexModel));
97
- if (models.length > 0) {
98
- return models;
99
- }
100
- } catch {
101
- // Fall back to static list.
102
- }
103
-
104
- return [...FALLBACK_OPENAI_CODEX_STABLE];
105
- }
1
+ import { spawnSync } from "node:child_process";
2
+ import { resolveCodexExecutable } from "./codex-host.mjs";
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
+ ];
12
+
13
+ const OPENAI_CODEX_PREFIXES = ["openai/", "openai-multi/"];
14
+
15
+ export function isOpenAiCodexProviderPrefix(modelId) {
16
+ return OPENAI_CODEX_PREFIXES.some((prefix) => modelId.startsWith(prefix));
17
+ }
18
+
19
+ export function codexTail(modelId) {
20
+ const slash = modelId.indexOf("/");
21
+ return slash >= 0 ? modelId.slice(slash + 1) : modelId;
22
+ }
23
+
24
+ export function canonicalCodexModelId(modelId) {
25
+ return `openai/${codexTail(modelId)}`;
26
+ }
27
+
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
+ }
36
+
37
+ export function isStableOpenAiCodexModel(modelId) {
38
+ if (!isOpenAiCodexProviderPrefix(modelId)) {
39
+ return false;
40
+ }
41
+ if (!modelId.includes("codex")) {
42
+ return false;
43
+ }
44
+ if (modelId.includes("-spark") || modelId.includes("-latest")) {
45
+ return false;
46
+ }
47
+ return true;
48
+ }
49
+
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),
58
+ killSignal: "SIGKILL",
59
+ });
60
+ if (child.error && child.error.code === "ETIMEDOUT") {
61
+ throw new Error(`Timed out while listing Codex models after ${process.env.CODEX_MODELS_TIMEOUT_MS ?? "30000"}ms`);
62
+ }
63
+ const text = `${child.stdout ?? ""}\n${child.stderr ?? ""}`;
64
+ if ((child.status ?? 1) !== 0) {
65
+ throw new Error(`Failed to list Codex models (exit=${child.status ?? 1})`);
66
+ }
67
+ return text
68
+ .split(/\r?\n/)
69
+ .map((line) => line.trim())
70
+ .filter((line) => line.length > 0)
71
+ .filter((line) => !line.startsWith("Using Node "));
72
+ }
73
+
74
+ function dedupeCodexModels(models) {
75
+ const byCanonical = new Map();
76
+ for (const modelId of models) {
77
+ const canonical = canonicalCodexModelId(modelId);
78
+ const existing = byCanonical.get(canonical);
79
+ if (!existing) {
80
+ byCanonical.set(canonical, modelId);
81
+ continue;
82
+ }
83
+ // Prefer the shorter/default provider form when both exist.
84
+ if (existing.startsWith("openai-multi/") && modelId.startsWith("openai/")) {
85
+ byCanonical.set(canonical, modelId);
86
+ }
87
+ }
88
+ return [...byCanonical.values()].sort((a, b) => canonicalCodexModelId(a).localeCompare(canonicalCodexModelId(b)));
89
+ }
90
+
91
+ export function resolveModelPreset(presetName, explicitModels) {
92
+ if (explicitModels && explicitModels.length > 0) {
93
+ return explicitModels;
94
+ }
95
+
96
+ if (presetName !== "codex-core") {
97
+ throw new Error(`Unsupported preset: ${presetName}`);
98
+ }
99
+
100
+ try {
101
+ const models = dedupeCodexModels(listCodexModels().filter(isStableOpenAiCodexModel));
102
+ if (models.length > 0) {
103
+ return models;
104
+ }
105
+ } catch {
106
+ // Fall back to static list.
107
+ }
108
+
109
+ return [...FALLBACK_OPENAI_CODEX_STABLE];
110
+ }
111
+