@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.
- package/README.md +3 -0
- package/codex-runtime.js +546 -39
- package/package.json +38 -34
- package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
- package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
- package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
- package/scripts/board-render-ownership-smoke.mjs +85 -0
- package/scripts/codex-runtime-home-smoke.mjs +58 -0
- package/scripts/codex-sdk-image-live-smoke.mjs +43 -0
- package/scripts/codex-sdk-image-runtime-smoke.mjs +122 -0
- package/server.js +17 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-board-render-ownership.js +81 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +85 -32
- package/wwwroot/_framework/MindExecution.Core.7oa1zr128r.dll +0 -0
- package/wwwroot/_framework/{MindExecution.Kernel.98wpeg0ntq.dll → MindExecution.Kernel.dqr12udjsr.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Admin.y25y20jkiv.dll → MindExecution.Plugins.Admin.6bt51zpdq7.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Business.guko3zr1rb.dll → MindExecution.Plugins.Business.e92nh6aw24.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Concept.qmin06apyt.dll → MindExecution.Plugins.Concept.z5rfp7c5gn.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Directory.6rw2l9utyr.dll → MindExecution.Plugins.Directory.6qmfycdkxt.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.72ir717kho.dll → MindExecution.Plugins.PlanMaster.lbbov61zg3.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.YouTube.rkge49vbw6.dll → MindExecution.Plugins.YouTube.7vjisvqu3j.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Shared.q4dgev2e3z.dll → MindExecution.Shared.ydgyfah35e.dll} +0 -0
- package/wwwroot/_framework/MindExecution.Web.b68gpm21e3.dll +0 -0
- package/wwwroot/_framework/blazor.boot.json +21 -21
- package/wwwroot/index.html +2 -1
- package/wwwroot/service-worker-assets.js +28 -24
- package/wwwroot/service-worker.js +1 -1
- package/wwwroot/_framework/MindExecution.Core.o29fdql6j5.dll +0 -0
- 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
|
|
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
|
|
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 ||
|
|
137
|
+
return path.resolve(configured || resolveUserCodexHome());
|
|
100
138
|
}
|
|
101
139
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
625
|
-
await
|
|
1007
|
+
const sdk = await sdkLoader();
|
|
1008
|
+
await prepareRuntimeHome();
|
|
626
1009
|
const codex = new sdk.Codex({
|
|
627
|
-
env:
|
|
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
|
|
690
|
-
await
|
|
1073
|
+
const sdk = await sdkLoader();
|
|
1074
|
+
await prepareRuntimeHome();
|
|
691
1075
|
const codex = new sdk.Codex({
|
|
692
|
-
env:
|
|
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
|
|
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:
|
|
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
|
|
1004
|
-
await
|
|
1508
|
+
const sdk = await sdkLoader();
|
|
1509
|
+
await prepareRuntimeHome();
|
|
1005
1510
|
const codex = new sdk.Codex({
|
|
1006
|
-
env:
|
|
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
|