@mindexec/cli 0.2.465 → 0.2.467

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 (29) hide show
  1. package/README.md +3 -0
  2. package/codex-runtime.js +546 -39
  3. package/package.json +38 -34
  4. package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
  5. package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
  6. package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
  7. package/scripts/board-render-ownership-smoke.mjs +85 -0
  8. package/scripts/codex-runtime-home-smoke.mjs +58 -0
  9. package/scripts/codex-sdk-image-live-smoke.mjs +43 -0
  10. package/scripts/codex-sdk-image-runtime-smoke.mjs +122 -0
  11. package/server.js +17 -0
  12. package/wwwroot/_content/MindExecution.Shared/js/mind-map-board-render-ownership.js +81 -0
  13. package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +85 -32
  14. package/wwwroot/_framework/MindExecution.Core.7oa1zr128r.dll +0 -0
  15. package/wwwroot/_framework/{MindExecution.Kernel.98wpeg0ntq.dll → MindExecution.Kernel.dqr12udjsr.dll} +0 -0
  16. package/wwwroot/_framework/{MindExecution.Plugins.Admin.y25y20jkiv.dll → MindExecution.Plugins.Admin.6bt51zpdq7.dll} +0 -0
  17. package/wwwroot/_framework/{MindExecution.Plugins.Business.guko3zr1rb.dll → MindExecution.Plugins.Business.e92nh6aw24.dll} +0 -0
  18. package/wwwroot/_framework/{MindExecution.Plugins.Concept.qmin06apyt.dll → MindExecution.Plugins.Concept.z5rfp7c5gn.dll} +0 -0
  19. package/wwwroot/_framework/{MindExecution.Plugins.Directory.6rw2l9utyr.dll → MindExecution.Plugins.Directory.6qmfycdkxt.dll} +0 -0
  20. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.72ir717kho.dll → MindExecution.Plugins.PlanMaster.lbbov61zg3.dll} +0 -0
  21. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.rkge49vbw6.dll → MindExecution.Plugins.YouTube.7vjisvqu3j.dll} +0 -0
  22. package/wwwroot/_framework/{MindExecution.Shared.q4dgev2e3z.dll → MindExecution.Shared.ydgyfah35e.dll} +0 -0
  23. package/wwwroot/_framework/MindExecution.Web.b68gpm21e3.dll +0 -0
  24. package/wwwroot/_framework/blazor.boot.json +21 -21
  25. package/wwwroot/index.html +2 -1
  26. package/wwwroot/service-worker-assets.js +28 -24
  27. package/wwwroot/service-worker.js +1 -1
  28. package/wwwroot/_framework/MindExecution.Core.o29fdql6j5.dll +0 -0
  29. package/wwwroot/_framework/MindExecution.Web.ckdrqv28k2.dll +0 -0
package/codex-runtime.js CHANGED
@@ -34,14 +34,47 @@ const API_SANDBOX_BY_SDK_VALUE = Object.freeze({
34
34
  });
35
35
 
36
36
  const DEFAULT_TIMEOUT_MS = 8 * 60 * 1000;
37
+ const CODEX_LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
37
38
  const MAX_LOG_CHARS = 4000;
38
39
  const MAX_EVENT_LOG = 120;
40
+ const MAX_IMAGE_REFERENCE_COUNT = 8;
41
+ const MAX_IMAGE_REFERENCE_BYTES = 40 * 1024 * 1024;
42
+ const MAX_GENERATED_IMAGE_BYTES = 50 * 1024 * 1024;
39
43
  const TEMP_DIR = '.ai/codex';
40
- const CODEX_SOURCE_HOME = path.join(os.homedir(), '.codex');
41
44
  const CODEX_RUNTIME_HOME_ENV = 'MINDEXEC_CODEX_HOME';
42
- const DEFAULT_CODEX_RUNTIME_HOME = path.join(os.homedir(), '.mindexec', 'codex-runtime');
43
45
  const CODEX_RUNTIME_CONFIG_MARKER = '# Generated by MindExec LocalBridge for isolated AI node runs.';
44
- const CODEX_RUNTIME_AUTH_FILES = ['auth.json'];
46
+ const CODEX_TARGET_BY_PLATFORM = Object.freeze({
47
+ 'win32:x64': {
48
+ packageName: '@openai/codex-win32-x64',
49
+ targetTriple: 'x86_64-pc-windows-msvc',
50
+ binaryName: 'codex.exe'
51
+ },
52
+ 'win32:arm64': {
53
+ packageName: '@openai/codex-win32-arm64',
54
+ targetTriple: 'aarch64-pc-windows-msvc',
55
+ binaryName: 'codex.exe'
56
+ },
57
+ 'darwin:x64': {
58
+ packageName: '@openai/codex-darwin-x64',
59
+ targetTriple: 'x86_64-apple-darwin',
60
+ binaryName: 'codex'
61
+ },
62
+ 'darwin:arm64': {
63
+ packageName: '@openai/codex-darwin-arm64',
64
+ targetTriple: 'aarch64-apple-darwin',
65
+ binaryName: 'codex'
66
+ },
67
+ 'linux:x64': {
68
+ packageName: '@openai/codex-linux-x64',
69
+ targetTriple: 'x86_64-unknown-linux-musl',
70
+ binaryName: 'codex'
71
+ },
72
+ 'linux:arm64': {
73
+ packageName: '@openai/codex-linux-arm64',
74
+ targetTriple: 'aarch64-unknown-linux-musl',
75
+ binaryName: 'codex'
76
+ }
77
+ });
45
78
 
46
79
  let cachedSdkModule = null;
47
80
  let cachedSdkLoadError = null;
@@ -94,35 +127,35 @@ function normalizeProviderKind(value) {
94
127
  return '';
95
128
  }
96
129
 
97
- function resolveCodexRuntimeHome() {
130
+ function resolveUserCodexHome() {
131
+ const configured = String(process.env.CODEX_HOME || '').trim();
132
+ return path.resolve(configured || path.join(os.homedir(), '.codex'));
133
+ }
134
+
135
+ export function resolveCodexRuntimeHome() {
98
136
  const configured = String(process.env[CODEX_RUNTIME_HOME_ENV] || '').trim();
99
- return path.resolve(configured || DEFAULT_CODEX_RUNTIME_HOME);
137
+ return path.resolve(configured || resolveUserCodexHome());
100
138
  }
101
139
 
102
- async function copyCodexRuntimeFileIfPresent(fileName, runtimeHome) {
103
- const source = path.join(CODEX_SOURCE_HOME, fileName);
104
- const target = path.join(runtimeHome, fileName);
105
- try {
106
- const sourceStat = await fs.stat(source);
107
- if (!sourceStat.isFile()) {
108
- return false;
109
- }
140
+ function usesExplicitIsolatedCodexHome() {
141
+ return String(process.env[CODEX_RUNTIME_HOME_ENV] || '').trim().length > 0;
142
+ }
110
143
 
111
- await fs.copyFile(source, target);
112
- return true;
113
- } catch {
114
- return false;
115
- }
144
+ function buildCodexSdkConfigOverrides(additional = {}) {
145
+ return {
146
+ mcp_servers: {},
147
+ ...additional
148
+ };
116
149
  }
117
150
 
118
- async function ensureCodexRuntimeHome() {
151
+ export async function ensureCodexRuntimeHome() {
119
152
  const runtimeHome = resolveCodexRuntimeHome();
120
153
  await fs.mkdir(runtimeHome, { recursive: true });
121
154
  await fs.mkdir(path.join(runtimeHome, 'sessions'), { recursive: true });
122
155
  await fs.mkdir(path.join(runtimeHome, 'generated_images'), { recursive: true });
123
156
 
124
- for (const fileName of CODEX_RUNTIME_AUTH_FILES) {
125
- await copyCodexRuntimeFileIfPresent(fileName, runtimeHome);
157
+ if (!usesExplicitIsolatedCodexHome()) {
158
+ return runtimeHome;
126
159
  }
127
160
 
128
161
  const configPath = path.join(runtimeHome, 'config.toml');
@@ -145,7 +178,7 @@ async function ensureCodexRuntimeHome() {
145
178
  return runtimeHome;
146
179
  }
147
180
 
148
- function buildCodexChildEnv() {
181
+ export function buildCodexChildEnv() {
149
182
  const env = {};
150
183
  for (const [key, value] of Object.entries(process.env)) {
151
184
  if (value !== undefined) {
@@ -154,10 +187,87 @@ function buildCodexChildEnv() {
154
187
  }
155
188
 
156
189
  env.CODEX_HOME = resolveCodexRuntimeHome();
157
- env.MINDEXEC_CODEX_ISOLATED_HOME = '1';
190
+ if (usesExplicitIsolatedCodexHome()) {
191
+ env.MINDEXEC_CODEX_ISOLATED_HOME = '1';
192
+ } else {
193
+ delete env.MINDEXEC_CODEX_ISOLATED_HOME;
194
+ }
158
195
  return env;
159
196
  }
160
197
 
198
+ function resolveBundledCodexExecutable(packageRoot) {
199
+ const target = CODEX_TARGET_BY_PLATFORM[`${process.platform}:${process.arch}`];
200
+ if (!target) {
201
+ throw new Error(`Unsupported Codex login platform: ${process.platform} (${process.arch}).`);
202
+ }
203
+
204
+ return path.join(
205
+ packageRoot,
206
+ 'node_modules',
207
+ ...target.packageName.split('/'),
208
+ 'vendor',
209
+ target.targetTriple,
210
+ 'bin',
211
+ target.binaryName);
212
+ }
213
+
214
+ function runBundledCodexCommand(packageRoot, args, timeoutMs = CODEX_LOGIN_TIMEOUT_MS) {
215
+ return new Promise((resolve) => {
216
+ let executablePath;
217
+ try {
218
+ executablePath = resolveBundledCodexExecutable(packageRoot);
219
+ } catch (err) {
220
+ resolve({ success: false, exitCode: -1, stdout: '', stderr: '', error: err?.message || String(err) });
221
+ return;
222
+ }
223
+
224
+ const child = spawn(executablePath, args, {
225
+ env: buildCodexChildEnv(),
226
+ windowsHide: true,
227
+ stdio: ['ignore', 'pipe', 'pipe']
228
+ });
229
+ let stdout = '';
230
+ let stderr = '';
231
+ let settled = false;
232
+ let timeout = null;
233
+
234
+ const finish = (result) => {
235
+ if (settled) {
236
+ return;
237
+ }
238
+ settled = true;
239
+ if (timeout) {
240
+ clearTimeout(timeout);
241
+ }
242
+ resolve({
243
+ success: result.exitCode === 0,
244
+ exitCode: result.exitCode,
245
+ stdout: truncateLog(stdout),
246
+ stderr: truncateLog(stderr),
247
+ error: result.error || ''
248
+ });
249
+ };
250
+
251
+ child.stdout?.on('data', chunk => {
252
+ stdout = truncateLog(`${stdout}${chunk}`, MAX_LOG_CHARS);
253
+ });
254
+ child.stderr?.on('data', chunk => {
255
+ stderr = truncateLog(`${stderr}${chunk}`, MAX_LOG_CHARS);
256
+ });
257
+ child.once('error', err => finish({ exitCode: -1, error: err?.message || String(err) }));
258
+ child.once('exit', code => finish({ exitCode: Number.isFinite(Number(code)) ? Number(code) : -1 }));
259
+
260
+ timeout = setTimeout(() => {
261
+ try {
262
+ child.kill();
263
+ } catch {
264
+ // Ignore a process that already exited while the timeout fired.
265
+ }
266
+ finish({ exitCode: -1, error: 'Codex browser login timed out.' });
267
+ }, timeoutMs);
268
+ });
269
+ }
270
+
161
271
  function normalizeReasoningEffort(value) {
162
272
  const normalized = String(value || '').trim().toLowerCase();
163
273
  return ['minimal', 'low', 'medium', 'high', 'xhigh'].includes(normalized)
@@ -489,8 +599,8 @@ function serializeThreadOptionsForStatus(options) {
489
599
  };
490
600
  }
491
601
 
492
- async function checkSdkAvailability(packageRoot) {
493
- const sdk = await loadCodexSdk();
602
+ async function checkSdkAvailability(packageRoot, sdkLoader = loadCodexSdk) {
603
+ const sdk = await sdkLoader();
494
604
  return {
495
605
  kind: PROVIDER_KIND.typeScriptSdk,
496
606
  available: Boolean(sdk?.Codex),
@@ -541,19 +651,291 @@ function extractPrompt(body) {
541
651
  return prompt;
542
652
  }
543
653
 
654
+ function looksLikeCodexAuthenticationError(value) {
655
+ const text = String(value || '').toLowerCase();
656
+ return text.includes('invalid_refresh_token')
657
+ || text.includes('invalid refresh token')
658
+ || text.includes('login required')
659
+ || text.includes('not logged in')
660
+ || text.includes('please log out and sign in again')
661
+ || text.includes('could not parse your authentication token')
662
+ || text.includes('authrequired')
663
+ || text.includes('401 unauthorized');
664
+ }
665
+
666
+ function normalizeImageMimeType(value, fallback = 'image/png') {
667
+ const normalized = String(value || '').trim().toLowerCase();
668
+ return /^image\/[a-z0-9.+-]+$/.test(normalized) ? normalized : fallback;
669
+ }
670
+
671
+ function inferImageMimeType(buffer, fallback = 'image/png') {
672
+ if (!Buffer.isBuffer(buffer) || buffer.length < 4) {
673
+ return fallback;
674
+ }
675
+
676
+ if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47) {
677
+ return 'image/png';
678
+ }
679
+ if (buffer[0] === 0xff && buffer[1] === 0xd8) {
680
+ return 'image/jpeg';
681
+ }
682
+ if (buffer.slice(0, 3).toString('ascii') === 'GIF') {
683
+ return 'image/gif';
684
+ }
685
+ if (buffer.length >= 12
686
+ && buffer.slice(0, 4).toString('ascii') === 'RIFF'
687
+ && buffer.slice(8, 12).toString('ascii') === 'WEBP') {
688
+ return 'image/webp';
689
+ }
690
+
691
+ return fallback;
692
+ }
693
+
694
+ function normalizeGeneratedImageBase64(value, hintedMimeType = '') {
695
+ const text = String(value || '').trim();
696
+ if (!text) {
697
+ return null;
698
+ }
699
+
700
+ const dataUrlMatch = text.match(/^data:(image\/[a-z0-9.+-]+);base64,([a-z0-9+/=\r\n]+)$/i);
701
+ const base64 = (dataUrlMatch ? dataUrlMatch[2] : text).replace(/\s+/g, '');
702
+ if (base64.length < 32 || !/^[a-z0-9+/]+={0,2}$/i.test(base64)) {
703
+ return null;
704
+ }
705
+
706
+ try {
707
+ const bytes = Buffer.from(base64, 'base64');
708
+ if (!bytes.length || bytes.length > MAX_GENERATED_IMAGE_BYTES) {
709
+ return null;
710
+ }
711
+ const mimeType = inferImageMimeType(
712
+ bytes,
713
+ normalizeImageMimeType(dataUrlMatch?.[1] || hintedMimeType));
714
+ return { imageBase64: bytes.toString('base64'), mimeType };
715
+ } catch {
716
+ return null;
717
+ }
718
+ }
719
+
720
+ function extractGeneratedImageFromEvent(value, depth = 0, hintedMimeType = '') {
721
+ if (value == null || depth > 10) {
722
+ return null;
723
+ }
724
+
725
+ if (typeof value === 'string') {
726
+ return value.startsWith('data:image/')
727
+ ? normalizeGeneratedImageBase64(value, hintedMimeType)
728
+ : null;
729
+ }
730
+
731
+ if (Array.isArray(value)) {
732
+ for (const entry of value) {
733
+ const found = extractGeneratedImageFromEvent(entry, depth + 1, hintedMimeType);
734
+ if (found) {
735
+ return found;
736
+ }
737
+ }
738
+ return null;
739
+ }
740
+
741
+ if (typeof value !== 'object') {
742
+ return null;
743
+ }
744
+
745
+ const localMimeType = normalizeImageMimeType(
746
+ value.mime_type || value.mimeType || value.content_type || value.contentType,
747
+ hintedMimeType || 'image/png');
748
+ for (const key of ['b64_json', 'image_base64', 'imageBase64', 'base64']) {
749
+ if (typeof value[key] === 'string') {
750
+ const found = normalizeGeneratedImageBase64(value[key], localMimeType);
751
+ if (found) {
752
+ return found;
753
+ }
754
+ }
755
+ }
756
+
757
+ for (const key of ['result', 'output', 'image', 'images', 'data', 'payload', 'item']) {
758
+ if (value[key] != null) {
759
+ const found = extractGeneratedImageFromEvent(value[key], depth + 1, localMimeType);
760
+ if (found) {
761
+ return found;
762
+ }
763
+ }
764
+ }
765
+
766
+ return null;
767
+ }
768
+
769
+ function imageExtensionForReference(reference) {
770
+ const fileNameExtension = path.extname(String(reference?.fileName || '')).toLowerCase();
771
+ if (['.png', '.jpg', '.jpeg', '.webp', '.gif'].includes(fileNameExtension)) {
772
+ return fileNameExtension === '.jpeg' ? '.jpg' : fileNameExtension;
773
+ }
774
+
775
+ const mimeType = normalizeImageMimeType(reference?.mimeType);
776
+ if (mimeType === 'image/jpeg') return '.jpg';
777
+ if (mimeType === 'image/webp') return '.webp';
778
+ if (mimeType === 'image/gif') return '.gif';
779
+ return '.png';
780
+ }
781
+
782
+ async function writeSdkImageReferences(runtimeHome, references) {
783
+ const normalized = Array.isArray(references)
784
+ ? references.slice(0, MAX_IMAGE_REFERENCE_COUNT)
785
+ : [];
786
+ if (!normalized.length) {
787
+ return { directory: '', paths: [] };
788
+ }
789
+
790
+ const root = path.join(runtimeHome, 'image-inputs');
791
+ const directory = path.join(root, crypto.randomUUID());
792
+ await fs.mkdir(directory, { recursive: true });
793
+ const paths = [];
794
+ let totalBytes = 0;
795
+
796
+ try {
797
+ for (let index = 0; index < normalized.length; index += 1) {
798
+ const reference = normalized[index];
799
+ const bytes = Buffer.from(String(reference?.base64 || ''), 'base64');
800
+ totalBytes += bytes.length;
801
+ if (!bytes.length || totalBytes > MAX_IMAGE_REFERENCE_BYTES) {
802
+ throw new Error('Codex SDK image references are empty or exceed the 40 MB request limit.');
803
+ }
804
+
805
+ const filePath = path.join(directory, `reference-${index + 1}${imageExtensionForReference(reference)}`);
806
+ await fs.writeFile(filePath, bytes);
807
+ paths.push(filePath);
808
+ }
809
+
810
+ return { directory, paths };
811
+ } catch (err) {
812
+ await fs.rm(directory, { recursive: true, force: true }).catch(() => {});
813
+ throw err;
814
+ }
815
+ }
816
+
817
+ async function cleanupSdkImageReferences(runtimeHome, directory) {
818
+ if (!directory) {
819
+ return;
820
+ }
821
+
822
+ const root = path.resolve(runtimeHome, 'image-inputs');
823
+ const target = path.resolve(directory);
824
+ if (!target.startsWith(`${root}${path.sep}`)) {
825
+ return;
826
+ }
827
+ await fs.rm(target, { recursive: true, force: true }).catch(() => {});
828
+ }
829
+
830
+ function buildSdkImagePrompt(body, referenceCount) {
831
+ const prompt = extractPrompt(body);
832
+ const action = String(body?.action || '').trim().toLowerCase() === 'edit' || referenceCount > 0
833
+ ? 'edit'
834
+ : 'generate';
835
+ const size = String(body?.size || '').trim();
836
+ const quality = String(body?.quality || '').trim();
837
+ return [
838
+ action === 'edit'
839
+ ? `Use the image generation tool to edit the ${referenceCount} attached reference image(s).`
840
+ : 'Use the image generation tool to create one new image.',
841
+ prompt,
842
+ size ? `Requested output size: ${size}.` : '',
843
+ quality ? `Requested quality: ${quality}.` : '',
844
+ 'Return exactly one generated image. Do not answer with instructions or prose.'
845
+ ].filter(Boolean).join('\n\n');
846
+ }
847
+
848
+ async function listGeneratedImageCandidates(directory, minimumMtimeMs, depth = 0) {
849
+ if (depth > 4) {
850
+ return [];
851
+ }
852
+
853
+ const entries = await fs.readdir(directory, { withFileTypes: true }).catch(() => []);
854
+ const candidates = [];
855
+ for (const entry of entries) {
856
+ const fullPath = path.join(directory, entry.name);
857
+ if (entry.isDirectory()) {
858
+ candidates.push(...await listGeneratedImageCandidates(fullPath, minimumMtimeMs, depth + 1));
859
+ continue;
860
+ }
861
+ if (!entry.isFile() || !/\.(png|jpe?g|webp|gif)$/i.test(entry.name)) {
862
+ continue;
863
+ }
864
+
865
+ const stat = await fs.stat(fullPath).catch(() => null);
866
+ if (stat && stat.size > 0 && stat.size <= MAX_GENERATED_IMAGE_BYTES && stat.mtimeMs >= minimumMtimeMs) {
867
+ candidates.push({ fullPath, mtimeMs: stat.mtimeMs });
868
+ }
869
+ }
870
+ return candidates;
871
+ }
872
+
873
+ async function recoverSdkGeneratedImage(runtimeHome, startedAtMs) {
874
+ const generatedRoot = path.join(runtimeHome, 'generated_images');
875
+ const minimumMtimeMs = startedAtMs - 15 * 1000;
876
+ for (let attempt = 0; attempt < 8; attempt += 1) {
877
+ const candidates = await listGeneratedImageCandidates(generatedRoot, minimumMtimeMs);
878
+ candidates.sort((left, right) => right.mtimeMs - left.mtimeMs);
879
+ if (candidates[0]) {
880
+ const bytes = await fs.readFile(candidates[0].fullPath);
881
+ return {
882
+ imageBase64: bytes.toString('base64'),
883
+ mimeType: inferImageMimeType(bytes)
884
+ };
885
+ }
886
+ await new Promise(resolve => setTimeout(resolve, 350));
887
+ }
888
+ return null;
889
+ }
890
+
544
891
  export function createCodexRuntime(options) {
545
892
  const packageRoot = options.packageRoot;
546
893
  const resolveWorkingDirectory = options.resolveWorkingDirectory;
547
894
  const getCurrentRuntime = options.getCurrentRuntime;
548
895
  const getModelCatalog = options.getModelCatalog;
549
896
  const log = typeof options.log === 'function' ? options.log : () => {};
897
+ const sdkLoader = typeof options.loadCodexSdk === 'function' ? options.loadCodexSdk : loadCodexSdk;
898
+ const prepareRuntimeHome = typeof options.ensureCodexRuntimeHome === 'function'
899
+ ? options.ensureCodexRuntimeHome
900
+ : ensureCodexRuntimeHome;
901
+ const childEnvFactory = typeof options.buildCodexChildEnv === 'function'
902
+ ? options.buildCodexChildEnv
903
+ : buildCodexChildEnv;
550
904
 
551
905
  const threads = new Map();
552
906
  const activeTurns = new Map();
907
+ let activeLoginPromise = null;
908
+
909
+ async function loginWithBundledCodex() {
910
+ if (typeof options.loginCodex === 'function') {
911
+ return options.loginCodex();
912
+ }
913
+
914
+ if (activeLoginPromise) {
915
+ return activeLoginPromise;
916
+ }
917
+
918
+ activeLoginPromise = (async () => {
919
+ await prepareRuntimeHome();
920
+ await runBundledCodexCommand(packageRoot, ['logout'], 15000).catch(() => null);
921
+ log('Codex SDK browser login start', { runtimeHome: resolveCodexRuntimeHome() });
922
+ const loginResult = await runBundledCodexCommand(packageRoot, ['login']);
923
+ log('Codex SDK browser login finish', {
924
+ success: loginResult.success,
925
+ exitCode: loginResult.exitCode,
926
+ error: loginResult.error || ''
927
+ });
928
+ return loginResult;
929
+ })().finally(() => {
930
+ activeLoginPromise = null;
931
+ });
932
+
933
+ return activeLoginPromise;
934
+ }
553
935
 
554
936
  async function getCapabilities(options = {}) {
555
937
  const [sdk, legacy, modelCatalog] = await Promise.all([
556
- checkSdkAvailability(packageRoot),
938
+ checkSdkAvailability(packageRoot, sdkLoader),
557
939
  checkLegacyAvailability(),
558
940
  getModelCatalog({ forceRefresh: Boolean(options.forceRefresh) })
559
941
  ]);
@@ -583,6 +965,7 @@ export function createCodexRuntime(options) {
583
965
  'POST /api/codex/thread/run',
584
966
  'POST /api/codex/thread/resume',
585
967
  'POST /api/codex/thread/cancel',
968
+ 'POST /api/codex/image/run',
586
969
  'GET /api/codex/thread/{threadId}/status'
587
970
  ],
588
971
  runtime: getCurrentRuntime(),
@@ -598,7 +981,7 @@ export function createCodexRuntime(options) {
598
981
 
599
982
  async function resolveProvider(requestedProviderKind) {
600
983
  const requested = normalizeProviderKind(requestedProviderKind);
601
- const sdk = await checkSdkAvailability(packageRoot);
984
+ const sdk = await checkSdkAvailability(packageRoot, sdkLoader);
602
985
  const legacy = requested === PROVIDER_KIND.typeScriptSdk
603
986
  ? null
604
987
  : await checkLegacyAvailability();
@@ -621,10 +1004,11 @@ export function createCodexRuntime(options) {
621
1004
  const now = new Date().toISOString();
622
1005
 
623
1006
  if (providerKind === PROVIDER_KIND.typeScriptSdk) {
624
- const sdk = await loadCodexSdk();
625
- await ensureCodexRuntimeHome();
1007
+ const sdk = await sdkLoader();
1008
+ await prepareRuntimeHome();
626
1009
  const codex = new sdk.Codex({
627
- env: buildCodexChildEnv()
1010
+ env: childEnvFactory(),
1011
+ config: buildCodexSdkConfigOverrides()
628
1012
  });
629
1013
  const thread = codex.startThread(threadOptions);
630
1014
  const localThreadId = `local_${crypto.randomUUID()}`;
@@ -686,10 +1070,11 @@ export function createCodexRuntime(options) {
686
1070
  };
687
1071
  }
688
1072
 
689
- const sdk = await loadCodexSdk();
690
- await ensureCodexRuntimeHome();
1073
+ const sdk = await sdkLoader();
1074
+ await prepareRuntimeHome();
691
1075
  const codex = new sdk.Codex({
692
- env: buildCodexChildEnv()
1076
+ env: childEnvFactory(),
1077
+ config: buildCodexSdkConfigOverrides()
693
1078
  });
694
1079
  const officialId = requestedThreadId && !requestedThreadId.startsWith('local_')
695
1080
  ? requestedThreadId
@@ -831,7 +1216,7 @@ export function createCodexRuntime(options) {
831
1216
  const tempDir = path.join(workingDirectory, TEMP_DIR);
832
1217
  const schema = normalizeOutputSchema(body.outputSchema || body.outputSchemaJson);
833
1218
  let schemaPath = null;
834
- await ensureCodexRuntimeHome();
1219
+ await prepareRuntimeHome();
835
1220
  const args = [
836
1221
  'exec',
837
1222
  '-',
@@ -844,7 +1229,9 @@ export function createCodexRuntime(options) {
844
1229
  '-s',
845
1230
  threadOptions.sandboxMode,
846
1231
  '--config',
847
- `model_reasoning_effort=${threadOptions.modelReasoningEffort}`
1232
+ `model_reasoning_effort=${threadOptions.modelReasoningEffort}`,
1233
+ '--config',
1234
+ 'mcp_servers={}'
848
1235
  ];
849
1236
  if (threadOptions.model) {
850
1237
  args.push('-m', threadOptions.model);
@@ -871,7 +1258,7 @@ export function createCodexRuntime(options) {
871
1258
  const childResult = await new Promise((resolve) => {
872
1259
  const child = spawn('codex', args, {
873
1260
  cwd: workingDirectory,
874
- env: buildCodexChildEnv(),
1261
+ env: childEnvFactory(),
875
1262
  windowsHide: true,
876
1263
  signal: abortController.signal
877
1264
  });
@@ -975,6 +1362,124 @@ export function createCodexRuntime(options) {
975
1362
  });
976
1363
  }
977
1364
 
1365
+ async function runSdkImageOnce(body = {}) {
1366
+ const workingDirectory = await resolveWorkingDirectory(body.workingDir || body.workingDirectory || '');
1367
+ const runtimeHome = await prepareRuntimeHome();
1368
+ const sdk = await sdkLoader();
1369
+ if (!sdk?.Codex) {
1370
+ return {
1371
+ success: false,
1372
+ providerKind: PROVIDER_KIND.unavailable,
1373
+ imageBase64: '',
1374
+ mimeType: '',
1375
+ error: cachedSdkLoadError || 'Codex TypeScript SDK is unavailable.',
1376
+ threadId: '',
1377
+ authenticationRequired: false,
1378
+ loginOpened: false
1379
+ };
1380
+ }
1381
+
1382
+ const references = await writeSdkImageReferences(runtimeHome, body.referenceImages);
1383
+ const prompt = buildSdkImagePrompt(body, references.paths.length);
1384
+ const threadOptions = buildThreadOptions({
1385
+ ...body,
1386
+ sandbox: 'read_only',
1387
+ approvalPolicy: 'on-request',
1388
+ reasoningEffort: 'low',
1389
+ networkAccessEnabled: true,
1390
+ webSearchMode: 'disabled'
1391
+ }, workingDirectory);
1392
+ const startedAtMs = Date.now();
1393
+ let threadId = '';
1394
+ let error = '';
1395
+ let generatedImage = null;
1396
+
1397
+ try {
1398
+ const codex = new sdk.Codex({
1399
+ env: childEnvFactory(),
1400
+ config: buildCodexSdkConfigOverrides({
1401
+ features: { image_generation: true }
1402
+ })
1403
+ });
1404
+ const thread = codex.startThread(threadOptions);
1405
+ const input = [
1406
+ { type: 'text', text: prompt },
1407
+ ...references.paths.map(referencePath => ({ type: 'local_image', path: referencePath }))
1408
+ ];
1409
+ const { events } = await thread.runStreamed(input);
1410
+
1411
+ for await (const event of events) {
1412
+ if (event?.type === 'thread.started' && event.thread_id) {
1413
+ threadId = String(event.thread_id);
1414
+ } else if (event?.type === 'turn.failed') {
1415
+ error = event.error?.message || 'Codex SDK image turn failed.';
1416
+ } else if (event?.type === 'error') {
1417
+ error = event.message || 'Codex SDK image stream failed.';
1418
+ }
1419
+
1420
+ generatedImage ||= extractGeneratedImageFromEvent(event);
1421
+ }
1422
+ } catch (err) {
1423
+ error = err?.message || String(err);
1424
+ } finally {
1425
+ await cleanupSdkImageReferences(runtimeHome, references.directory);
1426
+ }
1427
+
1428
+ const authenticationRequired = looksLikeCodexAuthenticationError(error);
1429
+ if (!authenticationRequired) {
1430
+ generatedImage ||= await recoverSdkGeneratedImage(runtimeHome, startedAtMs);
1431
+ }
1432
+ if (generatedImage) {
1433
+ return {
1434
+ success: true,
1435
+ providerKind: PROVIDER_KIND.typeScriptSdk,
1436
+ imageBase64: generatedImage.imageBase64,
1437
+ mimeType: generatedImage.mimeType,
1438
+ error: '',
1439
+ threadId,
1440
+ authenticationRequired: false,
1441
+ loginOpened: false
1442
+ };
1443
+ }
1444
+
1445
+ return {
1446
+ success: false,
1447
+ providerKind: PROVIDER_KIND.typeScriptSdk,
1448
+ imageBase64: '',
1449
+ mimeType: '',
1450
+ error: error || 'Codex SDK completed without returning an image.',
1451
+ threadId,
1452
+ authenticationRequired,
1453
+ loginOpened: false
1454
+ };
1455
+ }
1456
+
1457
+ async function runImage(body = {}) {
1458
+ extractPrompt(body);
1459
+ const firstResult = await runSdkImageOnce(body);
1460
+ if (!firstResult.authenticationRequired) {
1461
+ return firstResult;
1462
+ }
1463
+
1464
+ const loginResult = await loginWithBundledCodex();
1465
+ const loginSucceeded = loginResult === true || loginResult?.success === true;
1466
+ if (!loginSucceeded) {
1467
+ return {
1468
+ ...firstResult,
1469
+ loginOpened: true,
1470
+ error: loginResult?.error
1471
+ || 'Codex sign-in was not completed. Finish the browser login and retry.'
1472
+ };
1473
+ }
1474
+
1475
+ const retryResult = await runSdkImageOnce(body);
1476
+ return {
1477
+ ...retryResult,
1478
+ loginOpened: true,
1479
+ authenticationRequired: retryResult.authenticationRequired
1480
+ };
1481
+ }
1482
+
978
1483
  async function resumeThread(body = {}) {
979
1484
  const providerKind = await resolveProvider(body.providerKind || body.runtime);
980
1485
  const threadId = String(body.threadId || '').trim();
@@ -1000,10 +1505,11 @@ export function createCodexRuntime(options) {
1000
1505
  if (providerKind === PROVIDER_KIND.typeScriptSdk) {
1001
1506
  const workingDirectory = await resolveWorkingDirectory(body.workingDir || body.workingDirectory || '');
1002
1507
  const threadOptions = buildThreadOptions(body, workingDirectory);
1003
- const sdk = await loadCodexSdk();
1004
- await ensureCodexRuntimeHome();
1508
+ const sdk = await sdkLoader();
1509
+ await prepareRuntimeHome();
1005
1510
  const codex = new sdk.Codex({
1006
- env: buildCodexChildEnv()
1511
+ env: childEnvFactory(),
1512
+ config: buildCodexSdkConfigOverrides()
1007
1513
  });
1008
1514
  const thread = codex.resumeThread(threadId, threadOptions);
1009
1515
  threads.set(threadId, {
@@ -1094,6 +1600,7 @@ export function createCodexRuntime(options) {
1094
1600
  getCapabilities,
1095
1601
  startThread,
1096
1602
  runThread,
1603
+ runImage,
1097
1604
  resumeThread,
1098
1605
  cancelThread,
1099
1606
  getThreadStatus