@codex-native/sdk 0.0.1 → 0.0.2

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/dist/cli.cjs ADDED
@@ -0,0 +1,3507 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // node_modules/tsup/assets/cjs_shims.js
27
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
28
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
29
+
30
+ // src/cli/index.ts
31
+ var import_node_process3 = __toESM(require("process"));
32
+ var import_node_util2 = require("util");
33
+ var import_node_url4 = require("url");
34
+
35
+ // package.json
36
+ var package_default = {
37
+ name: "@codex-native/sdk",
38
+ version: "0.0.2",
39
+ description: "Native NAPI-based Codex SDK - complete standalone implementation.",
40
+ main: "dist/index.cjs",
41
+ module: "dist/index.mjs",
42
+ types: "dist/index.d.ts",
43
+ exports: {
44
+ ".": {
45
+ types: "./dist/index.d.ts",
46
+ import: "./dist/index.mjs",
47
+ require: "./dist/index.cjs"
48
+ }
49
+ },
50
+ bin: {
51
+ "codex-native": "dist/cli.cjs"
52
+ },
53
+ files: [
54
+ "dist",
55
+ "README.md",
56
+ "npm",
57
+ "*.node"
58
+ ],
59
+ dependencies: {
60
+ "@modelcontextprotocol/sdk": "^1.22.0",
61
+ "@opencode-ai/sdk": "^1.0.68",
62
+ "@toon-format/toon": "^1.0.0",
63
+ pyright: "^1.1.386",
64
+ "typescript-language-server": "^4.3.3",
65
+ "vscode-jsonrpc": "^8.2.1",
66
+ "vscode-languageserver-types": "^3.17.5"
67
+ },
68
+ scripts: {
69
+ clean: "rm -rf dist *.node npm target/release/*.node && cargo clean --release",
70
+ "clean:all": "rm -rf dist *.node npm target node_modules && cargo clean",
71
+ build: "npm run build:rust && npm run build:napi && npm run build:ts && npm run verify:build",
72
+ "build:ci": "npm run build:napi && npm run build:ts && npm run verify:build",
73
+ "build:rust": "cargo fmt && cargo clippy --release --features napi-bindings -- -D warnings",
74
+ "build:napi": "napi build --platform --release --features napi-bindings",
75
+ "build:napi:debug": "napi build --platform --features napi-bindings",
76
+ "build:ts": "tsup",
77
+ "verify:build": `node -e "const fs=require('fs');const hasNode=fs.readdirSync('.').some(f=>f.endsWith('.node'));const hasDist=fs.existsSync('dist/index.mjs')&&fs.existsSync('dist/index.d.ts');if(!hasNode||!hasDist){console.error('Build incomplete: missing .node or dist files');process.exit(1)}console.log('\u2713 Build verification passed')"`,
78
+ "create-npm-dirs": "napi create-npm-dirs",
79
+ artifacts: "napi artifacts",
80
+ prepublishOnly: "napi prepublish -t npm",
81
+ pretest: `node -e "const fs=require('fs');const hasNode=fs.readdirSync('.').some(f=>f.endsWith('.node'));if(!hasNode){process.exitCode=1;console.error('missing .node binary; run npm run build:ci');} else {console.log('\u2713 native binary present; skipping rebuild');}"`,
82
+ test: "node ./scripts/run-jest.mjs --runInBand",
83
+ "test:watch": "node ./scripts/run-jest.mjs --watch",
84
+ coverage: "node ./scripts/run-jest.mjs --coverage",
85
+ typecheck: "tsc --noEmit",
86
+ lint: "cargo fmt --check && cargo clippy --release --features napi-bindings -- -D warnings && npm run typecheck",
87
+ version: "napi version",
88
+ "release:patch": "npm version patch && npm run release",
89
+ "release:minor": "npm version minor && npm run release",
90
+ "release:major": "npm version major && npm run release",
91
+ "publish:platforms": "npm run create-npm-dirs && node scripts/publish-platform-packages.mjs",
92
+ release: "npm run build && npm run test && npm run publish:platforms && node scripts/publish-sdk.mjs",
93
+ "release:dry": "npm run build && npm run test && npm publish --dry-run"
94
+ },
95
+ napi: {
96
+ binaryName: "codex_native",
97
+ targets: [
98
+ "aarch64-apple-darwin",
99
+ "x86_64-apple-darwin",
100
+ "aarch64-unknown-linux-gnu",
101
+ "x86_64-unknown-linux-gnu",
102
+ "aarch64-unknown-linux-musl",
103
+ "x86_64-unknown-linux-musl",
104
+ "aarch64-pc-windows-msvc",
105
+ "x86_64-pc-windows-msvc"
106
+ ]
107
+ },
108
+ optionalDependencies: {
109
+ "@codex-native/sdk-darwin-arm64": "0.0.2",
110
+ "@codex-native/sdk-darwin-x64": "0.0.2",
111
+ "@codex-native/sdk-linux-arm64-gnu": "0.0.2",
112
+ "@codex-native/sdk-linux-arm64-musl": "0.0.2",
113
+ "@codex-native/sdk-linux-x64-gnu": "0.0.2",
114
+ "@codex-native/sdk-linux-x64-musl": "0.0.2",
115
+ "@codex-native/sdk-win32-arm64-msvc": "0.0.2",
116
+ "@codex-native/sdk-win32-x64-msvc": "0.0.2"
117
+ },
118
+ engines: {
119
+ node: ">=18"
120
+ },
121
+ publishConfig: {
122
+ access: "public"
123
+ },
124
+ devDependencies: {
125
+ "@babel/core": "7.25.2",
126
+ "@babel/preset-env": "7.25.4",
127
+ "@babel/preset-typescript": "7.28.5",
128
+ "@jest/globals": "^29.7.0",
129
+ "@napi-rs/cli": "^3.4.1",
130
+ "@openai/agents": "^0.3.0",
131
+ "@types/jest": "29.5.11",
132
+ "@types/node": "^22.0.0",
133
+ "babel-jest": "29.7.0",
134
+ jest: "^29.7.0",
135
+ "ts-jest": "^29.1.1",
136
+ tsup: "^8.5.0",
137
+ tsx: "^4.20.6",
138
+ typescript: "^5.7.2",
139
+ zod: "^3.25.76"
140
+ }
141
+ };
142
+
143
+ // src/cli/config.ts
144
+ var import_node_fs = __toESM(require("fs"));
145
+ var import_promises = __toESM(require("fs/promises"));
146
+ var import_node_path = __toESM(require("path"));
147
+ var import_node_module = require("module");
148
+ var import_node_url = require("url");
149
+ var requireFromThisModule = (0, import_node_module.createRequire)(importMetaUrl);
150
+ var CONFIG_CANDIDATES = [
151
+ "codex.config.js",
152
+ "codex.config.cjs",
153
+ "codex.config.mjs",
154
+ "codex.config.ts",
155
+ "codex.js",
156
+ ".codexrc.js",
157
+ ".codexrc.cjs",
158
+ ".codexrc.mjs",
159
+ ".codexrc.json"
160
+ ];
161
+ async function loadCliConfig(options) {
162
+ const warnings = [];
163
+ const discovery = await resolveConfigPath(options);
164
+ const configPath = discovery?.path ?? null;
165
+ let config = null;
166
+ if (discovery) {
167
+ const loadResult = await loadConfig(discovery, warnings);
168
+ config = loadResult ?? null;
169
+ if (config && typeof config !== "object") {
170
+ warnings.push(
171
+ `Config at ${discovery.path} must export an object. Received ${typeof config}.`
172
+ );
173
+ config = null;
174
+ }
175
+ }
176
+ const plugins = await resolvePlugins({
177
+ config,
178
+ configPath,
179
+ cliPluginPaths: options.pluginPaths ?? [],
180
+ cwd: options.cwd,
181
+ warnings
182
+ });
183
+ return {
184
+ configPath,
185
+ config,
186
+ plugins,
187
+ warnings
188
+ };
189
+ }
190
+ async function resolveConfigPath(options) {
191
+ if (options.explicitConfigPath) {
192
+ const explicitPath = import_node_path.default.resolve(options.cwd, options.explicitConfigPath);
193
+ if (!import_node_fs.default.existsSync(explicitPath)) {
194
+ throw new Error(`Config file not found at ${explicitPath}`);
195
+ }
196
+ return classifyPath(explicitPath);
197
+ }
198
+ if (options.noConfig) {
199
+ return null;
200
+ }
201
+ let currentDir = import_node_path.default.resolve(options.cwd);
202
+ const visited = /* @__PURE__ */ new Set();
203
+ while (!visited.has(currentDir)) {
204
+ visited.add(currentDir);
205
+ for (const candidate of CONFIG_CANDIDATES) {
206
+ const candidatePath = import_node_path.default.join(currentDir, candidate);
207
+ if (import_node_fs.default.existsSync(candidatePath) && import_node_fs.default.statSync(candidatePath).isFile()) {
208
+ return classifyPath(candidatePath);
209
+ }
210
+ }
211
+ const packageJsonPath = import_node_path.default.join(currentDir, "package.json");
212
+ if (import_node_fs.default.existsSync(packageJsonPath) && import_node_fs.default.statSync(packageJsonPath).isFile()) {
213
+ const manifest = await readJson(packageJsonPath);
214
+ if (manifest && manifest.codexNative != null) {
215
+ return { path: packageJsonPath, type: "package-json", field: "codexNative" };
216
+ }
217
+ }
218
+ const parentDir = import_node_path.default.dirname(currentDir);
219
+ if (parentDir === currentDir) {
220
+ break;
221
+ }
222
+ currentDir = parentDir;
223
+ }
224
+ return null;
225
+ }
226
+ async function loadConfig(discovery, warnings) {
227
+ if (discovery.type === "package-json") {
228
+ const manifest = await readJson(discovery.path);
229
+ if (!manifest) {
230
+ warnings.push(`Failed to parse ${discovery.path}; ignoring config.`);
231
+ return null;
232
+ }
233
+ const raw = manifest[discovery.field];
234
+ if (typeof raw === "string") {
235
+ const baseDir = import_node_path.default.dirname(discovery.path);
236
+ const nestedPath = import_node_path.default.resolve(baseDir, raw);
237
+ if (!import_node_fs.default.existsSync(nestedPath)) {
238
+ throw new Error(
239
+ `Config path "${raw}" referenced by ${discovery.field} in ${discovery.path} was not found.`
240
+ );
241
+ }
242
+ return loadConfig({ path: nestedPath, type: "file" }, warnings);
243
+ }
244
+ if (typeof raw === "object" && raw !== null) {
245
+ return raw;
246
+ }
247
+ warnings.push(
248
+ `The ${discovery.field} field in ${discovery.path} must be an object or path string.`
249
+ );
250
+ return null;
251
+ }
252
+ const ext = import_node_path.default.extname(discovery.path).toLowerCase();
253
+ if (ext === ".json") {
254
+ const json = await readJson(discovery.path);
255
+ if (json === null) {
256
+ warnings.push(`Failed to parse JSON config at ${discovery.path}`);
257
+ }
258
+ return json;
259
+ }
260
+ if (ext === ".js" || ext === ".cjs") {
261
+ return extractModuleDefault(await loadCommonJsModule(discovery.path));
262
+ }
263
+ if (ext === ".mjs") {
264
+ return extractModuleDefault(await importModule(discovery.path));
265
+ }
266
+ if (ext === ".ts") {
267
+ return extractModuleDefault(await loadTypeScriptModule(discovery.path, warnings));
268
+ }
269
+ throw new Error(`Unsupported config extension "${ext}" at ${discovery.path}`);
270
+ }
271
+ async function resolvePlugins(params) {
272
+ const plugins = [];
273
+ const { config, configPath, cliPluginPaths, cwd, warnings } = params;
274
+ const configDir = configPath ? import_node_path.default.dirname(configPath) : cwd;
275
+ const rawConfigPlugins = config?.plugins;
276
+ const configPlugins = Array.isArray(rawConfigPlugins) ? rawConfigPlugins : [];
277
+ for (const spec of configPlugins) {
278
+ if (typeof spec === "string") {
279
+ const loaded = await loadPlugin(spec, configDir, "config", warnings);
280
+ if (loaded) {
281
+ plugins.push(loaded);
282
+ }
283
+ } else if (spec != null) {
284
+ plugins.push({
285
+ source: "config",
286
+ spec: "<inline>",
287
+ plugin: spec
288
+ });
289
+ }
290
+ }
291
+ for (const spec of cliPluginPaths) {
292
+ const loaded = await loadPlugin(spec, cwd, "cli", warnings);
293
+ if (loaded) {
294
+ plugins.push(loaded);
295
+ }
296
+ }
297
+ return plugins;
298
+ }
299
+ async function loadPlugin(spec, baseDir, source, warnings) {
300
+ try {
301
+ const resolved = resolveModule(spec, baseDir);
302
+ const moduleExports = await loadModuleForPath(resolved);
303
+ return {
304
+ source,
305
+ spec,
306
+ resolvedPath: resolved,
307
+ plugin: extractModuleDefault(moduleExports)
308
+ };
309
+ } catch (err) {
310
+ warnings.push(`Failed to load plugin "${spec}": ${err.message}`);
311
+ return null;
312
+ }
313
+ }
314
+ async function loadModuleForPath(modulePath) {
315
+ const ext = import_node_path.default.extname(modulePath).toLowerCase();
316
+ if (ext === ".cjs") {
317
+ return loadCommonJsModule(modulePath);
318
+ }
319
+ if (ext === ".mjs") {
320
+ return importModule(modulePath);
321
+ }
322
+ if (ext === ".ts") {
323
+ return loadTypeScriptModule(modulePath);
324
+ }
325
+ if (ext === ".json") {
326
+ return readJson(modulePath);
327
+ }
328
+ if (ext === ".js") {
329
+ try {
330
+ return loadCommonJsModule(modulePath);
331
+ } catch (err) {
332
+ if (err instanceof Error && err.message.includes("ERR_REQUIRE_ESM")) {
333
+ return importModule(modulePath);
334
+ }
335
+ throw err;
336
+ }
337
+ }
338
+ return loadCommonJsModule(modulePath);
339
+ }
340
+ async function loadCommonJsModule(modulePath) {
341
+ return requireFromThisModule(modulePath);
342
+ }
343
+ async function importModule(modulePath) {
344
+ const href = (0, import_node_url.pathToFileURL)(modulePath).href;
345
+ return import(href);
346
+ }
347
+ async function loadTypeScriptModule(modulePath, warnings) {
348
+ try {
349
+ const { register } = requireFromThisModule("tsx/cjs/api");
350
+ const unregister = register({ transpileOnly: true });
351
+ try {
352
+ return requireFromThisModule(modulePath);
353
+ } finally {
354
+ await maybeCall(unregister);
355
+ }
356
+ } catch (cjsError) {
357
+ try {
358
+ const tsxEsmSpecifier = "tsx/esm/api";
359
+ const apiModule = await import(tsxEsmSpecifier);
360
+ const unregister = typeof apiModule.register === "function" ? apiModule.register({ transpileOnly: true }) : apiModule.default({ transpileOnly: true });
361
+ try {
362
+ return importModule(modulePath);
363
+ } finally {
364
+ await maybeCall(unregister);
365
+ }
366
+ } catch (esmError) {
367
+ const message = [
368
+ `Failed to load TypeScript module ${modulePath}.`,
369
+ 'Install the "tsx" package or convert the config to JavaScript.'
370
+ ].join(" ");
371
+ if (warnings) {
372
+ warnings.push(message);
373
+ return null;
374
+ }
375
+ throw new Error(message);
376
+ }
377
+ }
378
+ }
379
+ async function readJson(filePath) {
380
+ try {
381
+ const raw = await import_promises.default.readFile(filePath, "utf8");
382
+ return JSON.parse(raw);
383
+ } catch {
384
+ return null;
385
+ }
386
+ }
387
+ function extractModuleDefault(module2) {
388
+ if (module2 && typeof module2 === "object" && "default" in module2) {
389
+ const value = module2.default;
390
+ if (value !== void 0) {
391
+ return value;
392
+ }
393
+ }
394
+ return module2;
395
+ }
396
+ function resolveModule(specifier, baseDir) {
397
+ if (import_node_path.default.isAbsolute(specifier)) {
398
+ return specifier;
399
+ }
400
+ return requireFromThisModule.resolve(specifier, { paths: [baseDir] });
401
+ }
402
+ function classifyPath(filePath) {
403
+ if (import_node_path.default.basename(filePath) === "package.json") {
404
+ return { path: filePath, type: "package-json", field: "codexNative" };
405
+ }
406
+ return { path: filePath, type: "file" };
407
+ }
408
+ async function maybeCall(candidate) {
409
+ if (typeof candidate === "function") {
410
+ await Promise.resolve(candidate());
411
+ }
412
+ }
413
+
414
+ // src/cli/run.ts
415
+ var import_node_fs3 = __toESM(require("fs"));
416
+ var import_promises2 = __toESM(require("fs/promises"));
417
+ var import_node_path4 = __toESM(require("path"));
418
+ var import_node_process = __toESM(require("process"));
419
+
420
+ // src/nativeBinding.ts
421
+ var import_node_fs2 = __toESM(require("fs"));
422
+ var import_node_module2 = require("module");
423
+ var import_node_path2 = __toESM(require("path"));
424
+ var import_node_url2 = require("url");
425
+ var CLI_ENTRYPOINT_ENV = "CODEX_NODE_CLI_ENTRYPOINT";
426
+ function ensureCliEntrypointEnv() {
427
+ if (process.env[CLI_ENTRYPOINT_ENV]) {
428
+ return;
429
+ }
430
+ const filename = (0, import_node_url2.fileURLToPath)(importMetaUrl);
431
+ const dirname3 = import_node_path2.default.dirname(filename);
432
+ const candidates = [
433
+ import_node_path2.default.resolve(dirname3, "cli.cjs"),
434
+ import_node_path2.default.resolve(dirname3, "../cli.cjs"),
435
+ import_node_path2.default.resolve(dirname3, "../dist/cli.cjs")
436
+ ];
437
+ for (const candidate of candidates) {
438
+ if (import_node_fs2.default.existsSync(candidate)) {
439
+ process.env[CLI_ENTRYPOINT_ENV] = candidate;
440
+ break;
441
+ }
442
+ }
443
+ }
444
+ ensureCliEntrypointEnv();
445
+ var cachedBinding;
446
+ function getImportMetaUrl2() {
447
+ try {
448
+ return Function(
449
+ "return typeof import.meta !== 'undefined' && import.meta.url ? import.meta.url : undefined;"
450
+ )();
451
+ } catch {
452
+ return void 0;
453
+ }
454
+ }
455
+ function resolveBindingEntryPath() {
456
+ if (typeof __dirname === "string") {
457
+ return import_node_path2.default.resolve(__dirname, "..", "index.js");
458
+ }
459
+ const importMetaUrl2 = getImportMetaUrl2();
460
+ if (importMetaUrl2) {
461
+ try {
462
+ const filePath = (0, import_node_url2.fileURLToPath)(importMetaUrl2);
463
+ return import_node_path2.default.resolve(import_node_path2.default.dirname(filePath), "..", "index.js");
464
+ } catch {
465
+ }
466
+ }
467
+ return import_node_path2.default.resolve(process.cwd(), "index.js");
468
+ }
469
+ function resolveRequire() {
470
+ const globalRequire = globalThis.require;
471
+ if (typeof globalRequire === "function") {
472
+ return globalRequire;
473
+ }
474
+ if (typeof __filename === "string") {
475
+ try {
476
+ return (0, import_node_module2.createRequire)(__filename);
477
+ } catch {
478
+ }
479
+ }
480
+ const importMetaUrl2 = getImportMetaUrl2();
481
+ if (importMetaUrl2) {
482
+ try {
483
+ return (0, import_node_module2.createRequire)(importMetaUrl2);
484
+ } catch {
485
+ }
486
+ }
487
+ const fallbackBase = typeof __dirname === "string" ? __dirname : process.cwd();
488
+ const fallbackPath = import_node_path2.default.join(fallbackBase, "noop.js");
489
+ return (0, import_node_module2.createRequire)(fallbackPath);
490
+ }
491
+ function getNativeBinding() {
492
+ if (cachedBinding !== void 0) {
493
+ return cachedBinding;
494
+ }
495
+ const requireFn = resolveRequire();
496
+ const envPath = process.env.CODEX_NATIVE_BINDING;
497
+ if (envPath && envPath.length > 0) {
498
+ process.env.NAPI_RS_NATIVE_LIBRARY_PATH = envPath;
499
+ }
500
+ const bindingEntryPath = resolveBindingEntryPath();
501
+ try {
502
+ const binding = requireFn(bindingEntryPath);
503
+ binding.ensureTokioRuntime?.();
504
+ cachedBinding = binding;
505
+ return cachedBinding;
506
+ } catch (error) {
507
+ console.warn("Failed to load native NAPI binding:", error);
508
+ cachedBinding = null;
509
+ return cachedBinding;
510
+ }
511
+ }
512
+ function runApplyPatch(patch) {
513
+ if (!patch) {
514
+ throw new Error("apply_patch requires patch contents");
515
+ }
516
+ const binding = getNativeBinding();
517
+ if (!binding) throw new Error("Native binding not available");
518
+ binding.runApplyPatch(patch);
519
+ }
520
+ async function reverieIndexSemantic(codexHomePath, options) {
521
+ const binding = getNativeBinding();
522
+ if (!binding?.reverieIndexSemantic) throw new Error("Native binding not available or reverie functions not supported");
523
+ return binding.reverieIndexSemantic(codexHomePath, options);
524
+ }
525
+ async function fastEmbedInit(options) {
526
+ const binding = getNativeBinding();
527
+ if (!binding?.fastEmbedInit) throw new Error("Native binding not available or FastEmbed functions not supported");
528
+ await binding.fastEmbedInit(options);
529
+ }
530
+
531
+ // src/events/convert.ts
532
+ function convertRustEventToThreadEvent(rustEvent) {
533
+ if (rustEvent?.ThreadStarted) {
534
+ return {
535
+ type: "thread.started",
536
+ thread_id: rustEvent.ThreadStarted.thread_id
537
+ };
538
+ }
539
+ if (rustEvent?.TurnStarted) {
540
+ return { type: "turn.started" };
541
+ }
542
+ if (rustEvent?.TurnCompleted) {
543
+ return {
544
+ type: "turn.completed",
545
+ usage: rustEvent.TurnCompleted.usage
546
+ };
547
+ }
548
+ if (rustEvent?.TurnFailed) {
549
+ return {
550
+ type: "turn.failed",
551
+ error: rustEvent.TurnFailed.error
552
+ };
553
+ }
554
+ if (rustEvent?.ItemStarted) {
555
+ return {
556
+ type: "item.started",
557
+ item: rustEvent.ItemStarted.item
558
+ };
559
+ }
560
+ if (rustEvent?.ItemUpdated) {
561
+ return {
562
+ type: "item.updated",
563
+ item: rustEvent.ItemUpdated.item
564
+ };
565
+ }
566
+ if (rustEvent?.ItemCompleted) {
567
+ return {
568
+ type: "item.completed",
569
+ item: rustEvent.ItemCompleted.item
570
+ };
571
+ }
572
+ if (rustEvent?.Error) {
573
+ return {
574
+ type: "error",
575
+ message: rustEvent.Error.message
576
+ };
577
+ }
578
+ if (rustEvent?.BackgroundEvent) {
579
+ return {
580
+ type: "background_event",
581
+ message: rustEvent.BackgroundEvent.message
582
+ };
583
+ }
584
+ if (rustEvent?.type === "background_event" && typeof rustEvent.message === "string") {
585
+ return {
586
+ type: "background_event",
587
+ message: rustEvent.message
588
+ };
589
+ }
590
+ if (rustEvent?.type === "plan_update_scheduled" && rustEvent.plan) {
591
+ const planData = rustEvent.plan;
592
+ const planItems = planData.plan || [];
593
+ return {
594
+ type: "item.completed",
595
+ item: {
596
+ id: `plan-${Date.now()}`,
597
+ type: "todo_list",
598
+ items: planItems.map((item) => ({
599
+ text: item.step,
600
+ completed: item.status === "completed"
601
+ }))
602
+ }
603
+ };
604
+ }
605
+ if (rustEvent?.type) {
606
+ return rustEvent;
607
+ }
608
+ return rustEvent;
609
+ }
610
+
611
+ // src/cli/optionParsers.ts
612
+ var SANDBOX_MODE_VALUES = ["read-only", "workspace-write", "danger-full-access"];
613
+ var APPROVAL_MODE_VALUES = ["never", "on-request", "on-failure", "untrusted"];
614
+ function isSandboxMode(value) {
615
+ return SANDBOX_MODE_VALUES.includes(value);
616
+ }
617
+ function isApprovalMode(value) {
618
+ return APPROVAL_MODE_VALUES.includes(value);
619
+ }
620
+ function parseSandboxModeFlag(value, origin) {
621
+ if (value === void 0) {
622
+ return void 0;
623
+ }
624
+ if (isSandboxMode(value)) {
625
+ return value;
626
+ }
627
+ throw new Error(
628
+ `Invalid sandbox mode "${value}" from ${origin}. Valid values: ${SANDBOX_MODE_VALUES.join(
629
+ ", "
630
+ )}.`
631
+ );
632
+ }
633
+ function parseApprovalModeFlag(value, origin) {
634
+ if (value === void 0) {
635
+ return void 0;
636
+ }
637
+ if (isApprovalMode(value)) {
638
+ return value;
639
+ }
640
+ throw new Error(
641
+ `Invalid approval mode "${value}" from ${origin}. Valid values: ${APPROVAL_MODE_VALUES.join(
642
+ ", "
643
+ )}.`
644
+ );
645
+ }
646
+
647
+ // src/cli/hooks.ts
648
+ function emitWarnings(warnings, fromIndex = 0) {
649
+ for (let i = fromIndex; i < warnings.length; i += 1) {
650
+ const message = warnings[i];
651
+ process.stderr.write(`[codex-native] Warning: ${message}
652
+ `);
653
+ }
654
+ }
655
+ async function runBeforeStartHooks(hooks, context, warnings) {
656
+ for (const hook of hooks) {
657
+ try {
658
+ await hook.callback(context);
659
+ } catch (error) {
660
+ warnings.push(
661
+ `beforeStart hook "${hook.source}" threw: ${error.message ?? String(error)}`
662
+ );
663
+ }
664
+ }
665
+ }
666
+ async function runEventHooks(hooks, event, context, warnings) {
667
+ for (const hook of hooks) {
668
+ try {
669
+ await hook.callback(event, context);
670
+ } catch (error) {
671
+ warnings.push(`onEvent hook "${hook.source}" threw: ${error.message ?? String(error)}`);
672
+ }
673
+ }
674
+ }
675
+
676
+ // src/cli/elevatedDefaults.ts
677
+ var import_node_path3 = __toESM(require("path"));
678
+ var FULL_ACCESS_SANDBOX = "workspace-write";
679
+ var FULL_ACCESS_APPROVAL = "never";
680
+ function applyElevatedRunDefaults(request, cwd) {
681
+ const workingDirectory = resolveWorkingDirectory(request.workingDirectory, cwd);
682
+ request.workingDirectory = workingDirectory;
683
+ ensureSandboxModes(request);
684
+ request.workspaceWriteOptions = ensureWorkspaceWriteOptions(
685
+ request.workspaceWriteOptions,
686
+ workingDirectory
687
+ );
688
+ }
689
+ function applyElevatedTuiDefaults(params) {
690
+ const { request, thread, cwd } = params;
691
+ const workingDirectory = resolveWorkingDirectory(
692
+ request.workingDirectory ?? thread.workingDirectory,
693
+ cwd
694
+ );
695
+ request.workingDirectory = workingDirectory;
696
+ thread.workingDirectory = workingDirectory;
697
+ ensureSandboxModes(request);
698
+ thread.sandboxMode = request.sandboxMode ?? thread.sandboxMode ?? FULL_ACCESS_SANDBOX;
699
+ thread.approvalMode = request.approvalMode ?? thread.approvalMode ?? FULL_ACCESS_APPROVAL;
700
+ thread.workspaceWriteOptions = ensureWorkspaceWriteOptions(
701
+ thread.workspaceWriteOptions,
702
+ workingDirectory
703
+ );
704
+ }
705
+ function ensureSandboxModes(target) {
706
+ if (!target.sandboxMode) {
707
+ target.sandboxMode = FULL_ACCESS_SANDBOX;
708
+ }
709
+ if (!target.approvalMode) {
710
+ target.approvalMode = FULL_ACCESS_APPROVAL;
711
+ }
712
+ }
713
+ function ensureWorkspaceWriteOptions(options, workingDirectory) {
714
+ const resolved = import_node_path3.default.resolve(workingDirectory);
715
+ const writableRoots = new Set(options?.writableRoots ?? []);
716
+ writableRoots.add(resolved);
717
+ return {
718
+ ...options,
719
+ networkAccess: options?.networkAccess ?? true,
720
+ writableRoots: Array.from(writableRoots)
721
+ };
722
+ }
723
+ function resolveWorkingDirectory(candidate, cwd) {
724
+ if (!candidate || candidate.trim().length === 0) {
725
+ return import_node_path3.default.resolve(cwd);
726
+ }
727
+ return import_node_path3.default.isAbsolute(candidate) ? candidate : import_node_path3.default.resolve(cwd, candidate);
728
+ }
729
+
730
+ // src/lsp/bridge.ts
731
+ var path8 = __toESM(require("path"));
732
+
733
+ // src/lsp/manager.ts
734
+ var path6 = __toESM(require("path"));
735
+
736
+ // src/lsp/servers.ts
737
+ var fs3 = __toESM(require("fs"));
738
+ var path4 = __toESM(require("path"));
739
+ var MARKERS_NODE = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb", "bun.lock"];
740
+ var MARKERS_PY = ["pyproject.toml", "requirements.txt", "Pipfile", "setup.py", "setup.cfg", "poetry.lock"];
741
+ var MARKERS_RUST = ["Cargo.toml"];
742
+ var DEFAULT_SERVERS = [
743
+ {
744
+ id: "typescript",
745
+ displayName: "TypeScript Language Server",
746
+ command: ["typescript-language-server", "--stdio"],
747
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
748
+ workspace: { type: "markers", include: MARKERS_NODE }
749
+ },
750
+ {
751
+ id: "pyright",
752
+ displayName: "Pyright",
753
+ command: ["pyright-langserver", "--stdio"],
754
+ extensions: [".py", ".pyi"],
755
+ workspace: { type: "markers", include: MARKERS_PY }
756
+ },
757
+ {
758
+ id: "rust-analyzer",
759
+ displayName: "rust-analyzer",
760
+ command: ["rust-analyzer"],
761
+ extensions: [".rs"],
762
+ workspace: { type: "markers", include: MARKERS_RUST }
763
+ }
764
+ ];
765
+ function findServerForFile(filePath) {
766
+ const lower = filePath.toLowerCase();
767
+ return DEFAULT_SERVERS.find((server) => server.extensions.some((ext) => lower.endsWith(ext)));
768
+ }
769
+ function resolveWorkspaceRoot(filePath, locator, fallbackDir) {
770
+ if (!locator) {
771
+ return fallbackDir;
772
+ }
773
+ if (locator.type === "fixed") {
774
+ return locator.path;
775
+ }
776
+ const include = locator.include ?? [];
777
+ const exclude = locator.exclude ?? [];
778
+ let current = fs3.statSync(filePath, { throwIfNoEntry: false })?.isDirectory() ? filePath : path4.dirname(filePath);
779
+ const root = path4.parse(current).root;
780
+ while (true) {
781
+ if (exclude.some((pattern) => fs3.existsSync(path4.join(current, pattern)))) {
782
+ break;
783
+ }
784
+ if (include.some((pattern) => fs3.existsSync(path4.join(current, pattern)))) {
785
+ return current;
786
+ }
787
+ if (current === root) {
788
+ break;
789
+ }
790
+ const parent = path4.dirname(current);
791
+ if (parent === current) {
792
+ break;
793
+ }
794
+ current = parent;
795
+ }
796
+ return fallbackDir;
797
+ }
798
+
799
+ // src/lsp/client.ts
800
+ var import_node_child_process = require("child_process");
801
+ var fs4 = __toESM(require("fs/promises"));
802
+ var path5 = __toESM(require("path"));
803
+ var import_node_url3 = require("url");
804
+ var import_node_events = require("events");
805
+ var import_vscode_jsonrpc = require("vscode-jsonrpc");
806
+ var import_main = require("vscode-jsonrpc/lib/node/main.js");
807
+ var import_vscode_languageserver_types = require("vscode-languageserver-types");
808
+ var DEFAULT_TIMEOUT_MS = 3e3;
809
+ var LspClient = class _LspClient {
810
+ constructor(config, root) {
811
+ this.config = config;
812
+ this.root = root;
813
+ }
814
+ connection = null;
815
+ process = null;
816
+ diagnostics = /* @__PURE__ */ new Map();
817
+ versions = /* @__PURE__ */ new Map();
818
+ emitter = new import_node_events.EventEmitter();
819
+ static async start(server, root) {
820
+ const client = new _LspClient(server, root);
821
+ await client.initialize();
822
+ return client;
823
+ }
824
+ async initialize() {
825
+ const [command, ...args] = this.config.command;
826
+ if (!command) {
827
+ throw new Error(`LSP server ${this.config.id} is missing a command executable`);
828
+ }
829
+ try {
830
+ this.process = (0, import_node_child_process.spawn)(command, args, {
831
+ cwd: this.root,
832
+ env: { ...process.env, ...this.config.env },
833
+ stdio: "pipe"
834
+ });
835
+ } catch (error) {
836
+ throw new Error(`Failed to spawn ${this.config.displayName} (${command}): ${String(error)}`);
837
+ }
838
+ const child = this.process;
839
+ child.stderr.on("data", (_chunk) => {
840
+ });
841
+ const reader = new import_main.StreamMessageReader(child.stdout);
842
+ const writer = new import_main.StreamMessageWriter(child.stdin);
843
+ this.connection = (0, import_vscode_jsonrpc.createMessageConnection)(reader, writer);
844
+ this.connection.onNotification("textDocument/publishDiagnostics", (payload) => {
845
+ const fsPath = (0, import_node_url3.fileURLToPath)(payload.uri);
846
+ this.diagnostics.set(fsPath, payload.diagnostics);
847
+ this.emitter.emit(`diagnostics:${fsPath}`);
848
+ });
849
+ this.connection.onError((err) => {
850
+ console.warn(`[lsp:${this.config.id}] connection error`, err);
851
+ });
852
+ this.connection.listen();
853
+ await this.connection.sendRequest("initialize", {
854
+ rootUri: (0, import_node_url3.pathToFileURL)(this.root).href,
855
+ processId: process.pid,
856
+ initializationOptions: this.config.initializationOptions ?? {},
857
+ capabilities: {
858
+ textDocument: {
859
+ synchronization: {
860
+ didOpen: true,
861
+ didChange: true
862
+ },
863
+ publishDiagnostics: {
864
+ versionSupport: true
865
+ }
866
+ },
867
+ workspace: {
868
+ workspaceFolders: true
869
+ }
870
+ },
871
+ workspaceFolders: [
872
+ {
873
+ name: path5.basename(this.root),
874
+ uri: (0, import_node_url3.pathToFileURL)(this.root).href
875
+ }
876
+ ]
877
+ });
878
+ await this.connection.sendNotification("initialized", {});
879
+ }
880
+ async openFile(filePath, waitForDiagnostics) {
881
+ if (!this.connection) return;
882
+ const absolute = path5.resolve(filePath);
883
+ const text = await fs4.readFile(absolute, "utf8");
884
+ const uri = (0, import_node_url3.pathToFileURL)(absolute).href;
885
+ const languageId = detectLanguageId(absolute);
886
+ const existingVersion = this.versions.get(absolute);
887
+ if (existingVersion === void 0) {
888
+ this.versions.set(absolute, 0);
889
+ await this.connection.sendNotification("textDocument/didOpen", {
890
+ textDocument: {
891
+ uri,
892
+ languageId,
893
+ version: 0,
894
+ text
895
+ }
896
+ });
897
+ } else {
898
+ const next = existingVersion + 1;
899
+ this.versions.set(absolute, next);
900
+ await this.connection.sendNotification("textDocument/didChange", {
901
+ textDocument: {
902
+ uri,
903
+ version: next
904
+ },
905
+ contentChanges: [{ text }]
906
+ });
907
+ }
908
+ if (waitForDiagnostics) {
909
+ await this.waitForDiagnostics(absolute);
910
+ }
911
+ }
912
+ getDiagnostics(filePath) {
913
+ const absolute = path5.resolve(filePath);
914
+ return this.diagnostics.get(absolute) ?? [];
915
+ }
916
+ waitForDiagnostics(filePath, timeoutMs = DEFAULT_TIMEOUT_MS) {
917
+ const absolute = path5.resolve(filePath);
918
+ return new Promise((resolve5) => {
919
+ const timer = setTimeout(resolve5, timeoutMs).unref();
920
+ this.emitter.once(`diagnostics:${absolute}`, () => {
921
+ clearTimeout(timer);
922
+ resolve5();
923
+ });
924
+ });
925
+ }
926
+ async shutdown() {
927
+ try {
928
+ await this.connection?.dispose();
929
+ } catch {
930
+ }
931
+ if (this.process && !this.process.killed) {
932
+ this.process.kill();
933
+ }
934
+ }
935
+ };
936
+ function detectLanguageId(filePath) {
937
+ const ext = path5.extname(filePath).toLowerCase();
938
+ switch (ext) {
939
+ case ".ts":
940
+ case ".mts":
941
+ case ".cts":
942
+ return "typescript";
943
+ case ".tsx":
944
+ return "typescriptreact";
945
+ case ".js":
946
+ case ".mjs":
947
+ case ".cjs":
948
+ return "javascript";
949
+ case ".jsx":
950
+ return "javascriptreact";
951
+ case ".py":
952
+ case ".pyi":
953
+ return "python";
954
+ case ".rs":
955
+ return "rust";
956
+ default:
957
+ return "plaintext";
958
+ }
959
+ }
960
+ function normalizeSeverity(severity) {
961
+ switch (severity) {
962
+ case import_vscode_languageserver_types.DiagnosticSeverity.Error:
963
+ return "error";
964
+ case import_vscode_languageserver_types.DiagnosticSeverity.Warning:
965
+ return "warning";
966
+ case import_vscode_languageserver_types.DiagnosticSeverity.Information:
967
+ return "info";
968
+ case import_vscode_languageserver_types.DiagnosticSeverity.Hint:
969
+ return "hint";
970
+ default:
971
+ return "error";
972
+ }
973
+ }
974
+
975
+ // src/lsp/manager.ts
976
+ var LspManager = class {
977
+ constructor(options) {
978
+ this.options = options;
979
+ }
980
+ clients = /* @__PURE__ */ new Map();
981
+ async collectDiagnostics(files) {
982
+ const unique = Array.from(new Set(files.map((file) => path6.resolve(file))));
983
+ const results = [];
984
+ for (const filePath of unique) {
985
+ const server = findServerForFile(filePath);
986
+ if (!server) {
987
+ continue;
988
+ }
989
+ const root = resolveWorkspaceRoot(filePath, server.workspace, this.options.workingDirectory);
990
+ const client = await this.getClient(server, root);
991
+ if (!client) {
992
+ continue;
993
+ }
994
+ try {
995
+ await client.openFile(filePath, this.options.waitForDiagnostics !== false);
996
+ } catch (error) {
997
+ console.warn(`[lsp] failed to open ${filePath}:`, error);
998
+ continue;
999
+ }
1000
+ const normalized = client.getDiagnostics(filePath).map((diag) => normalizeDiagnostic(diag)).filter((diag) => diag.message.trim().length > 0);
1001
+ if (normalized.length > 0) {
1002
+ results.push({ path: filePath, diagnostics: normalized });
1003
+ }
1004
+ }
1005
+ return results;
1006
+ }
1007
+ async dispose() {
1008
+ await Promise.all(
1009
+ Array.from(this.clients.values()).map(async (promise) => {
1010
+ const client = await promise;
1011
+ await client?.shutdown();
1012
+ })
1013
+ );
1014
+ this.clients.clear();
1015
+ }
1016
+ async getClient(server, root) {
1017
+ const key = `${server.id}:${root}`;
1018
+ let existing = this.clients.get(key);
1019
+ if (!existing) {
1020
+ existing = this.createClient(server, root);
1021
+ this.clients.set(key, existing);
1022
+ }
1023
+ const client = await existing;
1024
+ if (!client) {
1025
+ this.clients.delete(key);
1026
+ }
1027
+ return client;
1028
+ }
1029
+ async createClient(server, root) {
1030
+ try {
1031
+ return await LspClient.start(server, root);
1032
+ } catch (error) {
1033
+ console.warn(`[lsp] unable to start ${server.displayName}:`, error);
1034
+ return null;
1035
+ }
1036
+ }
1037
+ };
1038
+ function normalizeDiagnostic(diag) {
1039
+ return {
1040
+ message: diag.message ?? "",
1041
+ severity: normalizeSeverity(diag.severity),
1042
+ source: diag.source,
1043
+ code: diag.code,
1044
+ range: diag.range
1045
+ };
1046
+ }
1047
+
1048
+ // src/lsp/format.ts
1049
+ var path7 = __toESM(require("path"));
1050
+ var MAX_DIAGNOSTICS_PER_FILE = 5;
1051
+ function formatDiagnosticsForTool(diagnostics) {
1052
+ return diagnostics.map(({ path: filePath, diagnostics: entries }) => {
1053
+ const rel = filePath;
1054
+ const lines = entries.slice(0, MAX_DIAGNOSTICS_PER_FILE).map((diag) => {
1055
+ const { line, character } = diag.range.start;
1056
+ const location = `${line + 1}:${character + 1}`;
1057
+ const source = diag.source ? ` \xB7 ${diag.source}` : "";
1058
+ return ` - [${diag.severity.toUpperCase()}] ${diag.message} (${location}${source})`;
1059
+ });
1060
+ const trimmed = entries.length > MAX_DIAGNOSTICS_PER_FILE ? " - \u2026" : "";
1061
+ return [`\u2022 ${rel}`, ...lines, trimmed].filter(Boolean).join("\n");
1062
+ }).join("\n");
1063
+ }
1064
+ function formatDiagnosticsForBackgroundEvent(diagnostics, cwd) {
1065
+ return diagnostics.map(({ path: filePath, diagnostics: entries }) => {
1066
+ const rel = path7.relative(cwd, filePath) || filePath;
1067
+ const lines = entries.slice(0, MAX_DIAGNOSTICS_PER_FILE).map((diag) => {
1068
+ const { line, character } = diag.range.start;
1069
+ const location = `${line + 1}:${character + 1}`;
1070
+ const source = diag.source ? ` \xB7 ${diag.source}` : "";
1071
+ return ` - [${diag.severity.toUpperCase()}] ${diag.message} (${location}${source})`;
1072
+ });
1073
+ const trimmed = entries.length > MAX_DIAGNOSTICS_PER_FILE ? " - \u2026" : "";
1074
+ return [`\u2022 ${rel}`, ...lines, trimmed].filter(Boolean).join("\n");
1075
+ }).join("\n");
1076
+ }
1077
+
1078
+ // src/lsp/bridge.ts
1079
+ var LspDiagnosticsBridge = class {
1080
+ constructor(options) {
1081
+ this.options = options;
1082
+ this.manager = new LspManager(options);
1083
+ }
1084
+ manager;
1085
+ attached = /* @__PURE__ */ new WeakSet();
1086
+ attach(thread) {
1087
+ if (this.attached.has(thread)) {
1088
+ return () => {
1089
+ };
1090
+ }
1091
+ this.attached.add(thread);
1092
+ const unsubscribe = thread.onEvent((event) => {
1093
+ if (event.type !== "item.completed") {
1094
+ return;
1095
+ }
1096
+ if (event.item.type === "file_change") {
1097
+ const targets = event.item.changes.filter((change) => change.kind !== "delete").map((change) => path8.resolve(this.options.workingDirectory, change.path));
1098
+ if (targets.length === 0) {
1099
+ return;
1100
+ }
1101
+ void this.processDiagnostics(thread, targets);
1102
+ return;
1103
+ }
1104
+ if (event.item.type === "mcp_tool_call") {
1105
+ const targets = extractReadFileTargets(event.item, this.options.workingDirectory);
1106
+ if (targets.length === 0) {
1107
+ return;
1108
+ }
1109
+ void this.processDiagnostics(thread, targets);
1110
+ }
1111
+ });
1112
+ return () => {
1113
+ this.attached.delete(thread);
1114
+ unsubscribe();
1115
+ };
1116
+ }
1117
+ async dispose() {
1118
+ await this.manager.dispose();
1119
+ }
1120
+ async processDiagnostics(thread, files) {
1121
+ try {
1122
+ const diagnostics = await this.manager.collectDiagnostics(files);
1123
+ if (diagnostics.length === 0) {
1124
+ return;
1125
+ }
1126
+ const summary = formatDiagnosticsForBackgroundEvent(
1127
+ diagnostics,
1128
+ this.options.workingDirectory
1129
+ );
1130
+ console.log(`
1131
+ \u{1F4DF} LSP diagnostics detected:
1132
+ ${summary}
1133
+ `);
1134
+ try {
1135
+ await thread.sendBackgroundEvent(`LSP diagnostics detected:
1136
+ ${summary}`);
1137
+ } catch {
1138
+ }
1139
+ } catch (error) {
1140
+ console.warn("[lsp] failed to collect diagnostics", error);
1141
+ }
1142
+ }
1143
+ };
1144
+ function extractReadFileTargets(item, cwd) {
1145
+ if (item.type !== "mcp_tool_call") {
1146
+ return [];
1147
+ }
1148
+ const toolName = item.tool?.toLowerCase?.();
1149
+ if (toolName !== "read_file" && toolName !== "read_file_v2") {
1150
+ return [];
1151
+ }
1152
+ let args = item.arguments;
1153
+ if (typeof args === "string") {
1154
+ try {
1155
+ args = JSON.parse(args);
1156
+ } catch {
1157
+ return [];
1158
+ }
1159
+ }
1160
+ if (!args || typeof args !== "object") {
1161
+ return [];
1162
+ }
1163
+ const filePath = args.file_path ?? args.path;
1164
+ if (typeof filePath !== "string" || filePath.trim().length === 0) {
1165
+ return [];
1166
+ }
1167
+ const resolved = path8.isAbsolute(filePath) ? filePath : path8.resolve(cwd, filePath);
1168
+ return [resolved];
1169
+ }
1170
+
1171
+ // src/lsp/hooks.ts
1172
+ function attachLspDiagnostics(thread, options) {
1173
+ const bridge = new LspDiagnosticsBridge(options);
1174
+ const detach = bridge.attach(thread);
1175
+ return () => {
1176
+ detach();
1177
+ void bridge.dispose().catch((error) => {
1178
+ console.warn("Failed to dispose LSP bridge", error);
1179
+ });
1180
+ };
1181
+ }
1182
+
1183
+ // src/cli/lspBridge.ts
1184
+ var RunCommandThreadRelay = class {
1185
+ constructor(binding, initialThreadId) {
1186
+ this.binding = binding;
1187
+ this.threadId = initialThreadId ?? null;
1188
+ }
1189
+ listeners = /* @__PURE__ */ new Set();
1190
+ threadId;
1191
+ onEvent(listener) {
1192
+ this.listeners.add(listener);
1193
+ return () => {
1194
+ this.listeners.delete(listener);
1195
+ };
1196
+ }
1197
+ async sendBackgroundEvent(message) {
1198
+ const trimmed = typeof message === "string" ? message.trim() : "";
1199
+ if (!trimmed) {
1200
+ throw new Error("Background event message must be a non-empty string");
1201
+ }
1202
+ if (!this.threadId) {
1203
+ throw new Error("Cannot emit a background event before the thread has started");
1204
+ }
1205
+ if (typeof this.binding.emitBackgroundEvent !== "function") {
1206
+ throw new Error("emitBackgroundEvent is not available in this build");
1207
+ }
1208
+ await this.binding.emitBackgroundEvent({ threadId: this.threadId, message: trimmed });
1209
+ }
1210
+ handleEvent(event) {
1211
+ if (event.type === "thread.started" && typeof event.thread_id === "string") {
1212
+ this.threadId = event.thread_id;
1213
+ }
1214
+ for (const listener of this.listeners) {
1215
+ try {
1216
+ listener(event);
1217
+ } catch (error) {
1218
+ console.warn("[codex-native] LSP listener failed", error);
1219
+ }
1220
+ }
1221
+ }
1222
+ setThreadId(id) {
1223
+ if (id) {
1224
+ this.threadId = id;
1225
+ }
1226
+ }
1227
+ };
1228
+ function createRunCommandLspBridge(params) {
1229
+ try {
1230
+ const relay = new RunCommandThreadRelay(params.binding, params.initialThreadId);
1231
+ const detach = attachLspDiagnostics(relay, {
1232
+ workingDirectory: params.workingDirectory,
1233
+ waitForDiagnostics: true
1234
+ });
1235
+ relay.setThreadId(params.initialThreadId);
1236
+ return {
1237
+ handleEvent: (event) => relay.handleEvent(event),
1238
+ dispose: () => detach()
1239
+ };
1240
+ } catch (error) {
1241
+ const message = error instanceof Error ? error.message : String(error);
1242
+ console.warn(`[codex-native] Failed to initialize LSP diagnostics bridge: ${message}`);
1243
+ return null;
1244
+ }
1245
+ }
1246
+
1247
+ // src/cli/run.ts
1248
+ async function executeRunCommand(argv, context) {
1249
+ const { combinedConfig } = context;
1250
+ emitWarnings(combinedConfig.warnings);
1251
+ const warningCount = combinedConfig.warnings.length;
1252
+ const prompt = await resolvePrompt(argv, combinedConfig.runDefaults.prompt, context.cwd);
1253
+ const request = await buildRunRequest({
1254
+ prompt,
1255
+ argv,
1256
+ combinedDefaults: combinedConfig.runDefaults,
1257
+ cwd: context.cwd
1258
+ });
1259
+ if (!request.skipGitRepoCheck) {
1260
+ await assertTrustedDirectory(request.workingDirectory);
1261
+ }
1262
+ validateModel(request.model, request.oss === true);
1263
+ const hookContext = {
1264
+ command: "run",
1265
+ cwd: context.cwd,
1266
+ options: argv
1267
+ };
1268
+ await runBeforeStartHooks(combinedConfig.beforeStartHooks, hookContext, combinedConfig.warnings);
1269
+ const binding = getNativeBinding();
1270
+ if (!binding) {
1271
+ throw new Error("Native N-API binding is not available.");
1272
+ }
1273
+ const queue = new AsyncQueue();
1274
+ let conversationId = null;
1275
+ const lspBridge = createRunCommandLspBridge({
1276
+ binding,
1277
+ workingDirectory: request.workingDirectory ?? context.cwd,
1278
+ initialThreadId: request.threadId
1279
+ });
1280
+ const handleEvent = async (eventJson) => {
1281
+ if (!eventJson) {
1282
+ return;
1283
+ }
1284
+ import_node_process.default.stdout.write(eventJson);
1285
+ import_node_process.default.stdout.write("\n");
1286
+ let eventPayload = eventJson;
1287
+ try {
1288
+ eventPayload = JSON.parse(eventJson);
1289
+ } catch {
1290
+ }
1291
+ conversationId ??= extractConversationId(eventPayload);
1292
+ const threadEvent = toThreadEvent(eventPayload);
1293
+ if (threadEvent && lspBridge) {
1294
+ lspBridge.handleEvent(threadEvent);
1295
+ }
1296
+ await runEventHooks(
1297
+ combinedConfig.onEventHooks,
1298
+ eventPayload,
1299
+ hookContext,
1300
+ combinedConfig.warnings
1301
+ );
1302
+ };
1303
+ let runPromise = Promise.resolve();
1304
+ runPromise = binding.runThreadStream(request, (err, eventJson) => {
1305
+ if (err) {
1306
+ queue.fail(err);
1307
+ return;
1308
+ }
1309
+ queue.push(eventJson ?? null);
1310
+ }).then(
1311
+ () => queue.end(),
1312
+ (error) => {
1313
+ queue.fail(error);
1314
+ }
1315
+ );
1316
+ let loopError;
1317
+ try {
1318
+ for await (const eventJson of queue) {
1319
+ try {
1320
+ await handleEvent(eventJson);
1321
+ } catch (error) {
1322
+ combinedConfig.warnings.push(
1323
+ `Event handler failed: ${error.message ?? String(error)}`
1324
+ );
1325
+ }
1326
+ }
1327
+ await runPromise;
1328
+ } catch (error) {
1329
+ loopError = error;
1330
+ throw error;
1331
+ } finally {
1332
+ queue.end();
1333
+ if (loopError) {
1334
+ await runPromise.catch(() => {
1335
+ });
1336
+ }
1337
+ if (lspBridge) {
1338
+ lspBridge.dispose();
1339
+ }
1340
+ }
1341
+ if (conversationId) {
1342
+ import_node_process.default.stdout.write(`
1343
+ To resume, run: codex-native tui --resume ${conversationId}
1344
+ `);
1345
+ }
1346
+ emitWarnings(combinedConfig.warnings, warningCount);
1347
+ }
1348
+ function toThreadEvent(payload) {
1349
+ if (!payload || typeof payload !== "object") {
1350
+ return null;
1351
+ }
1352
+ try {
1353
+ return convertRustEventToThreadEvent(payload);
1354
+ } catch {
1355
+ return null;
1356
+ }
1357
+ }
1358
+ async function resolvePrompt(argv, defaultPrompt, cwd) {
1359
+ if (argv.prompt && argv.prompt.trim().length > 0) {
1360
+ return argv.prompt;
1361
+ }
1362
+ if (defaultPrompt && defaultPrompt.trim().length > 0) {
1363
+ return defaultPrompt;
1364
+ }
1365
+ const stdinPrompt = await readPromptFromStdin();
1366
+ if (stdinPrompt && stdinPrompt.trim().length > 0) {
1367
+ return stdinPrompt;
1368
+ }
1369
+ if (argv.threadId) {
1370
+ return "";
1371
+ }
1372
+ const baseMessage = "No prompt provided. Supply a prompt or pipe one via stdin.";
1373
+ if (import_node_process.default.stdin.isTTY) {
1374
+ throw new Error(baseMessage);
1375
+ }
1376
+ throw new Error(baseMessage);
1377
+ }
1378
+ async function buildRunRequest(params) {
1379
+ const { prompt, argv, combinedDefaults, cwd } = params;
1380
+ const request = {
1381
+ ...combinedDefaults,
1382
+ prompt
1383
+ };
1384
+ if (combinedDefaults.images) {
1385
+ request.images = [...combinedDefaults.images];
1386
+ }
1387
+ if (combinedDefaults.workspaceWriteOptions) {
1388
+ request.workspaceWriteOptions = { ...combinedDefaults.workspaceWriteOptions };
1389
+ }
1390
+ if (argv.model !== void 0) request.model = argv.model;
1391
+ if (argv.oss !== void 0) request.oss = argv.oss;
1392
+ const sandboxMode = parseSandboxModeFlag(argv.sandbox, "--sandbox");
1393
+ if (sandboxMode !== void 0) {
1394
+ request.sandboxMode = sandboxMode;
1395
+ }
1396
+ const approvalMode = parseApprovalModeFlag(argv.approval, "--approval");
1397
+ if (approvalMode !== void 0) {
1398
+ request.approvalMode = approvalMode;
1399
+ }
1400
+ if (argv.threadId !== void 0) request.threadId = argv.threadId;
1401
+ if (argv.baseUrl !== void 0) request.baseUrl = argv.baseUrl;
1402
+ if (argv.apiKey !== void 0) request.apiKey = argv.apiKey;
1403
+ if (argv.linuxSandboxPath !== void 0) request.linuxSandboxPath = argv.linuxSandboxPath;
1404
+ if (argv.fullAuto !== void 0) request.fullAuto = argv.fullAuto;
1405
+ if (argv.skipGitRepoCheck !== void 0) request.skipGitRepoCheck = argv.skipGitRepoCheck;
1406
+ if (argv.cd !== void 0) request.workingDirectory = argv.cd;
1407
+ if (argv.reviewMode !== void 0) request.reviewMode = argv.reviewMode;
1408
+ if (argv.reviewHint !== void 0) request.reviewHint = argv.reviewHint;
1409
+ const images = [
1410
+ ...Array.isArray(request.images) ? request.images : [],
1411
+ ...argv.image ?? []
1412
+ ];
1413
+ request.images = images.length > 0 ? images : void 0;
1414
+ if (argv.schema) {
1415
+ request.outputSchema = await readJsonFile(argv.schema);
1416
+ }
1417
+ applyElevatedRunDefaults(request, cwd);
1418
+ return request;
1419
+ }
1420
+ async function readJsonFile(filePath) {
1421
+ const absolute = import_node_path4.default.resolve(import_node_process.default.cwd(), filePath);
1422
+ const data = await import_promises2.default.readFile(absolute, "utf8");
1423
+ try {
1424
+ return JSON.parse(data);
1425
+ } catch (error) {
1426
+ throw new Error(
1427
+ `Failed to parse JSON schema from ${absolute}: ${error.message ?? error}`
1428
+ );
1429
+ }
1430
+ }
1431
+ async function readPromptFromStdin() {
1432
+ if (import_node_process.default.stdin.isTTY) {
1433
+ return null;
1434
+ }
1435
+ const chunks = [];
1436
+ for await (const chunk of import_node_process.default.stdin) {
1437
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
1438
+ }
1439
+ if (chunks.length === 0) {
1440
+ return null;
1441
+ }
1442
+ return Buffer.concat(chunks).toString("utf8").trimEnd();
1443
+ }
1444
+ function extractConversationId(eventPayload) {
1445
+ if (!eventPayload || typeof eventPayload !== "object") {
1446
+ return null;
1447
+ }
1448
+ const record = eventPayload;
1449
+ if (typeof record.session_id === "string") {
1450
+ return record.session_id;
1451
+ }
1452
+ const sessionConfigured = record.SessionConfigured ?? record.sessionConfigured;
1453
+ if (sessionConfigured && typeof sessionConfigured === "object") {
1454
+ const configuredSessionId = sessionConfigured.session_id;
1455
+ if (typeof configuredSessionId === "string") {
1456
+ return configuredSessionId;
1457
+ }
1458
+ }
1459
+ const nestedSession = typeof record.session === "object" && record.session ? record.session.id : void 0;
1460
+ if (typeof nestedSession === "string") {
1461
+ return nestedSession;
1462
+ }
1463
+ return null;
1464
+ }
1465
+ function validateModel(model, oss) {
1466
+ if (!model) return;
1467
+ const trimmed = String(model).trim();
1468
+ if (oss) {
1469
+ if (!trimmed.startsWith("gpt-oss:")) {
1470
+ throw new Error(
1471
+ `Invalid model "${trimmed}" for OSS mode. Use models prefixed with "gpt-oss:", e.g. "gpt-oss:20b".`
1472
+ );
1473
+ }
1474
+ return;
1475
+ }
1476
+ const allowed = /* @__PURE__ */ new Set([
1477
+ // GPT models
1478
+ "gpt-5",
1479
+ "gpt-5-codex",
1480
+ "gpt-5-codex-mini",
1481
+ "gpt-5.1",
1482
+ "gpt-5.1-codex",
1483
+ "gpt-5.1-codex-mini",
1484
+ // Claude models
1485
+ "claude-sonnet-4-5-20250929",
1486
+ "claude-sonnet-4-20250514",
1487
+ "claude-opus-4-20250514"
1488
+ ]);
1489
+ if (!allowed.has(trimmed) && !trimmed.startsWith("claude-") && !trimmed.startsWith("gpt-")) {
1490
+ throw new Error(
1491
+ `Invalid model "${trimmed}". Supported models: ${Array.from(allowed).map((m) => `"${m}"`).join(", ")}, or any model starting with "claude-" or "gpt-".`
1492
+ );
1493
+ }
1494
+ }
1495
+ async function assertTrustedDirectory(workingDirectory) {
1496
+ const directory = workingDirectory ? import_node_path4.default.resolve(workingDirectory) : import_node_process.default.cwd();
1497
+ if (await findGitRoot(directory)) {
1498
+ return;
1499
+ }
1500
+ throw new Error(
1501
+ "Not inside a trusted directory and --skip-git-repo-check was not specified."
1502
+ );
1503
+ }
1504
+ async function findGitRoot(startDir) {
1505
+ let current = import_node_path4.default.resolve(startDir);
1506
+ while (true) {
1507
+ const gitPath = import_node_path4.default.join(current, ".git");
1508
+ if (import_node_fs3.default.existsSync(gitPath)) {
1509
+ try {
1510
+ const stats = await import_promises2.default.stat(gitPath);
1511
+ if (stats.isDirectory() || stats.isFile()) {
1512
+ return current;
1513
+ }
1514
+ } catch {
1515
+ }
1516
+ }
1517
+ const parent = import_node_path4.default.dirname(current);
1518
+ if (parent === current) {
1519
+ break;
1520
+ }
1521
+ current = parent;
1522
+ }
1523
+ return null;
1524
+ }
1525
+ var AsyncQueue = class {
1526
+ buffer = [];
1527
+ waiters = [];
1528
+ ended = false;
1529
+ error;
1530
+ push(value) {
1531
+ if (this.ended) return;
1532
+ if (value === null) {
1533
+ return;
1534
+ }
1535
+ if (this.waiters.length > 0) {
1536
+ const waiter = this.waiters.shift();
1537
+ waiter.resolve({ value, done: false });
1538
+ return;
1539
+ }
1540
+ this.buffer.push(value);
1541
+ }
1542
+ end() {
1543
+ if (this.ended) return;
1544
+ this.ended = true;
1545
+ const waiters = this.waiters;
1546
+ this.waiters = [];
1547
+ for (const waiter of waiters) {
1548
+ waiter.resolve({ value: void 0, done: true });
1549
+ }
1550
+ }
1551
+ fail(error) {
1552
+ if (this.ended) return;
1553
+ this.error = error;
1554
+ this.ended = true;
1555
+ const waiters = this.waiters;
1556
+ this.waiters = [];
1557
+ for (const waiter of waiters) {
1558
+ waiter.reject(error);
1559
+ }
1560
+ }
1561
+ async next() {
1562
+ if (this.buffer.length > 0) {
1563
+ const value = this.buffer.shift();
1564
+ return { value, done: false };
1565
+ }
1566
+ if (this.error) {
1567
+ return Promise.reject(this.error);
1568
+ }
1569
+ if (this.ended) {
1570
+ return { value: void 0, done: true };
1571
+ }
1572
+ return new Promise((resolve5, reject) => {
1573
+ this.waiters.push({ resolve: resolve5, reject });
1574
+ });
1575
+ }
1576
+ [Symbol.asyncIterator]() {
1577
+ return this;
1578
+ }
1579
+ };
1580
+
1581
+ // src/cli/tui.ts
1582
+ var import_node_process2 = __toESM(require("process"));
1583
+
1584
+ // src/exec.ts
1585
+ var CodexExec = class {
1586
+ native;
1587
+ constructor() {
1588
+ const nativeBinding = getNativeBinding();
1589
+ if (!nativeBinding) {
1590
+ throw new Error(
1591
+ "Native NAPI binding not available. Make sure @openai/codex-native is properly installed and built."
1592
+ );
1593
+ }
1594
+ this.native = nativeBinding;
1595
+ }
1596
+ requiresOutputSchemaFile() {
1597
+ return false;
1598
+ }
1599
+ async *run(args) {
1600
+ const binding = this.native;
1601
+ const queue = new AsyncQueue2();
1602
+ validateModel2(args.model, args.oss === true);
1603
+ const request = {
1604
+ prompt: args.input,
1605
+ threadId: args.threadId ?? void 0,
1606
+ images: args.images && args.images.length > 0 ? args.images : void 0,
1607
+ model: args.model,
1608
+ oss: args.oss,
1609
+ approvalMode: args.approvalMode,
1610
+ workspaceWriteOptions: args.workspaceWriteOptions,
1611
+ sandboxMode: args.sandboxMode,
1612
+ workingDirectory: args.workingDirectory,
1613
+ skipGitRepoCheck: args.skipGitRepoCheck,
1614
+ outputSchema: args.outputSchema,
1615
+ baseUrl: args.baseUrl,
1616
+ apiKey: args.apiKey,
1617
+ modelProvider: args.modelProvider,
1618
+ fullAuto: args.fullAuto,
1619
+ reviewMode: args.review ? true : void 0,
1620
+ reviewHint: args.review?.userFacingHint
1621
+ };
1622
+ let runPromise = Promise.resolve();
1623
+ try {
1624
+ runPromise = binding.runThreadStream(request, (err, eventJson) => {
1625
+ if (err) {
1626
+ queue.fail(err);
1627
+ return;
1628
+ }
1629
+ try {
1630
+ queue.push(eventJson ?? "null");
1631
+ } catch (error) {
1632
+ queue.fail(error);
1633
+ }
1634
+ }).then(
1635
+ () => {
1636
+ queue.end();
1637
+ },
1638
+ (error) => {
1639
+ queue.fail(error);
1640
+ }
1641
+ );
1642
+ } catch (error) {
1643
+ queue.fail(error);
1644
+ throw error;
1645
+ }
1646
+ let loopError;
1647
+ try {
1648
+ for await (const value of queue) {
1649
+ yield value;
1650
+ }
1651
+ await runPromise;
1652
+ } catch (error) {
1653
+ loopError = error;
1654
+ throw error;
1655
+ } finally {
1656
+ queue.end();
1657
+ if (loopError) {
1658
+ await runPromise.catch(() => {
1659
+ });
1660
+ }
1661
+ }
1662
+ }
1663
+ async compact(args) {
1664
+ validateModel2(args.model, args.oss === true);
1665
+ const request = {
1666
+ prompt: args.input,
1667
+ threadId: args.threadId ?? void 0,
1668
+ images: args.images && args.images.length > 0 ? args.images : void 0,
1669
+ model: args.model,
1670
+ modelProvider: args.modelProvider,
1671
+ oss: args.oss,
1672
+ sandboxMode: args.sandboxMode,
1673
+ approvalMode: args.approvalMode,
1674
+ workspaceWriteOptions: args.workspaceWriteOptions,
1675
+ workingDirectory: args.workingDirectory,
1676
+ skipGitRepoCheck: args.skipGitRepoCheck,
1677
+ outputSchema: args.outputSchema,
1678
+ baseUrl: args.baseUrl,
1679
+ apiKey: args.apiKey,
1680
+ fullAuto: args.fullAuto,
1681
+ reviewMode: args.review ? true : void 0,
1682
+ reviewHint: args.review?.userFacingHint
1683
+ };
1684
+ return this.native.compactThread(request);
1685
+ }
1686
+ async fork(args) {
1687
+ if (!args.threadId) {
1688
+ throw new Error("threadId is required to fork a conversation");
1689
+ }
1690
+ const request = {
1691
+ threadId: args.threadId,
1692
+ nthUserMessage: args.nthUserMessage,
1693
+ model: args.model,
1694
+ oss: args.oss,
1695
+ sandboxMode: args.sandboxMode,
1696
+ approvalMode: args.approvalMode,
1697
+ workspaceWriteOptions: args.workspaceWriteOptions,
1698
+ workingDirectory: args.workingDirectory,
1699
+ skipGitRepoCheck: args.skipGitRepoCheck,
1700
+ baseUrl: args.baseUrl,
1701
+ apiKey: args.apiKey,
1702
+ modelProvider: args.modelProvider,
1703
+ linuxSandboxPath: args.linuxSandboxPath,
1704
+ fullAuto: args.fullAuto
1705
+ };
1706
+ return this.native.forkThread(request);
1707
+ }
1708
+ async listConversations(request) {
1709
+ return this.native.listConversations(request);
1710
+ }
1711
+ async deleteConversation(request) {
1712
+ return this.native.deleteConversation(request);
1713
+ }
1714
+ async resumeConversationFromRollout(request) {
1715
+ return this.native.resumeConversationFromRollout(request);
1716
+ }
1717
+ };
1718
+ var AsyncQueue2 = class {
1719
+ buffer = [];
1720
+ waiters = [];
1721
+ ended = false;
1722
+ error;
1723
+ push(value) {
1724
+ if (this.ended) return;
1725
+ if (this.waiters.length > 0) {
1726
+ const waiter = this.waiters.shift();
1727
+ waiter.resolve({ value, done: false });
1728
+ return;
1729
+ }
1730
+ this.buffer.push(value);
1731
+ }
1732
+ end() {
1733
+ if (this.ended) return;
1734
+ this.ended = true;
1735
+ const waiters = this.waiters;
1736
+ this.waiters = [];
1737
+ for (const waiter of waiters) {
1738
+ waiter.resolve({ value: void 0, done: true });
1739
+ }
1740
+ }
1741
+ fail(error) {
1742
+ if (this.ended) return;
1743
+ this.error = error;
1744
+ this.ended = true;
1745
+ const waiters = this.waiters;
1746
+ this.waiters = [];
1747
+ for (const waiter of waiters) {
1748
+ waiter.reject(error);
1749
+ }
1750
+ }
1751
+ async next() {
1752
+ if (this.buffer.length > 0) {
1753
+ const value = this.buffer.shift();
1754
+ return { value, done: false };
1755
+ }
1756
+ if (this.error) {
1757
+ return Promise.reject(this.error);
1758
+ }
1759
+ if (this.ended) {
1760
+ return { value: void 0, done: true };
1761
+ }
1762
+ return new Promise((resolve5, reject) => {
1763
+ this.waiters.push({ resolve: resolve5, reject });
1764
+ });
1765
+ }
1766
+ [Symbol.asyncIterator]() {
1767
+ return this;
1768
+ }
1769
+ };
1770
+ function validateModel2(model, oss) {
1771
+ if (!model) return;
1772
+ const trimmed = String(model).trim();
1773
+ if (oss) {
1774
+ if (!trimmed.startsWith("gpt-oss:")) {
1775
+ throw new Error(
1776
+ `Invalid model "${trimmed}" for OSS mode. Use models prefixed with "gpt-oss:", e.g. "gpt-oss:20b".`
1777
+ );
1778
+ }
1779
+ return;
1780
+ }
1781
+ const allowed = /* @__PURE__ */ new Set([
1782
+ // GPT models
1783
+ "gpt-5",
1784
+ "gpt-5-codex",
1785
+ "gpt-5-codex-mini",
1786
+ "gpt-5.1",
1787
+ "gpt-5.1-codex",
1788
+ "gpt-5.1-codex-mini",
1789
+ // Claude models
1790
+ "claude-sonnet-4-5-20250929",
1791
+ "claude-sonnet-4-20250514",
1792
+ "claude-opus-4-20250514"
1793
+ ]);
1794
+ if (!allowed.has(trimmed) && !trimmed.startsWith("claude-") && !trimmed.startsWith("gpt-")) {
1795
+ throw new Error(
1796
+ `Invalid model "${trimmed}". Supported models: ${Array.from(allowed).map((m) => `"${m}"`).join(", ")}, or any model starting with "claude-" or "gpt-".`
1797
+ );
1798
+ }
1799
+ }
1800
+
1801
+ // src/thread.ts
1802
+ var fs7 = __toESM(require("fs"));
1803
+ var path11 = __toESM(require("path"));
1804
+
1805
+ // src/outputSchemaFile.ts
1806
+ var import_node_fs4 = require("fs");
1807
+ var import_node_os = __toESM(require("os"));
1808
+ var import_node_path5 = __toESM(require("path"));
1809
+ function normalizeOutputSchema(schema) {
1810
+ if (schema === void 0) {
1811
+ return void 0;
1812
+ }
1813
+ if (isJsonObject(schema) && (schema.type === "json_schema" || schema.type === "json-schema") && isJsonObject(schema.json_schema) && isJsonObject(schema.json_schema.schema)) {
1814
+ const strict = typeof schema.json_schema.strict === "boolean" ? schema.json_schema.strict : true;
1815
+ return normalizeJsonSchemaObject(schema.json_schema.schema, strict);
1816
+ }
1817
+ if (isJsonObject(schema) && isJsonObject(schema.schema)) {
1818
+ const strict = typeof schema.strict === "boolean" ? schema.strict : true;
1819
+ return normalizeJsonSchemaObject(schema.schema, strict);
1820
+ }
1821
+ if (!isJsonObject(schema)) {
1822
+ throw new Error(
1823
+ "outputSchema must be a plain JSON object or an OpenAI-style json_schema wrapper"
1824
+ );
1825
+ }
1826
+ return normalizeJsonSchemaObject(schema, true);
1827
+ }
1828
+ async function createOutputSchemaFile(schema) {
1829
+ const normalizedSchema = normalizeOutputSchema(schema);
1830
+ if (!normalizedSchema) {
1831
+ return { cleanup: async () => {
1832
+ } };
1833
+ }
1834
+ const schemaDir = await import_node_fs4.promises.mkdtemp(import_node_path5.default.join(import_node_os.default.tmpdir(), "codex-output-schema-"));
1835
+ const schemaPath = import_node_path5.default.join(schemaDir, "schema.json");
1836
+ const cleanup = async () => {
1837
+ try {
1838
+ await import_node_fs4.promises.rm(schemaDir, { recursive: true, force: true });
1839
+ } catch {
1840
+ }
1841
+ };
1842
+ try {
1843
+ await import_node_fs4.promises.writeFile(schemaPath, JSON.stringify(normalizedSchema), "utf8");
1844
+ return { schemaPath, cleanup };
1845
+ } catch (error) {
1846
+ await cleanup();
1847
+ throw error;
1848
+ }
1849
+ }
1850
+ function isJsonObject(value) {
1851
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1852
+ }
1853
+ function normalizeJsonSchemaObject(schema, strict) {
1854
+ const record = { ...schema };
1855
+ const hasExplicitAdditional = typeof record.additionalProperties === "boolean" || typeof record.additionalProperties === "object";
1856
+ const additionalProperties = hasExplicitAdditional ? record.additionalProperties : strict ? false : record.additionalProperties;
1857
+ return {
1858
+ ...record,
1859
+ ...hasExplicitAdditional || strict ? { additionalProperties } : {}
1860
+ };
1861
+ }
1862
+
1863
+ // src/tui.ts
1864
+ function startTui(request) {
1865
+ const binding = getNativeBinding();
1866
+ if (!binding) {
1867
+ throw new Error("Native binding is not available");
1868
+ }
1869
+ if (typeof binding.startTui === "function") {
1870
+ const nativeSession = binding.startTui(request);
1871
+ return wrapNativeSession(nativeSession);
1872
+ }
1873
+ if (typeof binding.runTui === "function") {
1874
+ return createLegacySession(binding, request);
1875
+ }
1876
+ throw new Error("Native binding does not expose startTui or runTui");
1877
+ }
1878
+ async function runTui(request, options = {}) {
1879
+ const session = startTui(request);
1880
+ const { signal } = options;
1881
+ let abortListener;
1882
+ try {
1883
+ if (signal) {
1884
+ if (signal.aborted) {
1885
+ session.shutdown();
1886
+ } else {
1887
+ abortListener = () => session.shutdown();
1888
+ signal.addEventListener("abort", abortListener, { once: true });
1889
+ }
1890
+ }
1891
+ return await session.wait();
1892
+ } finally {
1893
+ if (abortListener && signal) {
1894
+ signal.removeEventListener("abort", abortListener);
1895
+ }
1896
+ }
1897
+ }
1898
+ function wrapNativeSession(nativeSession) {
1899
+ return {
1900
+ wait: () => nativeSession.wait(),
1901
+ shutdown: () => nativeSession.shutdown(),
1902
+ get closed() {
1903
+ return nativeSession.closed;
1904
+ }
1905
+ };
1906
+ }
1907
+ function createLegacySession(binding, request) {
1908
+ if (typeof binding.runTui !== "function") {
1909
+ throw new Error("Native binding does not expose runTui");
1910
+ }
1911
+ let closed = false;
1912
+ const promise = binding.runTui(request).then(
1913
+ (result) => {
1914
+ closed = true;
1915
+ return result;
1916
+ },
1917
+ (error) => {
1918
+ closed = true;
1919
+ throw error;
1920
+ }
1921
+ );
1922
+ return {
1923
+ wait: () => promise,
1924
+ shutdown() {
1925
+ throw new Error(
1926
+ "Programmatic shutdown is not supported by this native binding build. Rebuild the SDK to enable startTui()."
1927
+ );
1928
+ },
1929
+ get closed() {
1930
+ return closed;
1931
+ }
1932
+ };
1933
+ }
1934
+
1935
+ // src/thread.ts
1936
+ var UNTRUSTED_DIRECTORY_ERROR = "Not inside a trusted directory and --skip-git-repo-check was not specified.";
1937
+ function findGitRoot2(startDir) {
1938
+ let current = path11.resolve(startDir);
1939
+ while (true) {
1940
+ const gitPath = path11.join(current, ".git");
1941
+ if (fs7.existsSync(gitPath)) {
1942
+ try {
1943
+ const stats = fs7.statSync(gitPath);
1944
+ if (stats.isDirectory() || stats.isFile()) {
1945
+ return current;
1946
+ }
1947
+ } catch {
1948
+ }
1949
+ }
1950
+ const parent = path11.dirname(current);
1951
+ if (parent === current) {
1952
+ break;
1953
+ }
1954
+ current = parent;
1955
+ }
1956
+ return null;
1957
+ }
1958
+ function assertTrustedDirectory2(workingDirectory) {
1959
+ const directory = workingDirectory ? path11.resolve(workingDirectory) : process.cwd();
1960
+ if (!findGitRoot2(directory)) {
1961
+ throw new Error(UNTRUSTED_DIRECTORY_ERROR);
1962
+ }
1963
+ }
1964
+ var Thread = class _Thread {
1965
+ _exec;
1966
+ _options;
1967
+ _id;
1968
+ _threadOptions;
1969
+ _eventListeners = [];
1970
+ _approvalHandler = null;
1971
+ /** Returns the ID of the thread. Populated after the first turn starts. */
1972
+ get id() {
1973
+ return this._id;
1974
+ }
1975
+ /**
1976
+ * Register an event listener for thread events.
1977
+ * @param listener Callback function that receives ThreadEvent objects
1978
+ * @returns Unsubscribe function to remove the listener
1979
+ */
1980
+ onEvent(listener) {
1981
+ this._eventListeners.push(listener);
1982
+ return () => {
1983
+ const index = this._eventListeners.indexOf(listener);
1984
+ if (index !== -1) {
1985
+ this._eventListeners.splice(index, 1);
1986
+ }
1987
+ };
1988
+ }
1989
+ /**
1990
+ * Remove an event listener.
1991
+ * @param listener The listener function to remove
1992
+ */
1993
+ offEvent(listener) {
1994
+ const index = this._eventListeners.indexOf(listener);
1995
+ if (index !== -1) {
1996
+ this._eventListeners.splice(index, 1);
1997
+ }
1998
+ }
1999
+ /**
2000
+ * Register a callback to handle approval requests from the agent.
2001
+ * The handler should return true to approve the action, false to deny it.
2002
+ *
2003
+ * @param handler Callback function that receives ApprovalRequest and returns approval decision
2004
+ * @example
2005
+ * ```typescript
2006
+ * thread.onApprovalRequest(async (request) => {
2007
+ * console.log(`Approval requested for ${request.type}`);
2008
+ * return true; // Auto-approve
2009
+ * });
2010
+ * ```
2011
+ */
2012
+ onApprovalRequest(handler) {
2013
+ this._approvalHandler = handler;
2014
+ const binding = getNativeBinding();
2015
+ if (binding && typeof binding.registerApprovalCallback === "function") {
2016
+ binding.registerApprovalCallback(handler);
2017
+ }
2018
+ }
2019
+ /**
2020
+ * Emit a background notification while the agent is running the current turn.
2021
+ * The message is surfaced to event subscribers but does not modify the user input queue.
2022
+ *
2023
+ * @throws Error if the thread has not been started yet.
2024
+ */
2025
+ async sendBackgroundEvent(message) {
2026
+ const trimmed = message?.toString();
2027
+ if (!trimmed || trimmed.trim().length === 0) {
2028
+ throw new Error("Background event message must be a non-empty string");
2029
+ }
2030
+ if (!this._id) {
2031
+ throw new Error("Cannot emit a background event before the thread has started");
2032
+ }
2033
+ const binding = getNativeBinding();
2034
+ if (!binding || typeof binding.emitBackgroundEvent !== "function") {
2035
+ throw new Error("emitBackgroundEvent is not available in this build");
2036
+ }
2037
+ await binding.emitBackgroundEvent({ threadId: this._id, message: trimmed });
2038
+ }
2039
+ /**
2040
+ * Programmatically update the agent's plan/todo list.
2041
+ * The plan will be applied at the start of the next turn.
2042
+ *
2043
+ * @param args The plan update arguments
2044
+ * @throws Error if no thread ID is available
2045
+ */
2046
+ updatePlan(args) {
2047
+ if (!this._id) {
2048
+ throw new Error("Cannot update plan: no active thread");
2049
+ }
2050
+ const binding = getNativeBinding();
2051
+ if (!binding || typeof binding.emitPlanUpdate !== "function") {
2052
+ throw new Error("emitPlanUpdate is not available in this build");
2053
+ }
2054
+ binding.emitPlanUpdate({
2055
+ threadId: this._id,
2056
+ explanation: args.explanation,
2057
+ plan: args.plan
2058
+ });
2059
+ }
2060
+ /**
2061
+ * Modify the agent's plan/todo list with granular operations.
2062
+ * Changes will be applied at the start of the next turn.
2063
+ *
2064
+ * @param operations Array of operations to perform on the plan
2065
+ * @throws Error if no thread ID is available
2066
+ */
2067
+ modifyPlan(operations) {
2068
+ if (!this._id) {
2069
+ throw new Error("Cannot modify plan: no active thread");
2070
+ }
2071
+ const binding = getNativeBinding();
2072
+ if (!binding || typeof binding.modifyPlan !== "function") {
2073
+ throw new Error("modifyPlan is not available in this build");
2074
+ }
2075
+ binding.modifyPlan({
2076
+ threadId: this._id,
2077
+ operations
2078
+ });
2079
+ }
2080
+ /**
2081
+ * Add a new todo item to the agent's plan.
2082
+ *
2083
+ * @param step The todo step description
2084
+ * @param status The initial status (defaults to "pending")
2085
+ */
2086
+ addTodo(step, status = "pending") {
2087
+ this.modifyPlan([{ type: "add", item: { step, status } }]);
2088
+ }
2089
+ /**
2090
+ * Update an existing todo item.
2091
+ *
2092
+ * @param index The index of the todo item to update
2093
+ * @param updates The updates to apply
2094
+ */
2095
+ updateTodo(index, updates) {
2096
+ this.modifyPlan([{ type: "update", index, updates }]);
2097
+ }
2098
+ /**
2099
+ * Remove a todo item from the plan.
2100
+ *
2101
+ * @param index The index of the todo item to remove
2102
+ */
2103
+ removeTodo(index) {
2104
+ this.modifyPlan([{ type: "remove", index }]);
2105
+ }
2106
+ /**
2107
+ * Reorder the todo items in the plan.
2108
+ *
2109
+ * @param newOrder Array of indices representing the new order
2110
+ */
2111
+ reorderTodos(newOrder) {
2112
+ this.modifyPlan([{ type: "reorder", newOrder }]);
2113
+ }
2114
+ /** Compacts the conversation history for this thread using Codex's builtin compaction. */
2115
+ async compact() {
2116
+ const skipGitRepoCheck = this._threadOptions?.skipGitRepoCheck ?? (typeof process !== "undefined" && process.env && process.env.CODEX_TEST_SKIP_GIT_REPO_CHECK === "1");
2117
+ if (!skipGitRepoCheck) {
2118
+ assertTrustedDirectory2(this._threadOptions?.workingDirectory);
2119
+ }
2120
+ const events = await this._exec.compact({
2121
+ input: "compact",
2122
+ threadId: this._id,
2123
+ baseUrl: this._options.baseUrl,
2124
+ apiKey: this._options.apiKey,
2125
+ model: this._threadOptions?.model ?? this._options.defaultModel,
2126
+ sandboxMode: this._threadOptions?.sandboxMode,
2127
+ approvalMode: this._threadOptions?.approvalMode,
2128
+ workspaceWriteOptions: this._threadOptions?.workspaceWriteOptions,
2129
+ workingDirectory: this._threadOptions?.workingDirectory,
2130
+ skipGitRepoCheck,
2131
+ modelProvider: this._options.modelProvider
2132
+ });
2133
+ if (!Array.isArray(events)) {
2134
+ throw new Error("Compact did not return event list");
2135
+ }
2136
+ }
2137
+ /**
2138
+ * Fork this thread at the specified user message, returning a new thread that starts
2139
+ * from the conversation history prior to that message.
2140
+ *
2141
+ * @param options Fork configuration including which user message to branch before and optional thread overrides.
2142
+ */
2143
+ async fork(options) {
2144
+ if (!this._id) {
2145
+ throw new Error("Cannot fork: no active thread");
2146
+ }
2147
+ const nthUserMessage = options?.nthUserMessage;
2148
+ if (typeof nthUserMessage !== "number" || !Number.isInteger(nthUserMessage) || nthUserMessage < 0) {
2149
+ throw new Error("nthUserMessage must be a non-negative integer");
2150
+ }
2151
+ const overrides = options.threadOptions ?? {};
2152
+ const nextThreadOptions = {
2153
+ ...this._threadOptions,
2154
+ ...overrides
2155
+ };
2156
+ const skipGitRepoCheck = nextThreadOptions.skipGitRepoCheck ?? (typeof process !== "undefined" && process.env && process.env.CODEX_TEST_SKIP_GIT_REPO_CHECK === "1");
2157
+ nextThreadOptions.skipGitRepoCheck = skipGitRepoCheck;
2158
+ if (!skipGitRepoCheck) {
2159
+ assertTrustedDirectory2(nextThreadOptions.workingDirectory);
2160
+ }
2161
+ const forkArgs = {
2162
+ threadId: this._id,
2163
+ nthUserMessage,
2164
+ baseUrl: this._options.baseUrl,
2165
+ apiKey: this._options.apiKey,
2166
+ model: nextThreadOptions.model ?? this._options.defaultModel,
2167
+ oss: nextThreadOptions.oss,
2168
+ sandboxMode: nextThreadOptions.sandboxMode,
2169
+ approvalMode: nextThreadOptions.approvalMode,
2170
+ workspaceWriteOptions: nextThreadOptions.workspaceWriteOptions,
2171
+ workingDirectory: nextThreadOptions.workingDirectory,
2172
+ skipGitRepoCheck,
2173
+ fullAuto: nextThreadOptions.fullAuto,
2174
+ modelProvider: this._options.modelProvider
2175
+ };
2176
+ const result = await this._exec.fork(forkArgs);
2177
+ return new _Thread(
2178
+ this._exec,
2179
+ this._options,
2180
+ nextThreadOptions,
2181
+ result.threadId
2182
+ );
2183
+ }
2184
+ /* @internal */
2185
+ constructor(exec, options, threadOptions, id = null) {
2186
+ this._exec = exec;
2187
+ this._options = options;
2188
+ this._id = id;
2189
+ this._threadOptions = threadOptions;
2190
+ }
2191
+ /** Provides the input to the agent and streams events as they are produced during the turn. */
2192
+ async runStreamed(input, turnOptions = {}) {
2193
+ return { events: this.runStreamedInternal(input, turnOptions, false) };
2194
+ }
2195
+ async *runStreamedInternal(input, turnOptions = {}, emitRawEvents = true) {
2196
+ const normalizedSchema = normalizeOutputSchema(turnOptions.outputSchema);
2197
+ const needsSchemaFile = this._exec.requiresOutputSchemaFile();
2198
+ const schemaFile = needsSchemaFile ? await createOutputSchemaFile(normalizedSchema) : { schemaPath: void 0, cleanup: async () => {
2199
+ } };
2200
+ const options = this._threadOptions;
2201
+ const { prompt, images } = normalizeInput(input);
2202
+ const skipGitRepoCheck = options?.skipGitRepoCheck ?? (typeof process !== "undefined" && process.env && process.env.CODEX_TEST_SKIP_GIT_REPO_CHECK === "1");
2203
+ if (!skipGitRepoCheck) {
2204
+ assertTrustedDirectory2(options?.workingDirectory);
2205
+ }
2206
+ const generator = this._exec.run({
2207
+ input: prompt,
2208
+ baseUrl: this._options.baseUrl,
2209
+ apiKey: this._options.apiKey,
2210
+ threadId: this._id,
2211
+ images,
2212
+ model: options?.model,
2213
+ oss: turnOptions?.oss ?? options?.oss,
2214
+ sandboxMode: options?.sandboxMode,
2215
+ approvalMode: options?.approvalMode,
2216
+ workspaceWriteOptions: options?.workspaceWriteOptions,
2217
+ workingDirectory: options?.workingDirectory,
2218
+ skipGitRepoCheck,
2219
+ outputSchemaFile: schemaFile.schemaPath,
2220
+ outputSchema: normalizedSchema,
2221
+ fullAuto: options?.fullAuto
2222
+ });
2223
+ try {
2224
+ for await (const item of generator) {
2225
+ let parsed;
2226
+ try {
2227
+ parsed = JSON.parse(item);
2228
+ } catch (error) {
2229
+ throw new Error(`Failed to parse item: ${item}. Parse error: ${error}`);
2230
+ }
2231
+ if (parsed === null) {
2232
+ continue;
2233
+ }
2234
+ if (emitRawEvents) {
2235
+ yield { type: "raw_event", raw: parsed };
2236
+ }
2237
+ const threadEvent = convertRustEventToThreadEvent(parsed);
2238
+ if (threadEvent.type === "thread.started") {
2239
+ this._id = threadEvent.thread_id;
2240
+ }
2241
+ for (const listener of this._eventListeners) {
2242
+ try {
2243
+ listener(threadEvent);
2244
+ } catch (error) {
2245
+ console.warn("Thread event listener threw error:", error);
2246
+ }
2247
+ }
2248
+ yield threadEvent;
2249
+ }
2250
+ } finally {
2251
+ await schemaFile.cleanup();
2252
+ }
2253
+ }
2254
+ /** Provides the input to the agent and returns the completed turn. */
2255
+ async run(input, turnOptions = {}) {
2256
+ const generator = this.runStreamedInternal(input, turnOptions, true);
2257
+ const items = [];
2258
+ let finalResponse = "";
2259
+ let usage = null;
2260
+ let turnFailure = null;
2261
+ for await (const event of generator) {
2262
+ if (event.type === "item.completed") {
2263
+ if (event.item.type === "agent_message") {
2264
+ finalResponse = event.item.text;
2265
+ }
2266
+ items.push(event.item);
2267
+ } else if (event.type === "turn.completed") {
2268
+ usage = event.usage;
2269
+ } else if (event.type === "turn.failed") {
2270
+ turnFailure = event.error;
2271
+ break;
2272
+ }
2273
+ }
2274
+ if (turnFailure) {
2275
+ throw new Error(turnFailure.message);
2276
+ }
2277
+ return { items, finalResponse, usage };
2278
+ }
2279
+ buildTuiRequest(overrides = {}) {
2280
+ const skipGitRepoCheck = this._threadOptions?.skipGitRepoCheck ?? (typeof process !== "undefined" && process.env && process.env.CODEX_TEST_SKIP_GIT_REPO_CHECK === "1");
2281
+ if (!skipGitRepoCheck) {
2282
+ assertTrustedDirectory2(this._threadOptions?.workingDirectory);
2283
+ }
2284
+ const request = { ...overrides };
2285
+ const assignIfUndefined = (key, value) => {
2286
+ if (request[key] === void 0 && value !== void 0) {
2287
+ request[key] = value;
2288
+ }
2289
+ };
2290
+ assignIfUndefined("model", this._threadOptions?.model ?? this._options.defaultModel);
2291
+ assignIfUndefined("oss", this._threadOptions?.oss);
2292
+ assignIfUndefined("sandboxMode", this._threadOptions?.sandboxMode);
2293
+ assignIfUndefined("approvalMode", this._threadOptions?.approvalMode);
2294
+ assignIfUndefined("fullAuto", this._threadOptions?.fullAuto);
2295
+ assignIfUndefined("workingDirectory", this._threadOptions?.workingDirectory);
2296
+ assignIfUndefined("baseUrl", this._options.baseUrl);
2297
+ assignIfUndefined("apiKey", this._options.apiKey);
2298
+ if (request.resumeSessionId === void 0 && request.resumePicker !== true && request.resumeLast !== true && this._id) {
2299
+ request.resumeSessionId = this._id;
2300
+ }
2301
+ return request;
2302
+ }
2303
+ /**
2304
+ * Launches the interactive Codex TUI (Terminal User Interface) for this thread and returns a session handle.
2305
+ *
2306
+ * The handle allows advanced workflows where the TUI can be started and stopped programmatically,
2307
+ * while preserving the underlying conversation state.
2308
+ */
2309
+ launchTui(overrides = {}) {
2310
+ const request = this.buildTuiRequest(overrides);
2311
+ const detachLsp = this.attachDefaultLspBridge(request);
2312
+ const session = startTui(request);
2313
+ return this.wrapTuiSession(session, detachLsp);
2314
+ }
2315
+ /**
2316
+ * Launches the interactive Codex TUI (Terminal User Interface) for this thread.
2317
+ *
2318
+ * This method enables seamless transition from programmatic agent interaction to
2319
+ * interactive terminal chat within the same session. The TUI takes over the terminal
2320
+ * and allows you to continue the conversation interactively.
2321
+ *
2322
+ * @param overrides - Optional configuration to override thread defaults. Supports all TUI options
2323
+ * including prompt, sandbox mode, approval mode, and resume options.
2324
+ * @param options - Optional run options including an AbortSignal to request shutdown.
2325
+ * @returns A Promise that resolves to TUI exit information including:
2326
+ * - tokenUsage: Token consumption statistics
2327
+ * - conversationId: Session ID for resuming later
2328
+ * - updateAction: Optional suggested update command
2329
+ * @throws {Error} If not in a trusted git repository (unless skipGitRepoCheck is set)
2330
+ * @throws {Error} If the terminal is not interactive (TTY required)
2331
+ */
2332
+ async tui(overrides = {}, options = {}) {
2333
+ const request = this.buildTuiRequest(overrides);
2334
+ const detachLsp = this.attachDefaultLspBridge(request);
2335
+ try {
2336
+ return await runTui(request, options);
2337
+ } finally {
2338
+ detachLsp();
2339
+ }
2340
+ }
2341
+ wrapTuiSession(session, cleanup) {
2342
+ let released = false;
2343
+ const release = () => {
2344
+ if (released) {
2345
+ return;
2346
+ }
2347
+ released = true;
2348
+ cleanup();
2349
+ };
2350
+ return {
2351
+ wait: async () => {
2352
+ try {
2353
+ return await session.wait();
2354
+ } finally {
2355
+ release();
2356
+ }
2357
+ },
2358
+ shutdown: () => {
2359
+ release();
2360
+ session.shutdown();
2361
+ },
2362
+ get closed() {
2363
+ return session.closed;
2364
+ }
2365
+ };
2366
+ }
2367
+ attachDefaultLspBridge(request) {
2368
+ const workingDirectory = request.workingDirectory ?? this._threadOptions?.workingDirectory ?? (typeof process !== "undefined" && typeof process.cwd === "function" ? process.cwd() : ".");
2369
+ return attachLspDiagnostics(this, {
2370
+ workingDirectory,
2371
+ waitForDiagnostics: true
2372
+ });
2373
+ }
2374
+ };
2375
+ function normalizeInput(input) {
2376
+ if (typeof input === "string") {
2377
+ return { prompt: input, images: [] };
2378
+ }
2379
+ const promptParts = [];
2380
+ const images = [];
2381
+ for (const item of input) {
2382
+ if (item.type === "text") {
2383
+ promptParts.push(item.text);
2384
+ } else if (item.type === "local_image") {
2385
+ images.push(item.path);
2386
+ }
2387
+ }
2388
+ return { prompt: promptParts.join("\n\n"), images };
2389
+ }
2390
+
2391
+ // src/reviewOptions.ts
2392
+ function buildReviewPrompt(target) {
2393
+ switch (target.type) {
2394
+ case "current_changes":
2395
+ return {
2396
+ prompt: "Review the current code changes (staged, unstaged, and untracked files) and provide prioritized findings.",
2397
+ hint: "current changes"
2398
+ };
2399
+ case "branch": {
2400
+ const branch = target.baseBranch;
2401
+ const prompt = `Review the code changes against the base branch '${branch}'. Start by finding the merge diff between the current branch and ${branch}'s upstream e.g. (\`git merge-base HEAD "$(git rev-parse --abbrev-ref "${branch}@{upstream}")"\`), then run \`git diff\` against that SHA to see what changes we would merge into the ${branch} branch. Provide prioritized, actionable findings.`;
2402
+ return {
2403
+ prompt,
2404
+ hint: `changes against '${branch}'`
2405
+ };
2406
+ }
2407
+ case "commit": {
2408
+ const shortSha = target.sha.slice(0, 7);
2409
+ const subject = target.subject ?? target.sha;
2410
+ return {
2411
+ prompt: `Review the code changes introduced by commit ${target.sha} ("${subject}"). Provide prioritized, actionable findings.`,
2412
+ hint: `commit ${shortSha}`
2413
+ };
2414
+ }
2415
+ case "custom": {
2416
+ const hint = target.hint ?? "custom review";
2417
+ return {
2418
+ prompt: target.prompt,
2419
+ hint
2420
+ };
2421
+ }
2422
+ default: {
2423
+ const exhaustive = target;
2424
+ throw new Error(`Unsupported review target: ${String(exhaustive)}`);
2425
+ }
2426
+ }
2427
+ }
2428
+
2429
+ // src/codex.ts
2430
+ var Codex = class {
2431
+ exec;
2432
+ options;
2433
+ nativeBinding;
2434
+ lspForTools;
2435
+ constructor(options = {}) {
2436
+ const predefinedTools = options.tools ? [...options.tools] : [];
2437
+ const preserveRegisteredTools = options.preserveRegisteredTools === true;
2438
+ this.nativeBinding = getNativeBinding();
2439
+ this.options = { ...options, tools: [] };
2440
+ if (this.nativeBinding) {
2441
+ if (!preserveRegisteredTools && typeof this.nativeBinding.clearRegisteredTools === "function") {
2442
+ this.nativeBinding.clearRegisteredTools();
2443
+ }
2444
+ for (const tool of predefinedTools) {
2445
+ this.registerTool(tool);
2446
+ }
2447
+ }
2448
+ this.lspForTools = this.createLspManagerForTools();
2449
+ if (this.lspForTools && this.nativeBinding) {
2450
+ this.registerDefaultReadFileInterceptor();
2451
+ }
2452
+ this.exec = new CodexExec();
2453
+ }
2454
+ /**
2455
+ * Register a tool for Codex. When `tool.name` matches a built-in Codex tool,
2456
+ * the native implementation is replaced for this Codex instance.
2457
+ */
2458
+ registerTool(tool) {
2459
+ if (!this.nativeBinding) {
2460
+ throw new Error("Native tool registration requires the NAPI binding");
2461
+ }
2462
+ if (typeof this.nativeBinding.registerTool !== "function") {
2463
+ console.warn("registerTool is not available in this build - tools feature may be incomplete");
2464
+ return;
2465
+ }
2466
+ const { handler, ...info } = tool;
2467
+ this.nativeBinding.registerTool(info, handler);
2468
+ if (!this.options.tools) {
2469
+ this.options.tools = [];
2470
+ }
2471
+ this.options.tools.push(tool);
2472
+ }
2473
+ /**
2474
+ * Register a tool interceptor for Codex. Interceptors can modify tool invocations
2475
+ * and results, and can call the built-in implementation.
2476
+ */
2477
+ registerToolInterceptor(toolName, handler) {
2478
+ if (!this.nativeBinding) {
2479
+ throw new Error("Native tool interceptor registration requires the NAPI binding");
2480
+ }
2481
+ if (typeof this.nativeBinding.registerToolInterceptor !== "function" || typeof this.nativeBinding.callToolBuiltin !== "function") {
2482
+ console.warn("registerToolInterceptor is not available in this build - interceptor feature may be incomplete");
2483
+ return;
2484
+ }
2485
+ this.nativeBinding.registerToolInterceptor(toolName, async (...args) => {
2486
+ const context = args.length === 1 ? args[0] : args[1];
2487
+ if (!context || typeof context !== "object") {
2488
+ throw new Error("Native interceptor callback did not receive a context object");
2489
+ }
2490
+ const { invocation, token } = context;
2491
+ const callBuiltin = (override) => this.nativeBinding.callToolBuiltin(token, override ?? invocation);
2492
+ return handler({ invocation, callBuiltin });
2493
+ });
2494
+ }
2495
+ /**
2496
+ * Clear all registered tools, restoring built-in defaults.
2497
+ */
2498
+ clearTools() {
2499
+ if (!this.nativeBinding) {
2500
+ throw new Error("Native tool management requires the NAPI binding");
2501
+ }
2502
+ if (typeof this.nativeBinding.clearRegisteredTools === "function") {
2503
+ this.nativeBinding.clearRegisteredTools();
2504
+ }
2505
+ if (this.options.tools) {
2506
+ this.options.tools = [];
2507
+ }
2508
+ }
2509
+ buildConversationConfig(options = {}) {
2510
+ return {
2511
+ model: options.model ?? this.options.defaultModel,
2512
+ modelProvider: options.modelProvider ?? this.options.modelProvider,
2513
+ oss: options.oss,
2514
+ sandboxMode: options.sandboxMode,
2515
+ approvalMode: options.approvalMode,
2516
+ workspaceWriteOptions: options.workspaceWriteOptions,
2517
+ workingDirectory: options.workingDirectory,
2518
+ skipGitRepoCheck: options.skipGitRepoCheck,
2519
+ reasoningEffort: options.reasoningEffort,
2520
+ reasoningSummary: options.reasoningSummary,
2521
+ fullAuto: options.fullAuto,
2522
+ baseUrl: this.options.baseUrl,
2523
+ apiKey: this.options.apiKey
2524
+ };
2525
+ }
2526
+ createLspManagerForTools() {
2527
+ const cwd = typeof process !== "undefined" && typeof process.cwd === "function" ? process.cwd() : ".";
2528
+ const options = {
2529
+ workingDirectory: cwd,
2530
+ waitForDiagnostics: true
2531
+ };
2532
+ try {
2533
+ return new LspManager(options);
2534
+ } catch {
2535
+ return null;
2536
+ }
2537
+ }
2538
+ registerDefaultReadFileInterceptor() {
2539
+ if (!this.lspForTools) {
2540
+ return;
2541
+ }
2542
+ try {
2543
+ this.registerToolInterceptor("read_file", async ({ invocation, callBuiltin }) => {
2544
+ let base;
2545
+ try {
2546
+ base = await callBuiltin();
2547
+ } catch (err) {
2548
+ return {
2549
+ success: false,
2550
+ error: err instanceof Error ? err.message : String(err),
2551
+ output: void 0
2552
+ };
2553
+ }
2554
+ if (!base.output || base.success === false) {
2555
+ return base;
2556
+ }
2557
+ let filePath;
2558
+ if (invocation.arguments) {
2559
+ try {
2560
+ const args = JSON.parse(invocation.arguments);
2561
+ const candidate = typeof args.file_path === "string" && args.file_path || typeof args.path === "string" && args.path || void 0;
2562
+ if (candidate && candidate.trim().length > 0) {
2563
+ filePath = candidate;
2564
+ }
2565
+ } catch {
2566
+ }
2567
+ }
2568
+ if (!filePath) {
2569
+ return base;
2570
+ }
2571
+ let diagnosticsText = "";
2572
+ try {
2573
+ const results = await this.lspForTools.collectDiagnostics([filePath]);
2574
+ if (!results.length) {
2575
+ return base;
2576
+ }
2577
+ diagnosticsText = formatDiagnosticsForTool(results);
2578
+ } catch {
2579
+ return base;
2580
+ }
2581
+ if (!diagnosticsText) {
2582
+ return base;
2583
+ }
2584
+ const header = `LSP diagnostics for ${filePath}:
2585
+ ${diagnosticsText}`;
2586
+ return prependSystemHintToToolResult(base, header);
2587
+ });
2588
+ } catch {
2589
+ }
2590
+ }
2591
+ /**
2592
+ * Register a programmatic approval callback that Codex will call before executing
2593
+ * sensitive operations (e.g., shell commands, file writes).
2594
+ */
2595
+ setApprovalCallback(handler) {
2596
+ if (!this.nativeBinding || typeof this.nativeBinding.registerApprovalCallback !== "function") {
2597
+ console.warn("Approval callback is not available in this build");
2598
+ return;
2599
+ }
2600
+ this.nativeBinding.registerApprovalCallback(handler);
2601
+ }
2602
+ /**
2603
+ * Starts a new conversation with an agent.
2604
+ * @returns A new thread instance.
2605
+ */
2606
+ startThread(options = {}) {
2607
+ const threadOptions = {
2608
+ ...options,
2609
+ model: options.model ?? this.options.defaultModel
2610
+ };
2611
+ return new Thread(this.exec, this.options, threadOptions);
2612
+ }
2613
+ /**
2614
+ * Resumes a conversation with an agent based on the thread id.
2615
+ * Threads are persisted in ~/.codex/sessions.
2616
+ *
2617
+ * @param id The id of the thread to resume.
2618
+ * @returns A new thread instance.
2619
+ */
2620
+ resumeThread(id, options = {}) {
2621
+ const threadOptions = {
2622
+ ...options,
2623
+ model: options.model ?? this.options.defaultModel
2624
+ };
2625
+ return new Thread(this.exec, this.options, threadOptions, id);
2626
+ }
2627
+ async listConversations(options = {}) {
2628
+ const request = {
2629
+ config: this.buildConversationConfig(options),
2630
+ pageSize: options.pageSize,
2631
+ cursor: options.cursor,
2632
+ modelProviders: options.modelProviders
2633
+ };
2634
+ return this.exec.listConversations(request);
2635
+ }
2636
+ async deleteConversation(id, options = {}) {
2637
+ const result = await this.exec.deleteConversation({
2638
+ id,
2639
+ config: this.buildConversationConfig(options)
2640
+ });
2641
+ return result.deleted;
2642
+ }
2643
+ async resumeConversationFromRollout(rolloutPath, options = {}) {
2644
+ const result = await this.exec.resumeConversationFromRollout({
2645
+ rolloutPath,
2646
+ config: this.buildConversationConfig(options)
2647
+ });
2648
+ const threadOptions = {
2649
+ ...options,
2650
+ model: options.model ?? this.options.defaultModel
2651
+ };
2652
+ return new Thread(this.exec, this.options, threadOptions, result.threadId);
2653
+ }
2654
+ /**
2655
+ * Starts a review task using the built-in Codex review flow.
2656
+ */
2657
+ async review(options) {
2658
+ const generator = this.reviewStreamedInternal(options);
2659
+ const items = [];
2660
+ let finalResponse = "";
2661
+ let usage = null;
2662
+ let turnFailure = null;
2663
+ for await (const event of generator) {
2664
+ if (event === null) continue;
2665
+ if (event.type === "item.completed") {
2666
+ if (event.item.type === "agent_message") {
2667
+ finalResponse = event.item.text;
2668
+ }
2669
+ items.push(event.item);
2670
+ } else if (event.type === "exited_review_mode") {
2671
+ if (event.review_output) {
2672
+ const reviewOutput = event.review_output;
2673
+ let reviewText = "";
2674
+ if (reviewOutput.overall_explanation) {
2675
+ reviewText += reviewOutput.overall_explanation;
2676
+ }
2677
+ if (reviewOutput.findings && reviewOutput.findings.length > 0) {
2678
+ if (reviewText) reviewText += "\n\n";
2679
+ reviewText += "## Review Findings\n\n";
2680
+ reviewOutput.findings.forEach((finding, index) => {
2681
+ reviewText += `### ${index + 1}. ${finding.title}
2682
+ `;
2683
+ reviewText += `${finding.body}
2684
+ `;
2685
+ reviewText += `**Priority:** ${finding.priority} | **Confidence:** ${finding.confidence_score}
2686
+ `;
2687
+ reviewText += `**Location:** ${finding.code_location.absolute_file_path}:${finding.code_location.line_range.start}-${finding.code_location.line_range.end}
2688
+
2689
+ `;
2690
+ });
2691
+ }
2692
+ finalResponse = reviewText;
2693
+ }
2694
+ } else if (event.type === "turn.completed") {
2695
+ usage = event.usage;
2696
+ } else if (event.type === "turn.failed") {
2697
+ turnFailure = event.error;
2698
+ break;
2699
+ }
2700
+ }
2701
+ if (turnFailure) {
2702
+ throw new Error(turnFailure.message);
2703
+ }
2704
+ return { items, finalResponse, usage };
2705
+ }
2706
+ /**
2707
+ * Starts a review task and returns the event stream.
2708
+ */
2709
+ async reviewStreamed(options) {
2710
+ return { events: this.reviewStreamedInternal(options) };
2711
+ }
2712
+ async *reviewStreamedInternal(options) {
2713
+ const { target, threadOptions = {}, turnOptions = {} } = options;
2714
+ const { prompt, hint } = buildReviewPrompt(target);
2715
+ const normalizedSchema = normalizeOutputSchema(turnOptions.outputSchema);
2716
+ const needsSchemaFile = this.exec.requiresOutputSchemaFile();
2717
+ const schemaFile = needsSchemaFile ? await createOutputSchemaFile(normalizedSchema) : { schemaPath: void 0, cleanup: async () => {
2718
+ } };
2719
+ const generator = this.exec.run({
2720
+ input: prompt,
2721
+ baseUrl: this.options.baseUrl,
2722
+ apiKey: this.options.apiKey,
2723
+ model: threadOptions.model,
2724
+ modelProvider: threadOptions.modelProvider ?? this.options.modelProvider,
2725
+ oss: threadOptions.oss,
2726
+ sandboxMode: threadOptions.sandboxMode,
2727
+ approvalMode: threadOptions.approvalMode,
2728
+ workspaceWriteOptions: threadOptions.workspaceWriteOptions,
2729
+ workingDirectory: threadOptions.workingDirectory,
2730
+ skipGitRepoCheck: threadOptions.skipGitRepoCheck,
2731
+ outputSchemaFile: schemaFile.schemaPath,
2732
+ outputSchema: normalizedSchema,
2733
+ fullAuto: threadOptions.fullAuto,
2734
+ review: {
2735
+ userFacingHint: hint
2736
+ }
2737
+ });
2738
+ try {
2739
+ for await (const item of generator) {
2740
+ let parsed;
2741
+ try {
2742
+ parsed = JSON.parse(item);
2743
+ } catch (error) {
2744
+ throw new Error(`Failed to parse item: ${item}`, { cause: error });
2745
+ }
2746
+ yield parsed;
2747
+ }
2748
+ } finally {
2749
+ await schemaFile.cleanup();
2750
+ }
2751
+ }
2752
+ };
2753
+ function prependSystemHintToToolResult(base, hint) {
2754
+ const trimmedHint = hint.trim();
2755
+ if (!trimmedHint) {
2756
+ return base;
2757
+ }
2758
+ const existing = base.output ?? "";
2759
+ const separator = existing.length === 0 || existing.startsWith("\n") ? "\n\n" : "\n\n";
2760
+ const output = existing.length === 0 ? `[SYSTEM_HINT]
2761
+ ${trimmedHint}` : `[SYSTEM_HINT]
2762
+ ${trimmedHint}${separator}${existing}`;
2763
+ return {
2764
+ ...base,
2765
+ output
2766
+ };
2767
+ }
2768
+
2769
+ // src/cli/tui.ts
2770
+ async function executeTuiCommand(argv, context) {
2771
+ if (!import_node_process2.default.stdout.isTTY || !import_node_process2.default.stdin.isTTY) {
2772
+ throw new Error("The interactive TUI requires an interactive terminal (TTY).");
2773
+ }
2774
+ const { combinedConfig } = context;
2775
+ emitWarnings(combinedConfig.warnings);
2776
+ const warningCount = combinedConfig.warnings.length;
2777
+ const { request, thread: threadOptions } = buildTuiConfig({
2778
+ argv,
2779
+ defaults: combinedConfig.tuiDefaults,
2780
+ cwd: context.cwd
2781
+ });
2782
+ applyElevatedTuiDefaults({ request, thread: threadOptions, cwd: context.cwd });
2783
+ const hookContext = {
2784
+ command: "tui",
2785
+ cwd: context.cwd,
2786
+ options: argv
2787
+ };
2788
+ await runBeforeStartHooks(combinedConfig.beforeStartHooks, hookContext, combinedConfig.warnings);
2789
+ const codex = new Codex({
2790
+ baseUrl: request.baseUrl,
2791
+ apiKey: request.apiKey,
2792
+ preserveRegisteredTools: true
2793
+ });
2794
+ const thread = codex.startThread(threadOptions);
2795
+ const exitInfo = await thread.tui(request);
2796
+ if (exitInfo.conversationId) {
2797
+ import_node_process2.default.stdout.write(`
2798
+ Conversation ID: ${exitInfo.conversationId}
2799
+ `);
2800
+ }
2801
+ if (exitInfo.updateAction) {
2802
+ import_node_process2.default.stdout.write(
2803
+ `Update available (${exitInfo.updateAction.kind}): ${exitInfo.updateAction.command}
2804
+ `
2805
+ );
2806
+ }
2807
+ emitWarnings(combinedConfig.warnings, warningCount);
2808
+ }
2809
+ function buildTuiConfig(params) {
2810
+ const { argv, defaults, cwd } = params;
2811
+ const request = {
2812
+ ...defaults
2813
+ };
2814
+ if (argv.prompt !== void 0) request.prompt = argv.prompt;
2815
+ if (argv.model !== void 0) request.model = argv.model;
2816
+ if (argv.oss !== void 0) request.oss = argv.oss;
2817
+ const sandboxMode = parseSandboxModeFlag(argv.sandbox, "--sandbox");
2818
+ if (sandboxMode !== void 0) {
2819
+ request.sandboxMode = sandboxMode;
2820
+ }
2821
+ const approvalMode = parseApprovalModeFlag(argv.approval, "--approval");
2822
+ if (approvalMode !== void 0) {
2823
+ request.approvalMode = approvalMode;
2824
+ }
2825
+ if (argv.resume !== void 0) request.resumeSessionId = argv.resume;
2826
+ if (argv.resumeLast !== void 0) request.resumeLast = argv.resumeLast;
2827
+ if (argv.resumePicker !== void 0) request.resumePicker = argv.resumePicker;
2828
+ if (argv.fullAuto !== void 0) request.fullAuto = argv.fullAuto;
2829
+ if (argv.dangerouslyBypassApprovalsAndSandbox !== void 0) {
2830
+ request.dangerouslyBypassApprovalsAndSandbox = argv.dangerouslyBypassApprovalsAndSandbox;
2831
+ }
2832
+ if (argv.cd !== void 0) request.workingDirectory = argv.cd;
2833
+ if (argv.configProfile !== void 0) request.configProfile = argv.configProfile;
2834
+ if (argv.webSearch !== void 0) request.webSearch = argv.webSearch;
2835
+ if (argv.linuxSandboxPath !== void 0) request.linuxSandboxPath = argv.linuxSandboxPath;
2836
+ if (argv.baseUrl !== void 0) request.baseUrl = argv.baseUrl;
2837
+ if (argv.apiKey !== void 0) request.apiKey = argv.apiKey;
2838
+ if (argv.configOverrides) {
2839
+ const defaultsOverrides = Array.isArray(request.configOverrides) ? [...request.configOverrides] : [];
2840
+ request.configOverrides = [...defaultsOverrides, ...argv.configOverrides];
2841
+ }
2842
+ if (argv.addDir) {
2843
+ const defaultsAddDir = Array.isArray(request.addDir) ? [...request.addDir] : [];
2844
+ request.addDir = [...defaultsAddDir, ...argv.addDir];
2845
+ }
2846
+ if (argv.image) {
2847
+ const defaultsImages = Array.isArray(request.images) ? [...request.images] : [];
2848
+ request.images = [...defaultsImages, ...argv.image];
2849
+ }
2850
+ const thread = {
2851
+ model: request.model,
2852
+ oss: request.oss,
2853
+ sandboxMode: request.sandboxMode,
2854
+ approvalMode: request.approvalMode,
2855
+ workingDirectory: request.workingDirectory ?? cwd,
2856
+ skipGitRepoCheck: false
2857
+ };
2858
+ return { request, thread };
2859
+ }
2860
+
2861
+ // src/cli/reverie.ts
2862
+ var import_node_path6 = __toESM(require("path"));
2863
+ var import_node_os2 = __toESM(require("os"));
2864
+ var import_node_util = require("util");
2865
+ var DEFAULT_MODEL = "mixedbread-ai/mxbai-embed-large-v1";
2866
+ var INDEX_OPTION_DEFS = {
2867
+ "codex-home": { type: "string" },
2868
+ "project-root": { type: "string" },
2869
+ limit: { type: "string" },
2870
+ "max-candidates": { type: "string" },
2871
+ "batch-size": { type: "string" },
2872
+ normalize: { type: "boolean" },
2873
+ cache: { type: "boolean" },
2874
+ "embed-model": { type: "string" },
2875
+ "embed-cache-dir": { type: "string" },
2876
+ "embed-max-length": { type: "string" },
2877
+ "no-progress": { type: "boolean" },
2878
+ "skip-embed-init": { type: "boolean" }
2879
+ };
2880
+ async function executeReverieCommand(args) {
2881
+ const [first, ...rest] = args;
2882
+ const isFlag = first?.startsWith("-");
2883
+ const command = !first || isFlag ? "index" : first;
2884
+ const tail = !first || isFlag ? args : rest;
2885
+ if (command !== "index") {
2886
+ throw new Error(`Unknown reverie command '${command}'. Supported subcommands: index`);
2887
+ }
2888
+ await runReverieIndex(tail);
2889
+ }
2890
+ async function runReverieIndex(args) {
2891
+ const { values } = (0, import_node_util.parseArgs)({ args, options: INDEX_OPTION_DEFS, allowPositionals: false, strict: true });
2892
+ const codexHome = resolveCodexHome(values["codex-home"]);
2893
+ const projectRoot = resolveProjectRoot(values["project-root"]);
2894
+ const limit = parseOptionalInt(values.limit);
2895
+ const maxCandidates = parseOptionalInt(values["max-candidates"]);
2896
+ const batchSize = parseOptionalInt(values["batch-size"]);
2897
+ const embedMaxLength = parseOptionalInt(values["embed-max-length"]);
2898
+ const normalize = typeof values.normalize === "boolean" ? values.normalize : void 0;
2899
+ const cache = typeof values.cache === "boolean" ? values.cache : void 0;
2900
+ const embedModel = typeof values["embed-model"] === "string" ? values["embed-model"] : DEFAULT_MODEL;
2901
+ const embedCacheDir = typeof values["embed-cache-dir"] === "string" ? values["embed-cache-dir"] : void 0;
2902
+ const showDownloadProgress = values["no-progress"] ? false : true;
2903
+ const skipEmbedInit = values["skip-embed-init"] === true;
2904
+ if (!skipEmbedInit) {
2905
+ await fastEmbedInit({
2906
+ model: embedModel,
2907
+ cacheDir: embedCacheDir ? import_node_path6.default.resolve(embedCacheDir) : defaultCacheDir(),
2908
+ maxLength: embedMaxLength ?? void 0,
2909
+ showDownloadProgress
2910
+ });
2911
+ }
2912
+ const options = {
2913
+ limit,
2914
+ maxCandidates,
2915
+ projectRoot,
2916
+ batchSize,
2917
+ normalize,
2918
+ cache
2919
+ };
2920
+ console.log(`\u{1F4C2} Codex home: ${codexHome}`);
2921
+ console.log(`\u{1F4C1} Project root: ${projectRoot}`);
2922
+ const stats = await reverieIndexSemantic(codexHome, options);
2923
+ console.log(
2924
+ `\u2705 Indexed ${stats.documentsEmbedded} conversation(s) across ${stats.batches} batch(es); cache warmed at ${projectRoot}`
2925
+ );
2926
+ }
2927
+ function resolveCodexHome(explicit) {
2928
+ if (explicit) {
2929
+ return import_node_path6.default.resolve(explicit);
2930
+ }
2931
+ if (process.env.CODEX_HOME) {
2932
+ return process.env.CODEX_HOME;
2933
+ }
2934
+ const home = import_node_os2.default.homedir() || process.cwd();
2935
+ return import_node_path6.default.join(home, ".codex");
2936
+ }
2937
+ function resolveProjectRoot(explicit) {
2938
+ if (explicit) {
2939
+ return import_node_path6.default.resolve(explicit);
2940
+ }
2941
+ return process.cwd();
2942
+ }
2943
+ function parseOptionalInt(value) {
2944
+ if (typeof value === "string" && value.trim().length > 0) {
2945
+ const parsed = Number(value);
2946
+ if (!Number.isNaN(parsed)) {
2947
+ return parsed;
2948
+ }
2949
+ }
2950
+ return void 0;
2951
+ }
2952
+ function defaultCacheDir() {
2953
+ if (process.env.CODEX_EMBED_CACHE) {
2954
+ return import_node_path6.default.resolve(process.env.CODEX_EMBED_CACHE);
2955
+ }
2956
+ return import_node_path6.default.join(import_node_os2.default.tmpdir(), "codex-embed-cache");
2957
+ }
2958
+
2959
+ // src/cli/runtime.ts
2960
+ async function buildCombinedConfig(params) {
2961
+ const { cwd, config } = params;
2962
+ const warnings = [...config.warnings];
2963
+ const combined = {
2964
+ runDefaults: {},
2965
+ tuiDefaults: {},
2966
+ tools: [],
2967
+ interceptors: [],
2968
+ approval: void 0,
2969
+ beforeStartHooks: [],
2970
+ onEventHooks: [],
2971
+ warnings,
2972
+ allowReservedInterceptors: false
2973
+ };
2974
+ const pluginContext = { cwd, configPath: config.configPath };
2975
+ if (config.config) {
2976
+ accumulateConfig({
2977
+ combined,
2978
+ config: config.config,
2979
+ source: config.configPath ?? "config",
2980
+ warnings
2981
+ });
2982
+ }
2983
+ for (const loaded of config.plugins) {
2984
+ const pluginConfig = await evaluatePlugin(loaded, pluginContext, warnings);
2985
+ if (pluginConfig) {
2986
+ accumulateConfig({
2987
+ combined,
2988
+ config: pluginConfig,
2989
+ source: loaded.spec,
2990
+ warnings
2991
+ });
2992
+ }
2993
+ }
2994
+ combined.warnings = warnings;
2995
+ return combined;
2996
+ }
2997
+ function applyNativeRegistrations(combined) {
2998
+ const binding = getNativeBinding();
2999
+ if (!binding) {
3000
+ throw new Error("Native binding is not available.");
3001
+ }
3002
+ binding.clearRegisteredTools();
3003
+ const seenTools = /* @__PURE__ */ new Set();
3004
+ for (const tool of combined.tools) {
3005
+ const { handler, ...info } = tool;
3006
+ const name = String(info.name);
3007
+ if (seenTools.has(name)) {
3008
+ combined.warnings.push(`Duplicate tool "${name}" ignored (first definition wins).`);
3009
+ continue;
3010
+ }
3011
+ seenTools.add(name);
3012
+ binding.registerTool(info, handler);
3013
+ }
3014
+ if (combined.approval && typeof binding.registerApprovalCallback === "function") {
3015
+ binding.registerApprovalCallback(combined.approval.handler);
3016
+ }
3017
+ const RESERVED = /* @__PURE__ */ new Set(["local_shell", "exec_command", "apply_patch", "web_search"]);
3018
+ const seenInterceptors = /* @__PURE__ */ new Set();
3019
+ for (const interceptor of combined.interceptors) {
3020
+ const name = interceptor.toolName;
3021
+ if (RESERVED.has(name) && !combined.allowReservedInterceptors) {
3022
+ combined.warnings.push(
3023
+ `Interceptor for "${name}" ignored: reserved for approval gating. Use approvals() hook instead.`
3024
+ );
3025
+ continue;
3026
+ }
3027
+ if (seenInterceptors.has(name)) {
3028
+ combined.warnings.push(
3029
+ `Multiple interceptors for "${name}" detected; only the first will be used.`
3030
+ );
3031
+ continue;
3032
+ }
3033
+ seenInterceptors.add(name);
3034
+ binding.registerToolInterceptor(interceptor.toolName, interceptor.handler);
3035
+ }
3036
+ }
3037
+ async function evaluatePlugin(loaded, context, warnings) {
3038
+ const { plugin, spec } = loaded;
3039
+ try {
3040
+ if (typeof plugin === "function") {
3041
+ const result = await plugin(context);
3042
+ return coerceConfig(result, spec, warnings);
3043
+ }
3044
+ if (plugin && typeof plugin === "object") {
3045
+ const candidate = plugin;
3046
+ if (typeof candidate.setup === "function") {
3047
+ await candidate.setup(context);
3048
+ }
3049
+ if (typeof candidate.config === "function") {
3050
+ return coerceConfig(await candidate.config(context), spec, warnings);
3051
+ }
3052
+ if (candidate.config) {
3053
+ return coerceConfig(candidate.config, spec, warnings);
3054
+ }
3055
+ return coerceConfig(candidate, spec, warnings);
3056
+ }
3057
+ return coerceConfig(plugin, spec, warnings);
3058
+ } catch (error) {
3059
+ warnings.push(`Plugin "${spec}" threw an error: ${error.message}`);
3060
+ return null;
3061
+ }
3062
+ }
3063
+ function coerceConfig(value, source, warnings) {
3064
+ if (!value) {
3065
+ return null;
3066
+ }
3067
+ if (typeof value === "object") {
3068
+ return value;
3069
+ }
3070
+ warnings.push(`Plugin "${source}" did not return a config object.`);
3071
+ return null;
3072
+ }
3073
+ function accumulateConfig(params) {
3074
+ const { combined, config, source, warnings } = params;
3075
+ if (config.defaults?.run) {
3076
+ combined.runDefaults = { ...combined.runDefaults, ...config.defaults.run };
3077
+ }
3078
+ if (config.defaults?.tui) {
3079
+ combined.tuiDefaults = { ...combined.tuiDefaults, ...config.defaults.tui };
3080
+ }
3081
+ if (Array.isArray(config.tools)) {
3082
+ for (const tool of config.tools) {
3083
+ if (!tool || typeof tool !== "object" || typeof tool.handler !== "function") {
3084
+ warnings.push(`Invalid tool definition supplied by "${source}".`);
3085
+ continue;
3086
+ }
3087
+ combined.tools.push(tool);
3088
+ }
3089
+ }
3090
+ if (Array.isArray(config.interceptors)) {
3091
+ for (const interceptor of config.interceptors) {
3092
+ if (!interceptor || typeof interceptor !== "object" || typeof interceptor.toolName !== "string" || typeof interceptor.handler !== "function") {
3093
+ warnings.push(`Invalid interceptor definition supplied by "${source}".`);
3094
+ continue;
3095
+ }
3096
+ combined.interceptors.push(interceptor);
3097
+ }
3098
+ }
3099
+ if (config.approvals) {
3100
+ if (typeof config.approvals !== "function") {
3101
+ warnings.push(`Approval callback from "${source}" must be a function.`);
3102
+ } else {
3103
+ if (combined.approval) {
3104
+ warnings.push(
3105
+ `Approval callback from "${source}" overrides handler from "${combined.approval.source}".`
3106
+ );
3107
+ }
3108
+ combined.approval = { source, handler: config.approvals };
3109
+ }
3110
+ }
3111
+ if (config.hooks) {
3112
+ addHooks(combined, config.hooks, source, warnings);
3113
+ }
3114
+ if (config.allowReservedInterceptors === true) {
3115
+ combined.allowReservedInterceptors = true;
3116
+ }
3117
+ }
3118
+ function addHooks(combined, hooks, source, warnings) {
3119
+ if (hooks.beforeStart) {
3120
+ const beforeStartCallbacks = Array.isArray(hooks.beforeStart) ? hooks.beforeStart : [hooks.beforeStart];
3121
+ for (const callback of beforeStartCallbacks) {
3122
+ if (typeof callback !== "function") {
3123
+ warnings.push(`beforeStart hook from "${source}" must be a function.`);
3124
+ continue;
3125
+ }
3126
+ combined.beforeStartHooks.push({ source, callback });
3127
+ }
3128
+ }
3129
+ if (hooks.onEvent) {
3130
+ const eventCallbacks = Array.isArray(hooks.onEvent) ? hooks.onEvent : [hooks.onEvent];
3131
+ for (const callback of eventCallbacks) {
3132
+ if (typeof callback !== "function") {
3133
+ warnings.push(`onEvent hook from "${source}" must be a function.`);
3134
+ continue;
3135
+ }
3136
+ combined.onEventHooks.push({ source, callback });
3137
+ }
3138
+ }
3139
+ }
3140
+
3141
+ // src/cli/index.ts
3142
+ var VERSION = package_default.version;
3143
+ var SANDBOX_CHOICES = ["read-only", "workspace-write", "danger-full-access"];
3144
+ var APPROVAL_CHOICES = ["never", "on-request", "on-failure", "untrusted"];
3145
+ var APPLY_PATCH_FLAG = "--codex-run-as-apply-patch";
3146
+ var CLI_ENTRYPOINT_ENV2 = "CODEX_NODE_CLI_ENTRYPOINT";
3147
+ try {
3148
+ const entrypoint = (0, import_node_url4.fileURLToPath)(importMetaUrl);
3149
+ import_node_process3.default.env[CLI_ENTRYPOINT_ENV2] = entrypoint;
3150
+ } catch {
3151
+ if (import_node_process3.default.argv[1]) {
3152
+ import_node_process3.default.env[CLI_ENTRYPOINT_ENV2] = import_node_process3.default.argv[1];
3153
+ }
3154
+ }
3155
+ var GLOBAL_OPTION_DEFS = {
3156
+ config: { type: "string" },
3157
+ "no-config": { type: "boolean" },
3158
+ plugin: { type: "string", multiple: true }
3159
+ };
3160
+ var RUN_OPTION_DEFS = {
3161
+ model: { type: "string" },
3162
+ oss: { type: "boolean" },
3163
+ sandbox: { type: "string" },
3164
+ approval: { type: "string" },
3165
+ schema: { type: "string" },
3166
+ "thread-id": { type: "string" },
3167
+ "base-url": { type: "string" },
3168
+ "api-key": { type: "string" },
3169
+ "linux-sandbox-path": { type: "string" },
3170
+ "full-auto": { type: "boolean" },
3171
+ "skip-git-repo-check": { type: "boolean" },
3172
+ cd: { type: "string" },
3173
+ image: { type: "string", multiple: true },
3174
+ "review-mode": { type: "boolean" },
3175
+ "review-hint": { type: "string" }
3176
+ };
3177
+ var TUI_OPTION_DEFS = {
3178
+ model: { type: "string" },
3179
+ oss: { type: "boolean" },
3180
+ sandbox: { type: "string" },
3181
+ approval: { type: "string" },
3182
+ resume: { type: "string" },
3183
+ "resume-last": { type: "boolean" },
3184
+ "resume-picker": { type: "boolean" },
3185
+ "full-auto": { type: "boolean" },
3186
+ "dangerously-bypass-approvals-and-sandbox": { type: "boolean" },
3187
+ cd: { type: "string" },
3188
+ "config-profile": { type: "string" },
3189
+ "config-overrides": { type: "string", multiple: true },
3190
+ "add-dir": { type: "string", multiple: true },
3191
+ image: { type: "string", multiple: true },
3192
+ "web-search": { type: "boolean" },
3193
+ "linux-sandbox-path": { type: "string" },
3194
+ "base-url": { type: "string" },
3195
+ "api-key": { type: "string" }
3196
+ };
3197
+ async function main() {
3198
+ const rawArgs = import_node_process3.default.argv.slice(2);
3199
+ if (maybeHandleApplyPatch(rawArgs)) {
3200
+ return;
3201
+ }
3202
+ if (hasFlag(rawArgs, "--version") || hasFlag(rawArgs, "-v")) {
3203
+ printVersion();
3204
+ return;
3205
+ }
3206
+ const generalHelpRequested = hasFlag(rawArgs, "--help") || hasFlag(rawArgs, "-h");
3207
+ if (generalHelpRequested && !hasExplicitCommand(rawArgs)) {
3208
+ printGeneralHelp();
3209
+ return;
3210
+ }
3211
+ const { command, args } = selectCommand(rawArgs);
3212
+ if (hasCommandHelpFlag(args)) {
3213
+ printCommandHelp(command);
3214
+ return;
3215
+ }
3216
+ if (command === "reverie-index") {
3217
+ await executeReverieCommand(args);
3218
+ return;
3219
+ }
3220
+ const options = command === "tui" ? parseTuiCommand(args) : parseRunCommand(args);
3221
+ validateOptionChoices(command, options);
3222
+ const context = await createContext(options);
3223
+ if (command === "tui") {
3224
+ await executeTuiCommand(options, context);
3225
+ } else {
3226
+ await executeRunCommand(options, context);
3227
+ }
3228
+ }
3229
+ function maybeHandleApplyPatch(args) {
3230
+ if (args.length === 0 || args[0] !== APPLY_PATCH_FLAG) {
3231
+ return false;
3232
+ }
3233
+ const patch = args[1];
3234
+ if (!patch) {
3235
+ console.error(`${APPLY_PATCH_FLAG} requires a patch argument.`);
3236
+ import_node_process3.default.exitCode = 1;
3237
+ return true;
3238
+ }
3239
+ try {
3240
+ runApplyPatch(patch);
3241
+ } catch (error) {
3242
+ const message = error instanceof Error ? error.message : String(error);
3243
+ console.error(`apply_patch failed: ${message}`);
3244
+ import_node_process3.default.exitCode = 1;
3245
+ }
3246
+ return true;
3247
+ }
3248
+ function selectCommand(argv) {
3249
+ if (argv.length > 0) {
3250
+ const [first, ...rest] = argv;
3251
+ if (first === "tui") {
3252
+ return { command: "tui", args: rest };
3253
+ }
3254
+ if (first === "run") {
3255
+ return { command: "run", args: rest };
3256
+ }
3257
+ if (first === "reverie") {
3258
+ if (rest.length === 0) {
3259
+ return { command: "reverie-index", args: [] };
3260
+ }
3261
+ return { command: "reverie-index", args: rest };
3262
+ }
3263
+ }
3264
+ if (argv.length === 0) {
3265
+ const isInteractive2 = import_node_process3.default.stdout.isTTY && import_node_process3.default.stdin.isTTY;
3266
+ return { command: isInteractive2 ? "tui" : "run", args: [] };
3267
+ }
3268
+ const isInteractive = import_node_process3.default.stdout.isTTY && import_node_process3.default.stdin.isTTY;
3269
+ return { command: isInteractive ? "tui" : "run", args: argv };
3270
+ }
3271
+ function hasExplicitCommand(argv) {
3272
+ if (argv.length === 0) {
3273
+ return false;
3274
+ }
3275
+ const first = argv[0];
3276
+ return first === "tui" || first === "run" || first === "reverie";
3277
+ }
3278
+ function parseRunCommand(args) {
3279
+ const { values, positionals } = (0, import_node_util2.parseArgs)({
3280
+ args,
3281
+ options: { ...GLOBAL_OPTION_DEFS, ...RUN_OPTION_DEFS },
3282
+ allowPositionals: true,
3283
+ strict: true
3284
+ });
3285
+ const options = camelCaseKeys(values);
3286
+ const runOptions = {
3287
+ ...options
3288
+ };
3289
+ if (!runOptions.prompt && positionals.length > 0) {
3290
+ runOptions.prompt = positionals[0];
3291
+ }
3292
+ return runOptions;
3293
+ }
3294
+ function parseTuiCommand(args) {
3295
+ const { values, positionals } = (0, import_node_util2.parseArgs)({
3296
+ args,
3297
+ options: { ...GLOBAL_OPTION_DEFS, ...TUI_OPTION_DEFS },
3298
+ allowPositionals: true,
3299
+ strict: true
3300
+ });
3301
+ const options = camelCaseKeys(values);
3302
+ const tuiOptions = {
3303
+ ...options
3304
+ };
3305
+ if (!tuiOptions.prompt && positionals.length > 0) {
3306
+ tuiOptions.prompt = positionals[0];
3307
+ }
3308
+ return tuiOptions;
3309
+ }
3310
+ async function createContext(options) {
3311
+ const cwd = import_node_process3.default.cwd();
3312
+ const configOptions = {
3313
+ cwd,
3314
+ explicitConfigPath: options.config,
3315
+ noConfig: options.noConfig,
3316
+ pluginPaths: normalizeStringArray(options.plugin)
3317
+ };
3318
+ const config = await loadCliConfig(configOptions);
3319
+ const combinedConfig = await buildCombinedConfig({ cwd, config });
3320
+ applyNativeRegistrations(combinedConfig);
3321
+ return { cwd, config, combinedConfig };
3322
+ }
3323
+ function normalizeStringArray(value) {
3324
+ if (Array.isArray(value)) {
3325
+ return value.map((item) => String(item));
3326
+ }
3327
+ if (typeof value === "string") {
3328
+ return [value];
3329
+ }
3330
+ return [];
3331
+ }
3332
+ function camelCaseKeys(record) {
3333
+ return Object.entries(record).reduce((acc, [key, value]) => {
3334
+ acc[toCamelCase(key)] = value;
3335
+ return acc;
3336
+ }, {});
3337
+ }
3338
+ function toCamelCase(value) {
3339
+ return value.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
3340
+ }
3341
+ function hasFlag(args, flag) {
3342
+ return args.includes(flag);
3343
+ }
3344
+ function hasCommandHelpFlag(args) {
3345
+ return hasFlag(args, "--help") || hasFlag(args, "-h");
3346
+ }
3347
+ function printVersion() {
3348
+ console.log(VERSION);
3349
+ }
3350
+ function printGeneralHelp() {
3351
+ console.log(`codex-native v${VERSION}
3352
+
3353
+ Usage:
3354
+ codex-native [options] [prompt]
3355
+ codex-native run [options] [prompt]
3356
+ codex-native tui [options] [prompt]
3357
+
3358
+ Default behavior:
3359
+ Running 'codex-native' without arguments launches the interactive TUI.
3360
+ Use 'codex-native run <prompt>' for non-interactive exec mode.
3361
+
3362
+ Commands:
3363
+ (default) Launch the interactive TUI (with optional initial prompt)
3364
+ run Run Codex in non-interactive exec mode
3365
+ tui Explicitly launch the interactive TUI
3366
+ reverie index Pre-compute reverie embeddings for the current repo
3367
+
3368
+ Global options:
3369
+ --config <path> Path to codex.config.js (or similar)
3370
+ --no-config Skip automatic config discovery
3371
+ --plugin <path> Additional plugin module (repeatable)
3372
+
3373
+ Run options:
3374
+ --model <slug> Model slug to use
3375
+ --oss Use the built-in OSS provider
3376
+ --sandbox <mode> ${SANDBOX_CHOICES.join(" | ")}
3377
+ --approval <policy> ${APPROVAL_CHOICES.join(" | ")}
3378
+ --schema <file> Path to final-output JSON schema
3379
+ --thread-id <id> Resume an existing thread
3380
+ --base-url <url> Override the Codex API base URL
3381
+ --api-key <key> API key for Codex requests
3382
+ --linux-sandbox-path Path to codex-linux-sandbox binary
3383
+ --full-auto Enable workspace-write auto approvals
3384
+ --skip-git-repo-check Skip git repository validation
3385
+ --cd <path> Working directory for the run
3386
+ --image <path> Attach an image (repeatable)
3387
+ --review-mode Enable review mode
3388
+ --review-hint <text> Hint text for review mode
3389
+
3390
+ TUI options:
3391
+ --model <slug> Model slug to use
3392
+ --oss Use the built-in OSS provider
3393
+ --sandbox <mode> ${SANDBOX_CHOICES.join(" | ")}
3394
+ --approval <policy> ${APPROVAL_CHOICES.join(" | ")}
3395
+ --resume <id> Resume a saved session by id
3396
+ --resume-last Resume the most recent saved session
3397
+ --resume-picker Show the resume picker on startup
3398
+ --full-auto Enable workspace-write auto approvals
3399
+ --dangerously-bypass-approvals-and-sandbox
3400
+ Disable approvals and sandboxing (unsafe)
3401
+ --cd <path> Working directory for the session
3402
+ --config-profile <name> Config profile to activate
3403
+ --config-overrides <kv> Config overrides (key=value, repeatable)
3404
+ --add-dir <path> Additional writable directory (repeatable)
3405
+ --image <path> Attach an image (repeatable)
3406
+ --web-search Enable web search tool
3407
+ --linux-sandbox-path Path to codex-linux-sandbox binary
3408
+ --base-url <url> Override the Codex API base URL
3409
+ --api-key <key> API key for Codex requests
3410
+ `);
3411
+ }
3412
+ function printCommandHelp(command) {
3413
+ if (command === "reverie-index") {
3414
+ console.log(`codex-native reverie index [options]
3415
+
3416
+ Options:
3417
+ --codex-home <path> Override CODEX_HOME (defaults to ~/.codex)
3418
+ --project-root <path> Project root for scoping + embedding cache (default: cwd)
3419
+ --limit <n> Maximum conversations to index (default: 10)
3420
+ --max-candidates <n> Scan window before filtering (default: 80)
3421
+ --batch-size <n> Batch size forwarded to FastEmbed
3422
+ --normalize Force vector normalization (default: embed config)
3423
+ --no-normalize Disable normalization
3424
+ --cache / --no-cache Override embedding cache behavior
3425
+ --embed-model <name> FastEmbed model (default: BAAI/bge-large-en-v1.5)
3426
+ --embed-cache-dir <dir> Cache directory (defaults to $CODEX_EMBED_CACHE or system tmp)
3427
+ --embed-max-length <n> Override FastEmbed max token length
3428
+ --no-progress Hide FastEmbed download progress
3429
+ --skip-embed-init Assume fastEmbedInit was already called in this process
3430
+ `);
3431
+ return;
3432
+ }
3433
+ if (command === "tui") {
3434
+ console.log(`codex-native tui [options] [prompt]
3435
+
3436
+ Options:
3437
+ --model <slug> Model slug to use
3438
+ --oss Use the built-in OSS provider
3439
+ --sandbox <mode> ${SANDBOX_CHOICES.join(" | ")}
3440
+ --approval <policy> ${APPROVAL_CHOICES.join(" | ")}
3441
+ --resume <id> Resume a saved session by id
3442
+ --resume-last Resume the most recent saved session
3443
+ --resume-picker Show the resume picker on startup
3444
+ --full-auto Enable workspace-write auto approvals
3445
+ --dangerously-bypass-approvals-and-sandbox
3446
+ Disable approvals and sandboxing (unsafe)
3447
+ --cd <path> Working directory for the session
3448
+ --config-profile <name> Config profile to activate
3449
+ --config-overrides <kv> Config overrides (key=value, repeatable)
3450
+ --add-dir <path> Additional writable directory (repeatable)
3451
+ --image <path> Attach an image (repeatable)
3452
+ --web-search Enable web search tool
3453
+ --linux-sandbox-path Path to codex-linux-sandbox binary
3454
+ --base-url <url> Override the Codex API base URL
3455
+ --api-key <key> API key for Codex requests
3456
+ `);
3457
+ } else {
3458
+ console.log(`codex-native run [options] [prompt]
3459
+
3460
+ Options:
3461
+ --model <slug> Model slug to use
3462
+ --oss Use the built-in OSS provider
3463
+ --sandbox <mode> ${SANDBOX_CHOICES.join(" | ")}
3464
+ --approval <policy> ${APPROVAL_CHOICES.join(" | ")}
3465
+ --schema <file> Path to final-output JSON schema
3466
+ --thread-id <id> Resume an existing thread
3467
+ --base-url <url> Override the Codex API base URL
3468
+ --api-key <key> API key for Codex requests
3469
+ --linux-sandbox-path Path to codex-linux-sandbox binary
3470
+ --full-auto Enable workspace-write auto approvals
3471
+ --skip-git-repo-check Skip git repository validation
3472
+ --cd <path> Working directory for the run
3473
+ --image <path> Attach an image (repeatable)
3474
+ --review-mode Enable review mode
3475
+ --review-hint <text> Hint text for review mode
3476
+ `);
3477
+ }
3478
+ }
3479
+ function validateOptionChoices(command, options) {
3480
+ const sandbox = options.sandbox;
3481
+ if (sandbox && !SANDBOX_CHOICES.includes(sandbox)) {
3482
+ throw new Error(
3483
+ `Invalid sandbox mode "${sandbox}". Valid modes: ${SANDBOX_CHOICES.join(", ")}.`
3484
+ );
3485
+ }
3486
+ const approval = options.approval;
3487
+ if (approval && !APPROVAL_CHOICES.includes(approval)) {
3488
+ throw new Error(
3489
+ `Invalid approval policy "${approval}". Valid policies: ${APPROVAL_CHOICES.join(", ")}.`
3490
+ );
3491
+ }
3492
+ }
3493
+ function logError(error) {
3494
+ if (error instanceof Error) {
3495
+ console.error(error.message);
3496
+ if (import_node_process3.default.env.CODEX_NATIVE_DEBUG) {
3497
+ console.error(error.stack);
3498
+ }
3499
+ } else {
3500
+ console.error(String(error));
3501
+ }
3502
+ }
3503
+ main().catch((error) => {
3504
+ logError(error);
3505
+ import_node_process3.default.exitCode = 1;
3506
+ });
3507
+ //# sourceMappingURL=cli.cjs.map