ultracode-for-codex 0.2.0

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.
@@ -0,0 +1,685 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { copyFile, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
4
+ import { homedir, tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import readline from 'node:readline';
7
+ import { codexDefaultReasoningEffort, codexDefaultVerbosity, } from '../settings.js';
8
+ import { estimateTokens } from '../runtime/types.js';
9
+ import { codexChildProcessEnv } from './env.js';
10
+ const USAGE_NOTIFICATION_GRACE_MS = 100;
11
+ const BUFFERED_TURN_STATE_TTL_MS = 30_000;
12
+ const FALLBACK_CODEX_MODEL = 'gpt-5.5';
13
+ const DISABLED_CODEX_CONTEXT_FEATURES = [
14
+ 'apps',
15
+ 'browser_use',
16
+ 'computer_use',
17
+ 'goals',
18
+ 'hooks',
19
+ 'image_generation',
20
+ 'multi_agent',
21
+ 'plugins',
22
+ 'shell_tool',
23
+ 'workspace_dependencies',
24
+ ];
25
+ export class CodexSubagentBackend {
26
+ name = 'codex-subagent';
27
+ model;
28
+ command;
29
+ cwd;
30
+ configuredModel;
31
+ timeoutMs;
32
+ reasoningEffort;
33
+ verbosity;
34
+ child = null;
35
+ lineReader = null;
36
+ nextId = 1;
37
+ initialized = null;
38
+ stderr = '';
39
+ pending = new Map();
40
+ turnWaiters = new Map();
41
+ bufferedTurnStates = new Map();
42
+ isolation = null;
43
+ constructor(options) {
44
+ this.command = options.command ?? 'codex';
45
+ this.cwd = options.cwd;
46
+ this.model = options.model ?? 'codex-subagent';
47
+ this.configuredModel = options.model;
48
+ this.timeoutMs = options.timeoutMs;
49
+ this.reasoningEffort = options.reasoningEffort ?? codexDefaultReasoningEffort();
50
+ this.verbosity = options.verbosity ?? codexDefaultVerbosity();
51
+ }
52
+ async generate(request, signal) {
53
+ const startedAt = Date.now();
54
+ await this.ensureStarted();
55
+ const reasoningEffort = request.reasoningEffort ?? this.reasoningEffort;
56
+ const structuredTool = structuredOutputToolFor(request);
57
+ const prompt = workflowAgentPrompt(request, structuredTool);
58
+ let threadId = null;
59
+ let turnId = null;
60
+ const interruptTurn = async () => {
61
+ if (!threadId || !turnId)
62
+ return;
63
+ await this.send('turn/interrupt', { threadId, turnId }).catch(() => undefined);
64
+ };
65
+ const onAbort = () => {
66
+ void interruptTurn();
67
+ };
68
+ if (signal?.aborted)
69
+ throw new Error('request aborted');
70
+ if (signal)
71
+ signal.addEventListener('abort', onAbort, { once: true });
72
+ try {
73
+ const cwd = request.worktreePath ?? this.isolation?.workDir ?? this.cwd;
74
+ threadId = await this.startThread(reasoningEffort, this.verbosity, Boolean(structuredTool), cwd, Boolean(request.worktreePath));
75
+ const turn = await this.send('turn/start', {
76
+ threadId,
77
+ cwd,
78
+ runtimeWorkspaceRoots: [cwd],
79
+ environments: [],
80
+ input: [{ type: 'text', text: prompt, text_elements: [] }],
81
+ model: this.modelOverrideFor(request.model),
82
+ effort: reasoningEffort,
83
+ summary: 'none',
84
+ personality: 'none',
85
+ ...(structuredTool ? { outputSchema: structuredTool.inputSchema } : {}),
86
+ });
87
+ turnId = readPath(turn, ['result', 'turn', 'id']);
88
+ if (!turnId)
89
+ throw new Error('codex app-server did not return a turn id');
90
+ const result = await this.waitForTurn(threadId, turnId, signal);
91
+ const text = result.text.trim();
92
+ const usage = result.usage ?? estimatedUsage(prompt, text);
93
+ return {
94
+ id: `subagent_${randomUUID()}`,
95
+ model: request.model,
96
+ text: structuredTool ? '' : text,
97
+ toolCalls: structuredTool ? [{
98
+ id: `call_${randomUUID()}`,
99
+ name: structuredTool.name,
100
+ arguments: text,
101
+ }] : [],
102
+ usage,
103
+ latencyMs: Date.now() - startedAt,
104
+ };
105
+ }
106
+ finally {
107
+ if (signal)
108
+ signal.removeEventListener('abort', onAbort);
109
+ if (threadId)
110
+ this.archiveThread(threadId);
111
+ }
112
+ }
113
+ async close() {
114
+ for (const pending of this.pending.values()) {
115
+ clearTimeout(pending.timer);
116
+ pending.reject(new Error('codex subagent backend closed'));
117
+ }
118
+ this.pending.clear();
119
+ for (const waiter of this.turnWaiters.values()) {
120
+ waiter.reject(new Error('codex subagent backend closed'));
121
+ }
122
+ this.turnWaiters.clear();
123
+ this.clearBufferedTurnStates();
124
+ this.lineReader?.close();
125
+ this.child?.kill('SIGTERM');
126
+ this.child = null;
127
+ this.lineReader = null;
128
+ this.initialized = null;
129
+ await this.cleanupIsolation();
130
+ }
131
+ async ensureStarted() {
132
+ if (this.initialized)
133
+ return this.initialized;
134
+ this.initialized = this.start();
135
+ return this.initialized;
136
+ }
137
+ async start() {
138
+ const isolation = await createCodexIsolation({
139
+ configuredModel: this.configuredModel,
140
+ reasoningEffort: this.reasoningEffort,
141
+ verbosity: this.verbosity,
142
+ });
143
+ this.isolation = isolation;
144
+ const appServerArgs = [
145
+ 'app-server',
146
+ ...codexContextIsolationArgs({
147
+ model: this.configuredModel ?? isolation.defaultModel ?? FALLBACK_CODEX_MODEL,
148
+ reasoningEffort: this.reasoningEffort,
149
+ verbosity: this.verbosity,
150
+ }),
151
+ '--listen',
152
+ 'stdio://',
153
+ ];
154
+ this.child = spawn(this.command, appServerArgs, {
155
+ cwd: isolation.workDir,
156
+ shell: false,
157
+ env: codexChildProcessEnv({ CODEX_HOME: isolation.homeDir }),
158
+ stdio: ['pipe', 'pipe', 'pipe'],
159
+ });
160
+ this.child.stderr.setEncoding('utf8');
161
+ this.child.stderr.on('data', (chunk) => {
162
+ this.stderr = `${this.stderr}${chunk}`.slice(-12_000);
163
+ });
164
+ this.child.on('error', (err) => this.failAll(err));
165
+ this.child.on('close', (code, signal) => {
166
+ this.failAll(new Error(`codex app-server exited: code=${code ?? 'null'} signal=${signal ?? 'null'}`));
167
+ this.child = null;
168
+ this.lineReader = null;
169
+ this.initialized = null;
170
+ void this.cleanupIsolation();
171
+ });
172
+ this.lineReader = readline.createInterface({ input: this.child.stdout });
173
+ this.lineReader.on('line', (line) => this.handleLine(line));
174
+ await this.send('initialize', {
175
+ clientInfo: {
176
+ name: 'ultracode_for_codex',
177
+ title: 'Ultracode for Codex',
178
+ version: '0.2.0',
179
+ },
180
+ capabilities: {
181
+ experimentalApi: true,
182
+ requestAttestation: false,
183
+ optOutNotificationMethods: [],
184
+ },
185
+ });
186
+ this.notify('initialized', {});
187
+ }
188
+ async startThread(reasoningEffort, verbosity, structured, cwd, workspaceWrite) {
189
+ const thread = await this.send('thread/start', {
190
+ cwd,
191
+ runtimeWorkspaceRoots: [cwd],
192
+ approvalPolicy: 'never',
193
+ sandbox: workspaceWrite ? 'workspace-write' : 'read-only',
194
+ environments: [],
195
+ dynamicTools: [],
196
+ ephemeral: true,
197
+ baseInstructions: 'Ultracode workflow subagent. Produce only the workflow agent return value.',
198
+ developerInstructions: structured
199
+ ? 'Return exactly one JSON value matching the provided outputSchema.'
200
+ : 'Return exactly the raw result text for the workflow script.',
201
+ personality: 'none',
202
+ experimentalRawEvents: false,
203
+ persistExtendedHistory: false,
204
+ config: {
205
+ model_reasoning_effort: reasoningEffort,
206
+ model_reasoning_summary: 'none',
207
+ model_verbosity: verbosity,
208
+ web_search: 'disabled',
209
+ },
210
+ });
211
+ const threadId = readPath(thread, ['result', 'thread', 'id']);
212
+ if (!threadId)
213
+ throw new Error('codex app-server did not return a thread id');
214
+ return threadId;
215
+ }
216
+ archiveThread(threadId) {
217
+ void this.send('thread/archive', { threadId }).catch(() => undefined);
218
+ }
219
+ async cleanupIsolation() {
220
+ const isolation = this.isolation;
221
+ this.isolation = null;
222
+ if (isolation) {
223
+ await rm(isolation.rootDir, {
224
+ recursive: true,
225
+ force: true,
226
+ maxRetries: 3,
227
+ retryDelay: 100,
228
+ });
229
+ }
230
+ }
231
+ send(method, params) {
232
+ if (!this.child)
233
+ return Promise.reject(new Error('codex app-server is not running'));
234
+ const id = this.nextId;
235
+ this.nextId += 1;
236
+ this.child.stdin.write(`${JSON.stringify({ method, id, params })}\n`);
237
+ return new Promise((resolve, reject) => {
238
+ const timer = setTimeout(() => {
239
+ this.pending.delete(id);
240
+ reject(new Error(`${method} timed out after ${this.timeoutMs}ms`));
241
+ }, this.timeoutMs);
242
+ this.pending.set(id, { method, resolve, reject, timer });
243
+ });
244
+ }
245
+ notify(method, params) {
246
+ this.child?.stdin.write(`${JSON.stringify({ method, params })}\n`);
247
+ }
248
+ handleLine(line) {
249
+ let message;
250
+ try {
251
+ message = JSON.parse(line);
252
+ }
253
+ catch {
254
+ return;
255
+ }
256
+ const id = typeof message.id === 'number' ? message.id : null;
257
+ if (id !== null && this.pending.has(id)) {
258
+ const pending = this.pending.get(id);
259
+ if (!pending)
260
+ return;
261
+ this.pending.delete(id);
262
+ clearTimeout(pending.timer);
263
+ if (message.error)
264
+ pending.reject(new Error(`${pending.method} failed: ${JSON.stringify(message.error)}`));
265
+ else
266
+ pending.resolve(message);
267
+ return;
268
+ }
269
+ if (id !== null && typeof message.method === 'string') {
270
+ this.respondToServerRequest(id, message.method);
271
+ return;
272
+ }
273
+ if (typeof message.method === 'string')
274
+ this.handleNotification(message.method, message.params);
275
+ }
276
+ respondToServerRequest(id, method) {
277
+ const result = method.includes('requestApproval') ? { decision: 'decline' } : undefined;
278
+ this.child?.stdin.write(`${JSON.stringify({
279
+ id,
280
+ ...(result
281
+ ? { result }
282
+ : { error: { code: -32601, message: `Unsupported server request: ${method}` } }),
283
+ })}\n`);
284
+ }
285
+ handleNotification(method, params) {
286
+ const data = asRecord(params);
287
+ if (!data)
288
+ return;
289
+ if (method === 'item/agentMessage/delta') {
290
+ const key = turnStateKey(data.threadId, data.turnId);
291
+ if (!key || typeof data.delta !== 'string')
292
+ return;
293
+ const waiter = this.turnWaiters.get(key);
294
+ if (waiter)
295
+ waiter.text += data.delta;
296
+ else
297
+ this.bufferTurnState(key, (state) => state.textDeltas.push(data.delta));
298
+ return;
299
+ }
300
+ if (method === 'item/completed') {
301
+ const key = turnStateKey(data.threadId, data.turnId);
302
+ const item = asRecord(data.item);
303
+ if (!key || item?.type !== 'agentMessage' || typeof item.text !== 'string')
304
+ return;
305
+ const waiter = this.turnWaiters.get(key);
306
+ if (waiter)
307
+ waiter.text = item.text;
308
+ else
309
+ this.bufferTurnState(key, (state) => {
310
+ state.finalText = item.text;
311
+ });
312
+ return;
313
+ }
314
+ if (method === 'thread/tokenUsage/updated') {
315
+ const threadId = typeof data.threadId === 'string' ? data.threadId : null;
316
+ const turnId = typeof data.turnId === 'string' ? data.turnId : null;
317
+ if (!threadId || !turnId)
318
+ return;
319
+ const usage = usageFromCodexTokenUsage(data.tokenUsage);
320
+ if (!usage)
321
+ return;
322
+ const key = `${threadId}:${turnId}`;
323
+ const waiter = this.turnWaiters.get(key);
324
+ if (waiter) {
325
+ waiter.usage = usage;
326
+ waiter.usageUpdatedAt = Date.now();
327
+ if (waiter.completed)
328
+ this.resolveTurnWaiter(threadId, turnId);
329
+ }
330
+ else {
331
+ this.bufferTurnState(key, (state) => {
332
+ state.usage = usage;
333
+ state.usageUpdatedAt = Date.now();
334
+ });
335
+ }
336
+ return;
337
+ }
338
+ if (method === 'turn/completed') {
339
+ const threadId = typeof data.threadId === 'string' ? data.threadId : null;
340
+ const turn = asRecord(data.turn);
341
+ const turnId = typeof turn?.id === 'string' ? turn.id : null;
342
+ if (!threadId || !turnId)
343
+ return;
344
+ const key = `${threadId}:${turnId}`;
345
+ const waiter = this.turnWaiters.get(key);
346
+ if (!waiter) {
347
+ this.bufferTurnState(key, (state) => {
348
+ if (turn?.status === 'failed')
349
+ state.error = new Error(JSON.stringify(turn.error ?? 'turn failed'));
350
+ else {
351
+ state.completed = true;
352
+ state.completedAt = Date.now();
353
+ }
354
+ });
355
+ return;
356
+ }
357
+ if (turn?.status === 'failed') {
358
+ this.turnWaiters.delete(key);
359
+ waiter.reject(new Error(JSON.stringify(turn.error ?? 'turn failed')));
360
+ }
361
+ else {
362
+ this.markWaiterCompleted(threadId, turnId, waiter, Date.now());
363
+ }
364
+ }
365
+ }
366
+ waitForTurn(threadId, turnId, signal) {
367
+ const key = `${threadId}:${turnId}`;
368
+ return new Promise((resolve, reject) => {
369
+ let waiter;
370
+ const timer = setTimeout(() => {
371
+ this.turnWaiters.delete(key);
372
+ cleanup();
373
+ reject(new Error(`turn timed out after ${this.timeoutMs}ms`));
374
+ }, this.timeoutMs);
375
+ const cleanup = () => {
376
+ clearTimeout(timer);
377
+ if (waiter?.usageGraceTimer) {
378
+ clearTimeout(waiter.usageGraceTimer);
379
+ waiter.usageGraceTimer = undefined;
380
+ }
381
+ if (signal)
382
+ signal.removeEventListener('abort', abortFromSignal);
383
+ };
384
+ const abortFromSignal = () => {
385
+ this.turnWaiters.delete(key);
386
+ cleanup();
387
+ reject(new Error('request aborted'));
388
+ };
389
+ waiter = {
390
+ threadId,
391
+ turnId,
392
+ text: '',
393
+ completed: false,
394
+ resolve: (value) => {
395
+ cleanup();
396
+ resolve(value);
397
+ },
398
+ reject: (err) => {
399
+ cleanup();
400
+ reject(err);
401
+ },
402
+ };
403
+ this.turnWaiters.set(key, waiter);
404
+ if (signal) {
405
+ if (signal.aborted) {
406
+ abortFromSignal();
407
+ return;
408
+ }
409
+ signal.addEventListener('abort', abortFromSignal, { once: true });
410
+ }
411
+ const buffered = this.takeBufferedTurnState(key);
412
+ if (!buffered)
413
+ return;
414
+ waiter.text = buffered.finalText ?? buffered.textDeltas.join('');
415
+ waiter.usage = buffered.usage;
416
+ waiter.usageUpdatedAt = buffered.usageUpdatedAt;
417
+ if (buffered.error) {
418
+ this.turnWaiters.delete(key);
419
+ cleanup();
420
+ reject(buffered.error);
421
+ return;
422
+ }
423
+ if (buffered.completed) {
424
+ this.markWaiterCompleted(threadId, turnId, waiter, buffered.completedAt ?? Date.now());
425
+ }
426
+ });
427
+ }
428
+ markWaiterCompleted(threadId, turnId, waiter, completedAt) {
429
+ waiter.completed = true;
430
+ waiter.completedAt = completedAt;
431
+ if (waiter.usage)
432
+ this.resolveTurnWaiter(threadId, turnId);
433
+ else {
434
+ waiter.usageGraceTimer = setTimeout(() => this.resolveTurnWaiter(threadId, turnId), USAGE_NOTIFICATION_GRACE_MS);
435
+ }
436
+ }
437
+ resolveTurnWaiter(threadId, turnId) {
438
+ const key = `${threadId}:${turnId}`;
439
+ const waiter = this.turnWaiters.get(key);
440
+ if (!waiter)
441
+ return;
442
+ this.turnWaiters.delete(key);
443
+ if (waiter.usageGraceTimer) {
444
+ clearTimeout(waiter.usageGraceTimer);
445
+ waiter.usageGraceTimer = undefined;
446
+ }
447
+ waiter.resolve({
448
+ text: waiter.text.trim(),
449
+ usage: waiter.usage,
450
+ usageWaitMs: waiter.completedAt
451
+ ? Math.max(0, (waiter.usageUpdatedAt ?? Date.now()) - waiter.completedAt)
452
+ : 0,
453
+ });
454
+ }
455
+ failAll(err) {
456
+ const detail = this.stderr ? `\n${this.stderr.slice(-2000)}` : '';
457
+ const wrapped = new Error(`${err.message}${detail}`);
458
+ for (const pending of this.pending.values()) {
459
+ clearTimeout(pending.timer);
460
+ pending.reject(wrapped);
461
+ }
462
+ this.pending.clear();
463
+ for (const waiter of this.turnWaiters.values())
464
+ waiter.reject(wrapped);
465
+ this.turnWaiters.clear();
466
+ this.clearBufferedTurnStates();
467
+ }
468
+ modelOverrideFor(requestModel) {
469
+ if (this.configuredModel)
470
+ return this.configuredModel;
471
+ if (!requestModel || requestModel === this.model || requestModel === 'codex-subagent')
472
+ return undefined;
473
+ return requestModel;
474
+ }
475
+ bufferTurnState(key, apply) {
476
+ const state = this.bufferedTurnStates.get(key) ?? {
477
+ textDeltas: [],
478
+ completed: false,
479
+ };
480
+ if (state.cleanupTimer)
481
+ clearTimeout(state.cleanupTimer);
482
+ apply(state);
483
+ state.cleanupTimer = setTimeout(() => {
484
+ this.bufferedTurnStates.delete(key);
485
+ }, BUFFERED_TURN_STATE_TTL_MS);
486
+ this.bufferedTurnStates.set(key, state);
487
+ }
488
+ takeBufferedTurnState(key) {
489
+ const state = this.bufferedTurnStates.get(key);
490
+ if (!state)
491
+ return undefined;
492
+ this.bufferedTurnStates.delete(key);
493
+ if (state.cleanupTimer) {
494
+ clearTimeout(state.cleanupTimer);
495
+ state.cleanupTimer = undefined;
496
+ }
497
+ return state;
498
+ }
499
+ clearBufferedTurnStates() {
500
+ for (const state of this.bufferedTurnStates.values()) {
501
+ if (state.cleanupTimer)
502
+ clearTimeout(state.cleanupTimer);
503
+ }
504
+ this.bufferedTurnStates.clear();
505
+ }
506
+ }
507
+ function structuredOutputToolFor(request) {
508
+ if (request.toolChoice.type !== 'required')
509
+ return null;
510
+ return request.tools.length === 1 ? request.tools[0] ?? null : null;
511
+ }
512
+ function workflowAgentPrompt(request, structuredTool) {
513
+ const prompt = request.messages.map((message) => message.content).join('\n\n').trim();
514
+ if (!structuredTool)
515
+ return prompt;
516
+ return [
517
+ prompt,
518
+ '',
519
+ 'Return only the JSON value for StructuredOutput. No Markdown, code fence, or prose.',
520
+ ].join('\n');
521
+ }
522
+ function estimatedUsage(prompt, text) {
523
+ const inputTokens = estimateTokens(prompt);
524
+ const outputTokens = estimateTokens(text);
525
+ return {
526
+ inputTokens,
527
+ outputTokens,
528
+ totalTokens: inputTokens + outputTokens,
529
+ source: 'estimated',
530
+ };
531
+ }
532
+ function turnStateKey(threadId, turnId) {
533
+ return typeof threadId === 'string' && typeof turnId === 'string'
534
+ ? `${threadId}:${turnId}`
535
+ : null;
536
+ }
537
+ function readPath(value, path) {
538
+ let current = value;
539
+ for (const part of path) {
540
+ const obj = asRecord(current);
541
+ if (!obj)
542
+ return null;
543
+ current = obj[part];
544
+ }
545
+ return current;
546
+ }
547
+ export function usageFromCodexTokenUsage(value) {
548
+ const usage = asRecord(value);
549
+ const last = asRecord(usage?.last);
550
+ if (!last)
551
+ return null;
552
+ const totalTokens = readNumber(last.totalTokens);
553
+ const inputTokens = readNumber(last.inputTokens);
554
+ const outputTokens = readNumber(last.outputTokens);
555
+ const cachedInputTokens = readNumber(last.cachedInputTokens);
556
+ const reasoningOutputTokens = readNumber(last.reasoningOutputTokens);
557
+ if (totalTokens === 0
558
+ && inputTokens === 0
559
+ && outputTokens === 0
560
+ && cachedInputTokens === 0
561
+ && reasoningOutputTokens === 0) {
562
+ return null;
563
+ }
564
+ return {
565
+ inputTokens,
566
+ outputTokens,
567
+ totalTokens,
568
+ cachedInputTokens,
569
+ reasoningOutputTokens,
570
+ source: 'provider',
571
+ raw: value,
572
+ };
573
+ }
574
+ export function codexContextIsolationArgs(options) {
575
+ return [
576
+ '-c',
577
+ `model=${tomlString(options.model)}`,
578
+ '-c',
579
+ `model_reasoning_effort=${tomlString(options.reasoningEffort)}`,
580
+ '-c',
581
+ 'model_reasoning_summary="none"',
582
+ '-c',
583
+ `model_verbosity=${tomlString(options.verbosity)}`,
584
+ '-c',
585
+ 'web_search="disabled"',
586
+ '-c',
587
+ 'approval_policy="never"',
588
+ '-c',
589
+ 'sandbox_mode="read-only"',
590
+ '-c',
591
+ 'shell_environment_policy.inherit="none"',
592
+ ...DISABLED_CODEX_CONTEXT_FEATURES.flatMap((feature) => [
593
+ '-c',
594
+ `features.${feature}=false`,
595
+ ]),
596
+ '-c',
597
+ 'notify=[]',
598
+ '-c',
599
+ 'analytics.enabled=false',
600
+ ];
601
+ }
602
+ export async function createCodexIsolation(options) {
603
+ const rootDir = await mkdtemp(join(tmpdir(), 'ultracode-for-codex-codex-'));
604
+ const homeDir = join(rootDir, 'codex-home');
605
+ const workDir = join(rootDir, 'workspace');
606
+ await Promise.all([
607
+ mkdir(homeDir, { recursive: true }),
608
+ mkdir(workDir, { recursive: true }),
609
+ ]);
610
+ const sourceHome = sourceCodexHome();
611
+ const defaultModel = options.configuredModel ?? await readTopLevelStringConfig(sourceHome, 'model');
612
+ await copyCodexAuth(sourceHome, homeDir);
613
+ await writeFile(join(homeDir, 'config.toml'), minimalCodexConfigToml({
614
+ model: defaultModel ?? FALLBACK_CODEX_MODEL,
615
+ reasoningEffort: options.reasoningEffort,
616
+ verbosity: options.verbosity,
617
+ }), { mode: 0o600 });
618
+ return {
619
+ rootDir,
620
+ homeDir,
621
+ workDir,
622
+ defaultModel,
623
+ };
624
+ }
625
+ export function minimalCodexConfigToml(options) {
626
+ return [
627
+ `model = ${tomlString(options.model)}`,
628
+ `model_reasoning_effort = ${tomlString(options.reasoningEffort)}`,
629
+ 'model_reasoning_summary = "none"',
630
+ `model_verbosity = ${tomlString(options.verbosity)}`,
631
+ 'web_search = "disabled"',
632
+ 'approval_policy = "never"',
633
+ 'sandbox_mode = "read-only"',
634
+ '',
635
+ '[analytics]',
636
+ 'enabled = false',
637
+ '',
638
+ '[features]',
639
+ ...DISABLED_CODEX_CONTEXT_FEATURES.map((feature) => `${feature} = false`),
640
+ '',
641
+ '[shell_environment_policy]',
642
+ 'inherit = "none"',
643
+ '',
644
+ ].join('\n');
645
+ }
646
+ function sourceCodexHome() {
647
+ return process.env.CODEX_HOME && process.env.CODEX_HOME.trim()
648
+ ? process.env.CODEX_HOME
649
+ : join(homedir(), '.codex');
650
+ }
651
+ async function copyCodexAuth(sourceHome, targetHome) {
652
+ try {
653
+ await copyFile(join(sourceHome, 'auth.json'), join(targetHome, 'auth.json'));
654
+ }
655
+ catch {
656
+ // Let Codex surface its normal auth error if the user has no local auth state.
657
+ }
658
+ }
659
+ async function readTopLevelStringConfig(sourceHome, key) {
660
+ let text;
661
+ try {
662
+ text = await readFile(join(sourceHome, 'config.toml'), 'utf8');
663
+ }
664
+ catch {
665
+ return undefined;
666
+ }
667
+ const match = new RegExp(`^${escapeRegExp(key)}\\s*=\\s*"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"\\s*$`, 'm').exec(text);
668
+ if (!match?.[1])
669
+ return undefined;
670
+ return match[1].replace(/\\(["\\])/g, '$1');
671
+ }
672
+ function tomlString(value) {
673
+ return JSON.stringify(value);
674
+ }
675
+ function escapeRegExp(value) {
676
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
677
+ }
678
+ function asRecord(value) {
679
+ if (!value || typeof value !== 'object' || Array.isArray(value))
680
+ return null;
681
+ return value;
682
+ }
683
+ function readNumber(value) {
684
+ return typeof value === 'number' && Number.isFinite(value) ? value : 0;
685
+ }
@@ -0,0 +1,10 @@
1
+ export declare class AsyncQueue<T> implements AsyncIterable<T> {
2
+ private readonly values;
3
+ private readonly resolvers;
4
+ private error;
5
+ private closed;
6
+ push(value: T): void;
7
+ fail(err: Error): void;
8
+ close(): void;
9
+ [Symbol.asyncIterator](): AsyncIterator<T>;
10
+ }