@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.
@@ -0,0 +1,1996 @@
1
+ // node_modules/tsup/assets/esm_shims.js
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ var getFilename = () => fileURLToPath(import.meta.url);
5
+ var getDirname = () => path.dirname(getFilename());
6
+ var __dirname = /* @__PURE__ */ getDirname();
7
+ var __filename = /* @__PURE__ */ getFilename();
8
+
9
+ // src/nativeBinding.ts
10
+ import fs from "fs";
11
+ import { createRequire } from "module";
12
+ import path2 from "path";
13
+ import { fileURLToPath as fileURLToPath2 } from "url";
14
+ var CLI_ENTRYPOINT_ENV = "CODEX_NODE_CLI_ENTRYPOINT";
15
+ function ensureCliEntrypointEnv() {
16
+ if (process.env[CLI_ENTRYPOINT_ENV]) {
17
+ return;
18
+ }
19
+ const filename = fileURLToPath2(import.meta.url);
20
+ const dirname3 = path2.dirname(filename);
21
+ const candidates = [
22
+ path2.resolve(dirname3, "cli.cjs"),
23
+ path2.resolve(dirname3, "../cli.cjs"),
24
+ path2.resolve(dirname3, "../dist/cli.cjs")
25
+ ];
26
+ for (const candidate of candidates) {
27
+ if (fs.existsSync(candidate)) {
28
+ process.env[CLI_ENTRYPOINT_ENV] = candidate;
29
+ break;
30
+ }
31
+ }
32
+ }
33
+ ensureCliEntrypointEnv();
34
+ var cachedBinding;
35
+ function getImportMetaUrl() {
36
+ try {
37
+ return Function(
38
+ "return typeof import.meta !== 'undefined' && import.meta.url ? import.meta.url : undefined;"
39
+ )();
40
+ } catch {
41
+ return void 0;
42
+ }
43
+ }
44
+ function resolveBindingEntryPath() {
45
+ if (typeof __dirname === "string") {
46
+ return path2.resolve(__dirname, "..", "index.js");
47
+ }
48
+ const importMetaUrl = getImportMetaUrl();
49
+ if (importMetaUrl) {
50
+ try {
51
+ const filePath = fileURLToPath2(importMetaUrl);
52
+ return path2.resolve(path2.dirname(filePath), "..", "index.js");
53
+ } catch {
54
+ }
55
+ }
56
+ return path2.resolve(process.cwd(), "index.js");
57
+ }
58
+ function resolveRequire() {
59
+ const globalRequire = globalThis.require;
60
+ if (typeof globalRequire === "function") {
61
+ return globalRequire;
62
+ }
63
+ if (typeof __filename === "string") {
64
+ try {
65
+ return createRequire(__filename);
66
+ } catch {
67
+ }
68
+ }
69
+ const importMetaUrl = getImportMetaUrl();
70
+ if (importMetaUrl) {
71
+ try {
72
+ return createRequire(importMetaUrl);
73
+ } catch {
74
+ }
75
+ }
76
+ const fallbackBase = typeof __dirname === "string" ? __dirname : process.cwd();
77
+ const fallbackPath = path2.join(fallbackBase, "noop.js");
78
+ return createRequire(fallbackPath);
79
+ }
80
+ function getNativeBinding() {
81
+ if (cachedBinding !== void 0) {
82
+ return cachedBinding;
83
+ }
84
+ const requireFn = resolveRequire();
85
+ const envPath = process.env.CODEX_NATIVE_BINDING;
86
+ if (envPath && envPath.length > 0) {
87
+ process.env.NAPI_RS_NATIVE_LIBRARY_PATH = envPath;
88
+ }
89
+ const bindingEntryPath = resolveBindingEntryPath();
90
+ try {
91
+ const binding = requireFn(bindingEntryPath);
92
+ binding.ensureTokioRuntime?.();
93
+ cachedBinding = binding;
94
+ return cachedBinding;
95
+ } catch (error) {
96
+ console.warn("Failed to load native NAPI binding:", error);
97
+ cachedBinding = null;
98
+ return cachedBinding;
99
+ }
100
+ }
101
+ function runApplyPatch(patch) {
102
+ if (!patch) {
103
+ throw new Error("apply_patch requires patch contents");
104
+ }
105
+ const binding = getNativeBinding();
106
+ if (!binding) throw new Error("Native binding not available");
107
+ binding.runApplyPatch(patch);
108
+ }
109
+ async function reverieListConversations(codexHomePath, limit, offset) {
110
+ const binding = getNativeBinding();
111
+ if (!binding?.reverieListConversations) throw new Error("Native binding not available or reverie functions not supported");
112
+ return binding.reverieListConversations(codexHomePath, limit, offset);
113
+ }
114
+ async function reverieSearchConversations(codexHomePath, query, limit) {
115
+ const binding = getNativeBinding();
116
+ if (!binding?.reverieSearchConversations) throw new Error("Native binding not available or reverie functions not supported");
117
+ return binding.reverieSearchConversations(codexHomePath, query, limit);
118
+ }
119
+ async function reverieSearchSemantic(codexHomePath, context, options) {
120
+ const binding = getNativeBinding();
121
+ if (!binding?.reverieSearchSemantic) throw new Error("Native binding not available or reverie functions not supported");
122
+ return binding.reverieSearchSemantic(codexHomePath, context, options);
123
+ }
124
+ async function reverieIndexSemantic(codexHomePath, options) {
125
+ const binding = getNativeBinding();
126
+ if (!binding?.reverieIndexSemantic) throw new Error("Native binding not available or reverie functions not supported");
127
+ return binding.reverieIndexSemantic(codexHomePath, options);
128
+ }
129
+ async function reverieGetConversationInsights(conversationPath, query) {
130
+ const binding = getNativeBinding();
131
+ if (!binding?.reverieGetConversationInsights) throw new Error("Native binding not available or reverie functions not supported");
132
+ return binding.reverieGetConversationInsights(conversationPath, query);
133
+ }
134
+ function encodeToToon(value) {
135
+ const binding = getNativeBinding();
136
+ if (!binding?.toonEncode) throw new Error("Native binding not available or toon encoder not supported");
137
+ return binding.toonEncode(value);
138
+ }
139
+ async function fastEmbedInit(options) {
140
+ const binding = getNativeBinding();
141
+ if (!binding?.fastEmbedInit) throw new Error("Native binding not available or FastEmbed functions not supported");
142
+ await binding.fastEmbedInit(options);
143
+ }
144
+ async function fastEmbedEmbed(request) {
145
+ const binding = getNativeBinding();
146
+ if (!binding?.fastEmbedEmbed) throw new Error("Native binding not available or FastEmbed functions not supported");
147
+ return binding.fastEmbedEmbed(request);
148
+ }
149
+ function tokenizerCount(text, options) {
150
+ const binding = getNativeBinding();
151
+ if (!binding?.tokenizerCount) throw new Error("Native binding not available or tokenizer functions not supported");
152
+ return binding.tokenizerCount(text, options);
153
+ }
154
+ function tokenizerEncode(text, options) {
155
+ const binding = getNativeBinding();
156
+ if (!binding?.tokenizerEncode) throw new Error("Native binding not available or tokenizer functions not supported");
157
+ return binding.tokenizerEncode(text, options);
158
+ }
159
+ function tokenizerDecode(tokens, options) {
160
+ const binding = getNativeBinding();
161
+ if (!binding?.tokenizerDecode) throw new Error("Native binding not available or tokenizer functions not supported");
162
+ return binding.tokenizerDecode(tokens, options);
163
+ }
164
+ async function collectRepoDiffSummary(options) {
165
+ const binding = getNativeBinding();
166
+ if (!binding?.collectRepoDiffSummary) {
167
+ throw new Error("Native binding not available or repo diff helpers not supported");
168
+ }
169
+ const cwd = options?.cwd ?? process.cwd();
170
+ const nativeOptions = options && (options.maxFiles !== void 0 || options.diffContextLines !== void 0 || options.diffCharLimit !== void 0) ? {
171
+ maxFiles: options.maxFiles,
172
+ diffContextLines: options.diffContextLines,
173
+ diffCharLimit: options.diffCharLimit
174
+ } : void 0;
175
+ return binding.collectRepoDiffSummary(cwd, options?.baseBranchOverride, nativeOptions);
176
+ }
177
+
178
+ // src/tui.ts
179
+ function startTui(request) {
180
+ const binding = getNativeBinding();
181
+ if (!binding) {
182
+ throw new Error("Native binding is not available");
183
+ }
184
+ if (typeof binding.startTui === "function") {
185
+ const nativeSession = binding.startTui(request);
186
+ return wrapNativeSession(nativeSession);
187
+ }
188
+ if (typeof binding.runTui === "function") {
189
+ return createLegacySession(binding, request);
190
+ }
191
+ throw new Error("Native binding does not expose startTui or runTui");
192
+ }
193
+ async function runTui(request, options = {}) {
194
+ const session = startTui(request);
195
+ const { signal } = options;
196
+ let abortListener;
197
+ try {
198
+ if (signal) {
199
+ if (signal.aborted) {
200
+ session.shutdown();
201
+ } else {
202
+ abortListener = () => session.shutdown();
203
+ signal.addEventListener("abort", abortListener, { once: true });
204
+ }
205
+ }
206
+ return await session.wait();
207
+ } finally {
208
+ if (abortListener && signal) {
209
+ signal.removeEventListener("abort", abortListener);
210
+ }
211
+ }
212
+ }
213
+ function wrapNativeSession(nativeSession) {
214
+ return {
215
+ wait: () => nativeSession.wait(),
216
+ shutdown: () => nativeSession.shutdown(),
217
+ get closed() {
218
+ return nativeSession.closed;
219
+ }
220
+ };
221
+ }
222
+ function createLegacySession(binding, request) {
223
+ if (typeof binding.runTui !== "function") {
224
+ throw new Error("Native binding does not expose runTui");
225
+ }
226
+ let closed = false;
227
+ const promise = binding.runTui(request).then(
228
+ (result) => {
229
+ closed = true;
230
+ return result;
231
+ },
232
+ (error) => {
233
+ closed = true;
234
+ throw error;
235
+ }
236
+ );
237
+ return {
238
+ wait: () => promise,
239
+ shutdown() {
240
+ throw new Error(
241
+ "Programmatic shutdown is not supported by this native binding build. Rebuild the SDK to enable startTui()."
242
+ );
243
+ },
244
+ get closed() {
245
+ return closed;
246
+ }
247
+ };
248
+ }
249
+
250
+ // src/lsp/servers.ts
251
+ import * as fs2 from "fs";
252
+ import * as path3 from "path";
253
+ var MARKERS_NODE = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb", "bun.lock"];
254
+ var MARKERS_PY = ["pyproject.toml", "requirements.txt", "Pipfile", "setup.py", "setup.cfg", "poetry.lock"];
255
+ var MARKERS_RUST = ["Cargo.toml"];
256
+ var DEFAULT_SERVERS = [
257
+ {
258
+ id: "typescript",
259
+ displayName: "TypeScript Language Server",
260
+ command: ["typescript-language-server", "--stdio"],
261
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
262
+ workspace: { type: "markers", include: MARKERS_NODE }
263
+ },
264
+ {
265
+ id: "pyright",
266
+ displayName: "Pyright",
267
+ command: ["pyright-langserver", "--stdio"],
268
+ extensions: [".py", ".pyi"],
269
+ workspace: { type: "markers", include: MARKERS_PY }
270
+ },
271
+ {
272
+ id: "rust-analyzer",
273
+ displayName: "rust-analyzer",
274
+ command: ["rust-analyzer"],
275
+ extensions: [".rs"],
276
+ workspace: { type: "markers", include: MARKERS_RUST }
277
+ }
278
+ ];
279
+ function findServerForFile(filePath) {
280
+ const lower = filePath.toLowerCase();
281
+ return DEFAULT_SERVERS.find((server) => server.extensions.some((ext) => lower.endsWith(ext)));
282
+ }
283
+ function resolveWorkspaceRoot(filePath, locator, fallbackDir) {
284
+ if (!locator) {
285
+ return fallbackDir;
286
+ }
287
+ if (locator.type === "fixed") {
288
+ return locator.path;
289
+ }
290
+ const include = locator.include ?? [];
291
+ const exclude = locator.exclude ?? [];
292
+ let current = fs2.statSync(filePath, { throwIfNoEntry: false })?.isDirectory() ? filePath : path3.dirname(filePath);
293
+ const root = path3.parse(current).root;
294
+ while (true) {
295
+ if (exclude.some((pattern) => fs2.existsSync(path3.join(current, pattern)))) {
296
+ break;
297
+ }
298
+ if (include.some((pattern) => fs2.existsSync(path3.join(current, pattern)))) {
299
+ return current;
300
+ }
301
+ if (current === root) {
302
+ break;
303
+ }
304
+ const parent = path3.dirname(current);
305
+ if (parent === current) {
306
+ break;
307
+ }
308
+ current = parent;
309
+ }
310
+ return fallbackDir;
311
+ }
312
+
313
+ // src/lsp/manager.ts
314
+ import * as path5 from "path";
315
+
316
+ // src/lsp/client.ts
317
+ import { spawn } from "child_process";
318
+ import * as fs3 from "fs/promises";
319
+ import * as path4 from "path";
320
+ import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
321
+ import { EventEmitter } from "events";
322
+ import { createMessageConnection } from "vscode-jsonrpc";
323
+ import { StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/lib/node/main.js";
324
+ import { DiagnosticSeverity } from "vscode-languageserver-types";
325
+ var DEFAULT_TIMEOUT_MS = 3e3;
326
+ var LspClient = class _LspClient {
327
+ constructor(config, root) {
328
+ this.config = config;
329
+ this.root = root;
330
+ }
331
+ connection = null;
332
+ process = null;
333
+ diagnostics = /* @__PURE__ */ new Map();
334
+ versions = /* @__PURE__ */ new Map();
335
+ emitter = new EventEmitter();
336
+ static async start(server, root) {
337
+ const client = new _LspClient(server, root);
338
+ await client.initialize();
339
+ return client;
340
+ }
341
+ async initialize() {
342
+ const [command, ...args] = this.config.command;
343
+ if (!command) {
344
+ throw new Error(`LSP server ${this.config.id} is missing a command executable`);
345
+ }
346
+ try {
347
+ this.process = spawn(command, args, {
348
+ cwd: this.root,
349
+ env: { ...process.env, ...this.config.env },
350
+ stdio: "pipe"
351
+ });
352
+ } catch (error) {
353
+ throw new Error(`Failed to spawn ${this.config.displayName} (${command}): ${String(error)}`);
354
+ }
355
+ const child = this.process;
356
+ child.stderr.on("data", (_chunk) => {
357
+ });
358
+ const reader = new StreamMessageReader(child.stdout);
359
+ const writer = new StreamMessageWriter(child.stdin);
360
+ this.connection = createMessageConnection(reader, writer);
361
+ this.connection.onNotification("textDocument/publishDiagnostics", (payload) => {
362
+ const fsPath = fileURLToPath3(payload.uri);
363
+ this.diagnostics.set(fsPath, payload.diagnostics);
364
+ this.emitter.emit(`diagnostics:${fsPath}`);
365
+ });
366
+ this.connection.onError((err) => {
367
+ console.warn(`[lsp:${this.config.id}] connection error`, err);
368
+ });
369
+ this.connection.listen();
370
+ await this.connection.sendRequest("initialize", {
371
+ rootUri: pathToFileURL(this.root).href,
372
+ processId: process.pid,
373
+ initializationOptions: this.config.initializationOptions ?? {},
374
+ capabilities: {
375
+ textDocument: {
376
+ synchronization: {
377
+ didOpen: true,
378
+ didChange: true
379
+ },
380
+ publishDiagnostics: {
381
+ versionSupport: true
382
+ }
383
+ },
384
+ workspace: {
385
+ workspaceFolders: true
386
+ }
387
+ },
388
+ workspaceFolders: [
389
+ {
390
+ name: path4.basename(this.root),
391
+ uri: pathToFileURL(this.root).href
392
+ }
393
+ ]
394
+ });
395
+ await this.connection.sendNotification("initialized", {});
396
+ }
397
+ async openFile(filePath, waitForDiagnostics) {
398
+ if (!this.connection) return;
399
+ const absolute = path4.resolve(filePath);
400
+ const text = await fs3.readFile(absolute, "utf8");
401
+ const uri = pathToFileURL(absolute).href;
402
+ const languageId = detectLanguageId(absolute);
403
+ const existingVersion = this.versions.get(absolute);
404
+ if (existingVersion === void 0) {
405
+ this.versions.set(absolute, 0);
406
+ await this.connection.sendNotification("textDocument/didOpen", {
407
+ textDocument: {
408
+ uri,
409
+ languageId,
410
+ version: 0,
411
+ text
412
+ }
413
+ });
414
+ } else {
415
+ const next = existingVersion + 1;
416
+ this.versions.set(absolute, next);
417
+ await this.connection.sendNotification("textDocument/didChange", {
418
+ textDocument: {
419
+ uri,
420
+ version: next
421
+ },
422
+ contentChanges: [{ text }]
423
+ });
424
+ }
425
+ if (waitForDiagnostics) {
426
+ await this.waitForDiagnostics(absolute);
427
+ }
428
+ }
429
+ getDiagnostics(filePath) {
430
+ const absolute = path4.resolve(filePath);
431
+ return this.diagnostics.get(absolute) ?? [];
432
+ }
433
+ waitForDiagnostics(filePath, timeoutMs = DEFAULT_TIMEOUT_MS) {
434
+ const absolute = path4.resolve(filePath);
435
+ return new Promise((resolve5) => {
436
+ const timer = setTimeout(resolve5, timeoutMs).unref();
437
+ this.emitter.once(`diagnostics:${absolute}`, () => {
438
+ clearTimeout(timer);
439
+ resolve5();
440
+ });
441
+ });
442
+ }
443
+ async shutdown() {
444
+ try {
445
+ await this.connection?.dispose();
446
+ } catch {
447
+ }
448
+ if (this.process && !this.process.killed) {
449
+ this.process.kill();
450
+ }
451
+ }
452
+ };
453
+ function detectLanguageId(filePath) {
454
+ const ext = path4.extname(filePath).toLowerCase();
455
+ switch (ext) {
456
+ case ".ts":
457
+ case ".mts":
458
+ case ".cts":
459
+ return "typescript";
460
+ case ".tsx":
461
+ return "typescriptreact";
462
+ case ".js":
463
+ case ".mjs":
464
+ case ".cjs":
465
+ return "javascript";
466
+ case ".jsx":
467
+ return "javascriptreact";
468
+ case ".py":
469
+ case ".pyi":
470
+ return "python";
471
+ case ".rs":
472
+ return "rust";
473
+ default:
474
+ return "plaintext";
475
+ }
476
+ }
477
+ function normalizeSeverity(severity) {
478
+ switch (severity) {
479
+ case DiagnosticSeverity.Error:
480
+ return "error";
481
+ case DiagnosticSeverity.Warning:
482
+ return "warning";
483
+ case DiagnosticSeverity.Information:
484
+ return "info";
485
+ case DiagnosticSeverity.Hint:
486
+ return "hint";
487
+ default:
488
+ return "error";
489
+ }
490
+ }
491
+
492
+ // src/lsp/manager.ts
493
+ var LspManager = class {
494
+ constructor(options) {
495
+ this.options = options;
496
+ }
497
+ clients = /* @__PURE__ */ new Map();
498
+ async collectDiagnostics(files) {
499
+ const unique = Array.from(new Set(files.map((file) => path5.resolve(file))));
500
+ const results = [];
501
+ for (const filePath of unique) {
502
+ const server = findServerForFile(filePath);
503
+ if (!server) {
504
+ continue;
505
+ }
506
+ const root = resolveWorkspaceRoot(filePath, server.workspace, this.options.workingDirectory);
507
+ const client = await this.getClient(server, root);
508
+ if (!client) {
509
+ continue;
510
+ }
511
+ try {
512
+ await client.openFile(filePath, this.options.waitForDiagnostics !== false);
513
+ } catch (error) {
514
+ console.warn(`[lsp] failed to open ${filePath}:`, error);
515
+ continue;
516
+ }
517
+ const normalized = client.getDiagnostics(filePath).map((diag) => normalizeDiagnostic(diag)).filter((diag) => diag.message.trim().length > 0);
518
+ if (normalized.length > 0) {
519
+ results.push({ path: filePath, diagnostics: normalized });
520
+ }
521
+ }
522
+ return results;
523
+ }
524
+ async dispose() {
525
+ await Promise.all(
526
+ Array.from(this.clients.values()).map(async (promise) => {
527
+ const client = await promise;
528
+ await client?.shutdown();
529
+ })
530
+ );
531
+ this.clients.clear();
532
+ }
533
+ async getClient(server, root) {
534
+ const key = `${server.id}:${root}`;
535
+ let existing = this.clients.get(key);
536
+ if (!existing) {
537
+ existing = this.createClient(server, root);
538
+ this.clients.set(key, existing);
539
+ }
540
+ const client = await existing;
541
+ if (!client) {
542
+ this.clients.delete(key);
543
+ }
544
+ return client;
545
+ }
546
+ async createClient(server, root) {
547
+ try {
548
+ return await LspClient.start(server, root);
549
+ } catch (error) {
550
+ console.warn(`[lsp] unable to start ${server.displayName}:`, error);
551
+ return null;
552
+ }
553
+ }
554
+ };
555
+ function normalizeDiagnostic(diag) {
556
+ return {
557
+ message: diag.message ?? "",
558
+ severity: normalizeSeverity(diag.severity),
559
+ source: diag.source,
560
+ code: diag.code,
561
+ range: diag.range
562
+ };
563
+ }
564
+
565
+ // src/lsp/format.ts
566
+ import * as path6 from "path";
567
+ var MAX_DIAGNOSTICS_PER_FILE = 5;
568
+ function formatDiagnosticsForTool(diagnostics) {
569
+ return diagnostics.map(({ path: filePath, diagnostics: entries }) => {
570
+ const rel = filePath;
571
+ const lines = entries.slice(0, MAX_DIAGNOSTICS_PER_FILE).map((diag) => {
572
+ const { line, character } = diag.range.start;
573
+ const location = `${line + 1}:${character + 1}`;
574
+ const source = diag.source ? ` \xB7 ${diag.source}` : "";
575
+ return ` - [${diag.severity.toUpperCase()}] ${diag.message} (${location}${source})`;
576
+ });
577
+ const trimmed = entries.length > MAX_DIAGNOSTICS_PER_FILE ? " - \u2026" : "";
578
+ return [`\u2022 ${rel}`, ...lines, trimmed].filter(Boolean).join("\n");
579
+ }).join("\n");
580
+ }
581
+ function formatDiagnosticsForBackgroundEvent(diagnostics, cwd) {
582
+ return diagnostics.map(({ path: filePath, diagnostics: entries }) => {
583
+ const rel = path6.relative(cwd, filePath) || filePath;
584
+ const lines = entries.slice(0, MAX_DIAGNOSTICS_PER_FILE).map((diag) => {
585
+ const { line, character } = diag.range.start;
586
+ const location = `${line + 1}:${character + 1}`;
587
+ const source = diag.source ? ` \xB7 ${diag.source}` : "";
588
+ return ` - [${diag.severity.toUpperCase()}] ${diag.message} (${location}${source})`;
589
+ });
590
+ const trimmed = entries.length > MAX_DIAGNOSTICS_PER_FILE ? " - \u2026" : "";
591
+ return [`\u2022 ${rel}`, ...lines, trimmed].filter(Boolean).join("\n");
592
+ }).join("\n");
593
+ }
594
+ function filterBySeverity(diagnostics, minSeverity = "error") {
595
+ const severityOrder = {
596
+ error: 0,
597
+ warning: 1,
598
+ info: 2,
599
+ hint: 3
600
+ };
601
+ const threshold = severityOrder[minSeverity];
602
+ return diagnostics.map((file) => ({
603
+ ...file,
604
+ diagnostics: file.diagnostics.filter(
605
+ (diag) => severityOrder[diag.severity] <= threshold
606
+ )
607
+ })).filter((file) => file.diagnostics.length > 0);
608
+ }
609
+ function summarizeDiagnostics(diagnostics) {
610
+ let errorCount = 0;
611
+ let warningCount = 0;
612
+ let infoCount = 0;
613
+ let hintCount = 0;
614
+ for (const file of diagnostics) {
615
+ for (const diag of file.diagnostics) {
616
+ switch (diag.severity) {
617
+ case "error":
618
+ errorCount++;
619
+ break;
620
+ case "warning":
621
+ warningCount++;
622
+ break;
623
+ case "info":
624
+ infoCount++;
625
+ break;
626
+ case "hint":
627
+ hintCount++;
628
+ break;
629
+ }
630
+ }
631
+ }
632
+ return {
633
+ fileCount: diagnostics.length,
634
+ errorCount,
635
+ warningCount,
636
+ infoCount,
637
+ hintCount,
638
+ totalCount: errorCount + warningCount + infoCount + hintCount
639
+ };
640
+ }
641
+ function formatDiagnosticsWithSummary(diagnostics, cwd, options = {}) {
642
+ const filtered = options.minSeverity ? filterBySeverity(diagnostics, options.minSeverity) : diagnostics;
643
+ if (filtered.length === 0) {
644
+ return "No diagnostics found.";
645
+ }
646
+ const summary = summarizeDiagnostics(filtered);
647
+ const maxPerFile = options.maxPerFile ?? MAX_DIAGNOSTICS_PER_FILE;
648
+ const header = `LSP Diagnostics Summary: ${summary.errorCount} error${summary.errorCount !== 1 ? "s" : ""}, ${summary.warningCount} warning${summary.warningCount !== 1 ? "s" : ""} across ${summary.fileCount} file${summary.fileCount !== 1 ? "s" : ""}`;
649
+ const details = filtered.map(({ path: filePath, diagnostics: entries }) => {
650
+ const rel = path6.relative(cwd, filePath) || filePath;
651
+ const lines = entries.slice(0, maxPerFile).map((diag) => {
652
+ const { line, character } = diag.range.start;
653
+ const location = `${line + 1}:${character + 1}`;
654
+ const source = diag.source ? ` \xB7 ${diag.source}` : "";
655
+ return ` - [${diag.severity.toUpperCase()}] ${diag.message} (${location}${source})`;
656
+ });
657
+ const trimmed = entries.length > maxPerFile ? ` - \u2026 (${entries.length - maxPerFile} more)` : "";
658
+ return [`\u2022 ${rel}`, ...lines, trimmed].filter(Boolean).join("\n");
659
+ }).join("\n");
660
+ return `${header}
661
+
662
+ ${details}`;
663
+ }
664
+
665
+ // src/lsp/bridge.ts
666
+ import * as path7 from "path";
667
+ var LspDiagnosticsBridge = class {
668
+ constructor(options) {
669
+ this.options = options;
670
+ this.manager = new LspManager(options);
671
+ }
672
+ manager;
673
+ attached = /* @__PURE__ */ new WeakSet();
674
+ attach(thread) {
675
+ if (this.attached.has(thread)) {
676
+ return () => {
677
+ };
678
+ }
679
+ this.attached.add(thread);
680
+ const unsubscribe = thread.onEvent((event) => {
681
+ if (event.type !== "item.completed") {
682
+ return;
683
+ }
684
+ if (event.item.type === "file_change") {
685
+ const targets = event.item.changes.filter((change) => change.kind !== "delete").map((change) => path7.resolve(this.options.workingDirectory, change.path));
686
+ if (targets.length === 0) {
687
+ return;
688
+ }
689
+ void this.processDiagnostics(thread, targets);
690
+ return;
691
+ }
692
+ if (event.item.type === "mcp_tool_call") {
693
+ const targets = extractReadFileTargets(event.item, this.options.workingDirectory);
694
+ if (targets.length === 0) {
695
+ return;
696
+ }
697
+ void this.processDiagnostics(thread, targets);
698
+ }
699
+ });
700
+ return () => {
701
+ this.attached.delete(thread);
702
+ unsubscribe();
703
+ };
704
+ }
705
+ async dispose() {
706
+ await this.manager.dispose();
707
+ }
708
+ async processDiagnostics(thread, files) {
709
+ try {
710
+ const diagnostics = await this.manager.collectDiagnostics(files);
711
+ if (diagnostics.length === 0) {
712
+ return;
713
+ }
714
+ const summary = formatDiagnosticsForBackgroundEvent(
715
+ diagnostics,
716
+ this.options.workingDirectory
717
+ );
718
+ console.log(`
719
+ \u{1F4DF} LSP diagnostics detected:
720
+ ${summary}
721
+ `);
722
+ try {
723
+ await thread.sendBackgroundEvent(`LSP diagnostics detected:
724
+ ${summary}`);
725
+ } catch {
726
+ }
727
+ } catch (error) {
728
+ console.warn("[lsp] failed to collect diagnostics", error);
729
+ }
730
+ }
731
+ };
732
+ function extractReadFileTargets(item, cwd) {
733
+ if (item.type !== "mcp_tool_call") {
734
+ return [];
735
+ }
736
+ const toolName = item.tool?.toLowerCase?.();
737
+ if (toolName !== "read_file" && toolName !== "read_file_v2") {
738
+ return [];
739
+ }
740
+ let args = item.arguments;
741
+ if (typeof args === "string") {
742
+ try {
743
+ args = JSON.parse(args);
744
+ } catch {
745
+ return [];
746
+ }
747
+ }
748
+ if (!args || typeof args !== "object") {
749
+ return [];
750
+ }
751
+ const filePath = args.file_path ?? args.path;
752
+ if (typeof filePath !== "string" || filePath.trim().length === 0) {
753
+ return [];
754
+ }
755
+ const resolved = path7.isAbsolute(filePath) ? filePath : path7.resolve(cwd, filePath);
756
+ return [resolved];
757
+ }
758
+
759
+ // src/lsp/hooks.ts
760
+ function attachLspDiagnostics(thread, options) {
761
+ const bridge = new LspDiagnosticsBridge(options);
762
+ const detach = bridge.attach(thread);
763
+ return () => {
764
+ detach();
765
+ void bridge.dispose().catch((error) => {
766
+ console.warn("Failed to dispose LSP bridge", error);
767
+ });
768
+ };
769
+ }
770
+
771
+ // src/thread.ts
772
+ import * as fs5 from "fs";
773
+ import * as path9 from "path";
774
+
775
+ // src/events/convert.ts
776
+ function convertRustEventToThreadEvent(rustEvent) {
777
+ if (rustEvent?.ThreadStarted) {
778
+ return {
779
+ type: "thread.started",
780
+ thread_id: rustEvent.ThreadStarted.thread_id
781
+ };
782
+ }
783
+ if (rustEvent?.TurnStarted) {
784
+ return { type: "turn.started" };
785
+ }
786
+ if (rustEvent?.TurnCompleted) {
787
+ return {
788
+ type: "turn.completed",
789
+ usage: rustEvent.TurnCompleted.usage
790
+ };
791
+ }
792
+ if (rustEvent?.TurnFailed) {
793
+ return {
794
+ type: "turn.failed",
795
+ error: rustEvent.TurnFailed.error
796
+ };
797
+ }
798
+ if (rustEvent?.ItemStarted) {
799
+ return {
800
+ type: "item.started",
801
+ item: rustEvent.ItemStarted.item
802
+ };
803
+ }
804
+ if (rustEvent?.ItemUpdated) {
805
+ return {
806
+ type: "item.updated",
807
+ item: rustEvent.ItemUpdated.item
808
+ };
809
+ }
810
+ if (rustEvent?.ItemCompleted) {
811
+ return {
812
+ type: "item.completed",
813
+ item: rustEvent.ItemCompleted.item
814
+ };
815
+ }
816
+ if (rustEvent?.Error) {
817
+ return {
818
+ type: "error",
819
+ message: rustEvent.Error.message
820
+ };
821
+ }
822
+ if (rustEvent?.BackgroundEvent) {
823
+ return {
824
+ type: "background_event",
825
+ message: rustEvent.BackgroundEvent.message
826
+ };
827
+ }
828
+ if (rustEvent?.type === "background_event" && typeof rustEvent.message === "string") {
829
+ return {
830
+ type: "background_event",
831
+ message: rustEvent.message
832
+ };
833
+ }
834
+ if (rustEvent?.type === "plan_update_scheduled" && rustEvent.plan) {
835
+ const planData = rustEvent.plan;
836
+ const planItems = planData.plan || [];
837
+ return {
838
+ type: "item.completed",
839
+ item: {
840
+ id: `plan-${Date.now()}`,
841
+ type: "todo_list",
842
+ items: planItems.map((item) => ({
843
+ text: item.step,
844
+ completed: item.status === "completed"
845
+ }))
846
+ }
847
+ };
848
+ }
849
+ if (rustEvent?.type) {
850
+ return rustEvent;
851
+ }
852
+ return rustEvent;
853
+ }
854
+
855
+ // src/outputSchemaFile.ts
856
+ import { promises as fs4 } from "fs";
857
+ import os from "os";
858
+ import path8 from "path";
859
+ function normalizeOutputSchema(schema) {
860
+ if (schema === void 0) {
861
+ return void 0;
862
+ }
863
+ if (isJsonObject(schema) && (schema.type === "json_schema" || schema.type === "json-schema") && isJsonObject(schema.json_schema) && isJsonObject(schema.json_schema.schema)) {
864
+ const strict = typeof schema.json_schema.strict === "boolean" ? schema.json_schema.strict : true;
865
+ return normalizeJsonSchemaObject(schema.json_schema.schema, strict);
866
+ }
867
+ if (isJsonObject(schema) && isJsonObject(schema.schema)) {
868
+ const strict = typeof schema.strict === "boolean" ? schema.strict : true;
869
+ return normalizeJsonSchemaObject(schema.schema, strict);
870
+ }
871
+ if (!isJsonObject(schema)) {
872
+ throw new Error(
873
+ "outputSchema must be a plain JSON object or an OpenAI-style json_schema wrapper"
874
+ );
875
+ }
876
+ return normalizeJsonSchemaObject(schema, true);
877
+ }
878
+ async function createOutputSchemaFile(schema) {
879
+ const normalizedSchema = normalizeOutputSchema(schema);
880
+ if (!normalizedSchema) {
881
+ return { cleanup: async () => {
882
+ } };
883
+ }
884
+ const schemaDir = await fs4.mkdtemp(path8.join(os.tmpdir(), "codex-output-schema-"));
885
+ const schemaPath = path8.join(schemaDir, "schema.json");
886
+ const cleanup = async () => {
887
+ try {
888
+ await fs4.rm(schemaDir, { recursive: true, force: true });
889
+ } catch {
890
+ }
891
+ };
892
+ try {
893
+ await fs4.writeFile(schemaPath, JSON.stringify(normalizedSchema), "utf8");
894
+ return { schemaPath, cleanup };
895
+ } catch (error) {
896
+ await cleanup();
897
+ throw error;
898
+ }
899
+ }
900
+ function isJsonObject(value) {
901
+ return typeof value === "object" && value !== null && !Array.isArray(value);
902
+ }
903
+ function normalizeJsonSchemaObject(schema, strict) {
904
+ const record = { ...schema };
905
+ const hasExplicitAdditional = typeof record.additionalProperties === "boolean" || typeof record.additionalProperties === "object";
906
+ const additionalProperties = hasExplicitAdditional ? record.additionalProperties : strict ? false : record.additionalProperties;
907
+ return {
908
+ ...record,
909
+ ...hasExplicitAdditional || strict ? { additionalProperties } : {}
910
+ };
911
+ }
912
+
913
+ // src/thread.ts
914
+ var UNTRUSTED_DIRECTORY_ERROR = "Not inside a trusted directory and --skip-git-repo-check was not specified.";
915
+ function findGitRoot(startDir) {
916
+ let current = path9.resolve(startDir);
917
+ while (true) {
918
+ const gitPath = path9.join(current, ".git");
919
+ if (fs5.existsSync(gitPath)) {
920
+ try {
921
+ const stats = fs5.statSync(gitPath);
922
+ if (stats.isDirectory() || stats.isFile()) {
923
+ return current;
924
+ }
925
+ } catch {
926
+ }
927
+ }
928
+ const parent = path9.dirname(current);
929
+ if (parent === current) {
930
+ break;
931
+ }
932
+ current = parent;
933
+ }
934
+ return null;
935
+ }
936
+ function assertTrustedDirectory(workingDirectory) {
937
+ const directory = workingDirectory ? path9.resolve(workingDirectory) : process.cwd();
938
+ if (!findGitRoot(directory)) {
939
+ throw new Error(UNTRUSTED_DIRECTORY_ERROR);
940
+ }
941
+ }
942
+ var Thread = class _Thread {
943
+ _exec;
944
+ _options;
945
+ _id;
946
+ _threadOptions;
947
+ _eventListeners = [];
948
+ _approvalHandler = null;
949
+ /** Returns the ID of the thread. Populated after the first turn starts. */
950
+ get id() {
951
+ return this._id;
952
+ }
953
+ /**
954
+ * Register an event listener for thread events.
955
+ * @param listener Callback function that receives ThreadEvent objects
956
+ * @returns Unsubscribe function to remove the listener
957
+ */
958
+ onEvent(listener) {
959
+ this._eventListeners.push(listener);
960
+ return () => {
961
+ const index = this._eventListeners.indexOf(listener);
962
+ if (index !== -1) {
963
+ this._eventListeners.splice(index, 1);
964
+ }
965
+ };
966
+ }
967
+ /**
968
+ * Remove an event listener.
969
+ * @param listener The listener function to remove
970
+ */
971
+ offEvent(listener) {
972
+ const index = this._eventListeners.indexOf(listener);
973
+ if (index !== -1) {
974
+ this._eventListeners.splice(index, 1);
975
+ }
976
+ }
977
+ /**
978
+ * Register a callback to handle approval requests from the agent.
979
+ * The handler should return true to approve the action, false to deny it.
980
+ *
981
+ * @param handler Callback function that receives ApprovalRequest and returns approval decision
982
+ * @example
983
+ * ```typescript
984
+ * thread.onApprovalRequest(async (request) => {
985
+ * console.log(`Approval requested for ${request.type}`);
986
+ * return true; // Auto-approve
987
+ * });
988
+ * ```
989
+ */
990
+ onApprovalRequest(handler) {
991
+ this._approvalHandler = handler;
992
+ const binding = getNativeBinding();
993
+ if (binding && typeof binding.registerApprovalCallback === "function") {
994
+ binding.registerApprovalCallback(handler);
995
+ }
996
+ }
997
+ /**
998
+ * Emit a background notification while the agent is running the current turn.
999
+ * The message is surfaced to event subscribers but does not modify the user input queue.
1000
+ *
1001
+ * @throws Error if the thread has not been started yet.
1002
+ */
1003
+ async sendBackgroundEvent(message) {
1004
+ const trimmed = message?.toString();
1005
+ if (!trimmed || trimmed.trim().length === 0) {
1006
+ throw new Error("Background event message must be a non-empty string");
1007
+ }
1008
+ if (!this._id) {
1009
+ throw new Error("Cannot emit a background event before the thread has started");
1010
+ }
1011
+ const binding = getNativeBinding();
1012
+ if (!binding || typeof binding.emitBackgroundEvent !== "function") {
1013
+ throw new Error("emitBackgroundEvent is not available in this build");
1014
+ }
1015
+ await binding.emitBackgroundEvent({ threadId: this._id, message: trimmed });
1016
+ }
1017
+ /**
1018
+ * Programmatically update the agent's plan/todo list.
1019
+ * The plan will be applied at the start of the next turn.
1020
+ *
1021
+ * @param args The plan update arguments
1022
+ * @throws Error if no thread ID is available
1023
+ */
1024
+ updatePlan(args) {
1025
+ if (!this._id) {
1026
+ throw new Error("Cannot update plan: no active thread");
1027
+ }
1028
+ const binding = getNativeBinding();
1029
+ if (!binding || typeof binding.emitPlanUpdate !== "function") {
1030
+ throw new Error("emitPlanUpdate is not available in this build");
1031
+ }
1032
+ binding.emitPlanUpdate({
1033
+ threadId: this._id,
1034
+ explanation: args.explanation,
1035
+ plan: args.plan
1036
+ });
1037
+ }
1038
+ /**
1039
+ * Modify the agent's plan/todo list with granular operations.
1040
+ * Changes will be applied at the start of the next turn.
1041
+ *
1042
+ * @param operations Array of operations to perform on the plan
1043
+ * @throws Error if no thread ID is available
1044
+ */
1045
+ modifyPlan(operations) {
1046
+ if (!this._id) {
1047
+ throw new Error("Cannot modify plan: no active thread");
1048
+ }
1049
+ const binding = getNativeBinding();
1050
+ if (!binding || typeof binding.modifyPlan !== "function") {
1051
+ throw new Error("modifyPlan is not available in this build");
1052
+ }
1053
+ binding.modifyPlan({
1054
+ threadId: this._id,
1055
+ operations
1056
+ });
1057
+ }
1058
+ /**
1059
+ * Add a new todo item to the agent's plan.
1060
+ *
1061
+ * @param step The todo step description
1062
+ * @param status The initial status (defaults to "pending")
1063
+ */
1064
+ addTodo(step, status = "pending") {
1065
+ this.modifyPlan([{ type: "add", item: { step, status } }]);
1066
+ }
1067
+ /**
1068
+ * Update an existing todo item.
1069
+ *
1070
+ * @param index The index of the todo item to update
1071
+ * @param updates The updates to apply
1072
+ */
1073
+ updateTodo(index, updates) {
1074
+ this.modifyPlan([{ type: "update", index, updates }]);
1075
+ }
1076
+ /**
1077
+ * Remove a todo item from the plan.
1078
+ *
1079
+ * @param index The index of the todo item to remove
1080
+ */
1081
+ removeTodo(index) {
1082
+ this.modifyPlan([{ type: "remove", index }]);
1083
+ }
1084
+ /**
1085
+ * Reorder the todo items in the plan.
1086
+ *
1087
+ * @param newOrder Array of indices representing the new order
1088
+ */
1089
+ reorderTodos(newOrder) {
1090
+ this.modifyPlan([{ type: "reorder", newOrder }]);
1091
+ }
1092
+ /** Compacts the conversation history for this thread using Codex's builtin compaction. */
1093
+ async compact() {
1094
+ const skipGitRepoCheck = this._threadOptions?.skipGitRepoCheck ?? (typeof process !== "undefined" && process.env && process.env.CODEX_TEST_SKIP_GIT_REPO_CHECK === "1");
1095
+ if (!skipGitRepoCheck) {
1096
+ assertTrustedDirectory(this._threadOptions?.workingDirectory);
1097
+ }
1098
+ const events = await this._exec.compact({
1099
+ input: "compact",
1100
+ threadId: this._id,
1101
+ baseUrl: this._options.baseUrl,
1102
+ apiKey: this._options.apiKey,
1103
+ model: this._threadOptions?.model ?? this._options.defaultModel,
1104
+ sandboxMode: this._threadOptions?.sandboxMode,
1105
+ approvalMode: this._threadOptions?.approvalMode,
1106
+ workspaceWriteOptions: this._threadOptions?.workspaceWriteOptions,
1107
+ workingDirectory: this._threadOptions?.workingDirectory,
1108
+ skipGitRepoCheck,
1109
+ modelProvider: this._options.modelProvider
1110
+ });
1111
+ if (!Array.isArray(events)) {
1112
+ throw new Error("Compact did not return event list");
1113
+ }
1114
+ }
1115
+ /**
1116
+ * Fork this thread at the specified user message, returning a new thread that starts
1117
+ * from the conversation history prior to that message.
1118
+ *
1119
+ * @param options Fork configuration including which user message to branch before and optional thread overrides.
1120
+ */
1121
+ async fork(options) {
1122
+ if (!this._id) {
1123
+ throw new Error("Cannot fork: no active thread");
1124
+ }
1125
+ const nthUserMessage = options?.nthUserMessage;
1126
+ if (typeof nthUserMessage !== "number" || !Number.isInteger(nthUserMessage) || nthUserMessage < 0) {
1127
+ throw new Error("nthUserMessage must be a non-negative integer");
1128
+ }
1129
+ const overrides = options.threadOptions ?? {};
1130
+ const nextThreadOptions = {
1131
+ ...this._threadOptions,
1132
+ ...overrides
1133
+ };
1134
+ const skipGitRepoCheck = nextThreadOptions.skipGitRepoCheck ?? (typeof process !== "undefined" && process.env && process.env.CODEX_TEST_SKIP_GIT_REPO_CHECK === "1");
1135
+ nextThreadOptions.skipGitRepoCheck = skipGitRepoCheck;
1136
+ if (!skipGitRepoCheck) {
1137
+ assertTrustedDirectory(nextThreadOptions.workingDirectory);
1138
+ }
1139
+ const forkArgs = {
1140
+ threadId: this._id,
1141
+ nthUserMessage,
1142
+ baseUrl: this._options.baseUrl,
1143
+ apiKey: this._options.apiKey,
1144
+ model: nextThreadOptions.model ?? this._options.defaultModel,
1145
+ oss: nextThreadOptions.oss,
1146
+ sandboxMode: nextThreadOptions.sandboxMode,
1147
+ approvalMode: nextThreadOptions.approvalMode,
1148
+ workspaceWriteOptions: nextThreadOptions.workspaceWriteOptions,
1149
+ workingDirectory: nextThreadOptions.workingDirectory,
1150
+ skipGitRepoCheck,
1151
+ fullAuto: nextThreadOptions.fullAuto,
1152
+ modelProvider: this._options.modelProvider
1153
+ };
1154
+ const result = await this._exec.fork(forkArgs);
1155
+ return new _Thread(
1156
+ this._exec,
1157
+ this._options,
1158
+ nextThreadOptions,
1159
+ result.threadId
1160
+ );
1161
+ }
1162
+ /* @internal */
1163
+ constructor(exec, options, threadOptions, id = null) {
1164
+ this._exec = exec;
1165
+ this._options = options;
1166
+ this._id = id;
1167
+ this._threadOptions = threadOptions;
1168
+ }
1169
+ /** Provides the input to the agent and streams events as they are produced during the turn. */
1170
+ async runStreamed(input, turnOptions = {}) {
1171
+ return { events: this.runStreamedInternal(input, turnOptions, false) };
1172
+ }
1173
+ async *runStreamedInternal(input, turnOptions = {}, emitRawEvents = true) {
1174
+ const normalizedSchema = normalizeOutputSchema(turnOptions.outputSchema);
1175
+ const needsSchemaFile = this._exec.requiresOutputSchemaFile();
1176
+ const schemaFile = needsSchemaFile ? await createOutputSchemaFile(normalizedSchema) : { schemaPath: void 0, cleanup: async () => {
1177
+ } };
1178
+ const options = this._threadOptions;
1179
+ const { prompt, images } = normalizeInput(input);
1180
+ const skipGitRepoCheck = options?.skipGitRepoCheck ?? (typeof process !== "undefined" && process.env && process.env.CODEX_TEST_SKIP_GIT_REPO_CHECK === "1");
1181
+ if (!skipGitRepoCheck) {
1182
+ assertTrustedDirectory(options?.workingDirectory);
1183
+ }
1184
+ const generator = this._exec.run({
1185
+ input: prompt,
1186
+ baseUrl: this._options.baseUrl,
1187
+ apiKey: this._options.apiKey,
1188
+ threadId: this._id,
1189
+ images,
1190
+ model: options?.model,
1191
+ oss: turnOptions?.oss ?? options?.oss,
1192
+ sandboxMode: options?.sandboxMode,
1193
+ approvalMode: options?.approvalMode,
1194
+ workspaceWriteOptions: options?.workspaceWriteOptions,
1195
+ workingDirectory: options?.workingDirectory,
1196
+ skipGitRepoCheck,
1197
+ outputSchemaFile: schemaFile.schemaPath,
1198
+ outputSchema: normalizedSchema,
1199
+ fullAuto: options?.fullAuto
1200
+ });
1201
+ try {
1202
+ for await (const item of generator) {
1203
+ let parsed;
1204
+ try {
1205
+ parsed = JSON.parse(item);
1206
+ } catch (error) {
1207
+ throw new Error(`Failed to parse item: ${item}. Parse error: ${error}`);
1208
+ }
1209
+ if (parsed === null) {
1210
+ continue;
1211
+ }
1212
+ if (emitRawEvents) {
1213
+ yield { type: "raw_event", raw: parsed };
1214
+ }
1215
+ const threadEvent = convertRustEventToThreadEvent(parsed);
1216
+ if (threadEvent.type === "thread.started") {
1217
+ this._id = threadEvent.thread_id;
1218
+ }
1219
+ for (const listener of this._eventListeners) {
1220
+ try {
1221
+ listener(threadEvent);
1222
+ } catch (error) {
1223
+ console.warn("Thread event listener threw error:", error);
1224
+ }
1225
+ }
1226
+ yield threadEvent;
1227
+ }
1228
+ } finally {
1229
+ await schemaFile.cleanup();
1230
+ }
1231
+ }
1232
+ /** Provides the input to the agent and returns the completed turn. */
1233
+ async run(input, turnOptions = {}) {
1234
+ const generator = this.runStreamedInternal(input, turnOptions, true);
1235
+ const items = [];
1236
+ let finalResponse = "";
1237
+ let usage = null;
1238
+ let turnFailure = null;
1239
+ for await (const event of generator) {
1240
+ if (event.type === "item.completed") {
1241
+ if (event.item.type === "agent_message") {
1242
+ finalResponse = event.item.text;
1243
+ }
1244
+ items.push(event.item);
1245
+ } else if (event.type === "turn.completed") {
1246
+ usage = event.usage;
1247
+ } else if (event.type === "turn.failed") {
1248
+ turnFailure = event.error;
1249
+ break;
1250
+ }
1251
+ }
1252
+ if (turnFailure) {
1253
+ throw new Error(turnFailure.message);
1254
+ }
1255
+ return { items, finalResponse, usage };
1256
+ }
1257
+ buildTuiRequest(overrides = {}) {
1258
+ const skipGitRepoCheck = this._threadOptions?.skipGitRepoCheck ?? (typeof process !== "undefined" && process.env && process.env.CODEX_TEST_SKIP_GIT_REPO_CHECK === "1");
1259
+ if (!skipGitRepoCheck) {
1260
+ assertTrustedDirectory(this._threadOptions?.workingDirectory);
1261
+ }
1262
+ const request = { ...overrides };
1263
+ const assignIfUndefined = (key, value) => {
1264
+ if (request[key] === void 0 && value !== void 0) {
1265
+ request[key] = value;
1266
+ }
1267
+ };
1268
+ assignIfUndefined("model", this._threadOptions?.model ?? this._options.defaultModel);
1269
+ assignIfUndefined("oss", this._threadOptions?.oss);
1270
+ assignIfUndefined("sandboxMode", this._threadOptions?.sandboxMode);
1271
+ assignIfUndefined("approvalMode", this._threadOptions?.approvalMode);
1272
+ assignIfUndefined("fullAuto", this._threadOptions?.fullAuto);
1273
+ assignIfUndefined("workingDirectory", this._threadOptions?.workingDirectory);
1274
+ assignIfUndefined("baseUrl", this._options.baseUrl);
1275
+ assignIfUndefined("apiKey", this._options.apiKey);
1276
+ if (request.resumeSessionId === void 0 && request.resumePicker !== true && request.resumeLast !== true && this._id) {
1277
+ request.resumeSessionId = this._id;
1278
+ }
1279
+ return request;
1280
+ }
1281
+ /**
1282
+ * Launches the interactive Codex TUI (Terminal User Interface) for this thread and returns a session handle.
1283
+ *
1284
+ * The handle allows advanced workflows where the TUI can be started and stopped programmatically,
1285
+ * while preserving the underlying conversation state.
1286
+ */
1287
+ launchTui(overrides = {}) {
1288
+ const request = this.buildTuiRequest(overrides);
1289
+ const detachLsp = this.attachDefaultLspBridge(request);
1290
+ const session = startTui(request);
1291
+ return this.wrapTuiSession(session, detachLsp);
1292
+ }
1293
+ /**
1294
+ * Launches the interactive Codex TUI (Terminal User Interface) for this thread.
1295
+ *
1296
+ * This method enables seamless transition from programmatic agent interaction to
1297
+ * interactive terminal chat within the same session. The TUI takes over the terminal
1298
+ * and allows you to continue the conversation interactively.
1299
+ *
1300
+ * @param overrides - Optional configuration to override thread defaults. Supports all TUI options
1301
+ * including prompt, sandbox mode, approval mode, and resume options.
1302
+ * @param options - Optional run options including an AbortSignal to request shutdown.
1303
+ * @returns A Promise that resolves to TUI exit information including:
1304
+ * - tokenUsage: Token consumption statistics
1305
+ * - conversationId: Session ID for resuming later
1306
+ * - updateAction: Optional suggested update command
1307
+ * @throws {Error} If not in a trusted git repository (unless skipGitRepoCheck is set)
1308
+ * @throws {Error} If the terminal is not interactive (TTY required)
1309
+ */
1310
+ async tui(overrides = {}, options = {}) {
1311
+ const request = this.buildTuiRequest(overrides);
1312
+ const detachLsp = this.attachDefaultLspBridge(request);
1313
+ try {
1314
+ return await runTui(request, options);
1315
+ } finally {
1316
+ detachLsp();
1317
+ }
1318
+ }
1319
+ wrapTuiSession(session, cleanup) {
1320
+ let released = false;
1321
+ const release = () => {
1322
+ if (released) {
1323
+ return;
1324
+ }
1325
+ released = true;
1326
+ cleanup();
1327
+ };
1328
+ return {
1329
+ wait: async () => {
1330
+ try {
1331
+ return await session.wait();
1332
+ } finally {
1333
+ release();
1334
+ }
1335
+ },
1336
+ shutdown: () => {
1337
+ release();
1338
+ session.shutdown();
1339
+ },
1340
+ get closed() {
1341
+ return session.closed;
1342
+ }
1343
+ };
1344
+ }
1345
+ attachDefaultLspBridge(request) {
1346
+ const workingDirectory = request.workingDirectory ?? this._threadOptions?.workingDirectory ?? (typeof process !== "undefined" && typeof process.cwd === "function" ? process.cwd() : ".");
1347
+ return attachLspDiagnostics(this, {
1348
+ workingDirectory,
1349
+ waitForDiagnostics: true
1350
+ });
1351
+ }
1352
+ };
1353
+ function normalizeInput(input) {
1354
+ if (typeof input === "string") {
1355
+ return { prompt: input, images: [] };
1356
+ }
1357
+ const promptParts = [];
1358
+ const images = [];
1359
+ for (const item of input) {
1360
+ if (item.type === "text") {
1361
+ promptParts.push(item.text);
1362
+ } else if (item.type === "local_image") {
1363
+ images.push(item.path);
1364
+ }
1365
+ }
1366
+ return { prompt: promptParts.join("\n\n"), images };
1367
+ }
1368
+
1369
+ // src/exec.ts
1370
+ var CodexExec = class {
1371
+ native;
1372
+ constructor() {
1373
+ const nativeBinding = getNativeBinding();
1374
+ if (!nativeBinding) {
1375
+ throw new Error(
1376
+ "Native NAPI binding not available. Make sure @openai/codex-native is properly installed and built."
1377
+ );
1378
+ }
1379
+ this.native = nativeBinding;
1380
+ }
1381
+ requiresOutputSchemaFile() {
1382
+ return false;
1383
+ }
1384
+ async *run(args) {
1385
+ const binding = this.native;
1386
+ const queue = new AsyncQueue();
1387
+ validateModel(args.model, args.oss === true);
1388
+ const request = {
1389
+ prompt: args.input,
1390
+ threadId: args.threadId ?? void 0,
1391
+ images: args.images && args.images.length > 0 ? args.images : void 0,
1392
+ model: args.model,
1393
+ oss: args.oss,
1394
+ approvalMode: args.approvalMode,
1395
+ workspaceWriteOptions: args.workspaceWriteOptions,
1396
+ sandboxMode: args.sandboxMode,
1397
+ workingDirectory: args.workingDirectory,
1398
+ skipGitRepoCheck: args.skipGitRepoCheck,
1399
+ outputSchema: args.outputSchema,
1400
+ baseUrl: args.baseUrl,
1401
+ apiKey: args.apiKey,
1402
+ modelProvider: args.modelProvider,
1403
+ fullAuto: args.fullAuto,
1404
+ reviewMode: args.review ? true : void 0,
1405
+ reviewHint: args.review?.userFacingHint
1406
+ };
1407
+ let runPromise = Promise.resolve();
1408
+ try {
1409
+ runPromise = binding.runThreadStream(request, (err, eventJson) => {
1410
+ if (err) {
1411
+ queue.fail(err);
1412
+ return;
1413
+ }
1414
+ try {
1415
+ queue.push(eventJson ?? "null");
1416
+ } catch (error) {
1417
+ queue.fail(error);
1418
+ }
1419
+ }).then(
1420
+ () => {
1421
+ queue.end();
1422
+ },
1423
+ (error) => {
1424
+ queue.fail(error);
1425
+ }
1426
+ );
1427
+ } catch (error) {
1428
+ queue.fail(error);
1429
+ throw error;
1430
+ }
1431
+ let loopError;
1432
+ try {
1433
+ for await (const value of queue) {
1434
+ yield value;
1435
+ }
1436
+ await runPromise;
1437
+ } catch (error) {
1438
+ loopError = error;
1439
+ throw error;
1440
+ } finally {
1441
+ queue.end();
1442
+ if (loopError) {
1443
+ await runPromise.catch(() => {
1444
+ });
1445
+ }
1446
+ }
1447
+ }
1448
+ async compact(args) {
1449
+ validateModel(args.model, args.oss === true);
1450
+ const request = {
1451
+ prompt: args.input,
1452
+ threadId: args.threadId ?? void 0,
1453
+ images: args.images && args.images.length > 0 ? args.images : void 0,
1454
+ model: args.model,
1455
+ modelProvider: args.modelProvider,
1456
+ oss: args.oss,
1457
+ sandboxMode: args.sandboxMode,
1458
+ approvalMode: args.approvalMode,
1459
+ workspaceWriteOptions: args.workspaceWriteOptions,
1460
+ workingDirectory: args.workingDirectory,
1461
+ skipGitRepoCheck: args.skipGitRepoCheck,
1462
+ outputSchema: args.outputSchema,
1463
+ baseUrl: args.baseUrl,
1464
+ apiKey: args.apiKey,
1465
+ fullAuto: args.fullAuto,
1466
+ reviewMode: args.review ? true : void 0,
1467
+ reviewHint: args.review?.userFacingHint
1468
+ };
1469
+ return this.native.compactThread(request);
1470
+ }
1471
+ async fork(args) {
1472
+ if (!args.threadId) {
1473
+ throw new Error("threadId is required to fork a conversation");
1474
+ }
1475
+ const request = {
1476
+ threadId: args.threadId,
1477
+ nthUserMessage: args.nthUserMessage,
1478
+ model: args.model,
1479
+ oss: args.oss,
1480
+ sandboxMode: args.sandboxMode,
1481
+ approvalMode: args.approvalMode,
1482
+ workspaceWriteOptions: args.workspaceWriteOptions,
1483
+ workingDirectory: args.workingDirectory,
1484
+ skipGitRepoCheck: args.skipGitRepoCheck,
1485
+ baseUrl: args.baseUrl,
1486
+ apiKey: args.apiKey,
1487
+ modelProvider: args.modelProvider,
1488
+ linuxSandboxPath: args.linuxSandboxPath,
1489
+ fullAuto: args.fullAuto
1490
+ };
1491
+ return this.native.forkThread(request);
1492
+ }
1493
+ async listConversations(request) {
1494
+ return this.native.listConversations(request);
1495
+ }
1496
+ async deleteConversation(request) {
1497
+ return this.native.deleteConversation(request);
1498
+ }
1499
+ async resumeConversationFromRollout(request) {
1500
+ return this.native.resumeConversationFromRollout(request);
1501
+ }
1502
+ };
1503
+ var AsyncQueue = class {
1504
+ buffer = [];
1505
+ waiters = [];
1506
+ ended = false;
1507
+ error;
1508
+ push(value) {
1509
+ if (this.ended) return;
1510
+ if (this.waiters.length > 0) {
1511
+ const waiter = this.waiters.shift();
1512
+ waiter.resolve({ value, done: false });
1513
+ return;
1514
+ }
1515
+ this.buffer.push(value);
1516
+ }
1517
+ end() {
1518
+ if (this.ended) return;
1519
+ this.ended = true;
1520
+ const waiters = this.waiters;
1521
+ this.waiters = [];
1522
+ for (const waiter of waiters) {
1523
+ waiter.resolve({ value: void 0, done: true });
1524
+ }
1525
+ }
1526
+ fail(error) {
1527
+ if (this.ended) return;
1528
+ this.error = error;
1529
+ this.ended = true;
1530
+ const waiters = this.waiters;
1531
+ this.waiters = [];
1532
+ for (const waiter of waiters) {
1533
+ waiter.reject(error);
1534
+ }
1535
+ }
1536
+ async next() {
1537
+ if (this.buffer.length > 0) {
1538
+ const value = this.buffer.shift();
1539
+ return { value, done: false };
1540
+ }
1541
+ if (this.error) {
1542
+ return Promise.reject(this.error);
1543
+ }
1544
+ if (this.ended) {
1545
+ return { value: void 0, done: true };
1546
+ }
1547
+ return new Promise((resolve5, reject) => {
1548
+ this.waiters.push({ resolve: resolve5, reject });
1549
+ });
1550
+ }
1551
+ [Symbol.asyncIterator]() {
1552
+ return this;
1553
+ }
1554
+ };
1555
+ function validateModel(model, oss) {
1556
+ if (!model) return;
1557
+ const trimmed = String(model).trim();
1558
+ if (oss) {
1559
+ if (!trimmed.startsWith("gpt-oss:")) {
1560
+ throw new Error(
1561
+ `Invalid model "${trimmed}" for OSS mode. Use models prefixed with "gpt-oss:", e.g. "gpt-oss:20b".`
1562
+ );
1563
+ }
1564
+ return;
1565
+ }
1566
+ const allowed = /* @__PURE__ */ new Set([
1567
+ // GPT models
1568
+ "gpt-5",
1569
+ "gpt-5-codex",
1570
+ "gpt-5-codex-mini",
1571
+ "gpt-5.1",
1572
+ "gpt-5.1-codex",
1573
+ "gpt-5.1-codex-mini",
1574
+ // Claude models
1575
+ "claude-sonnet-4-5-20250929",
1576
+ "claude-sonnet-4-20250514",
1577
+ "claude-opus-4-20250514"
1578
+ ]);
1579
+ if (!allowed.has(trimmed) && !trimmed.startsWith("claude-") && !trimmed.startsWith("gpt-")) {
1580
+ throw new Error(
1581
+ `Invalid model "${trimmed}". Supported models: ${Array.from(allowed).map((m) => `"${m}"`).join(", ")}, or any model starting with "claude-" or "gpt-".`
1582
+ );
1583
+ }
1584
+ }
1585
+
1586
+ // src/reviewOptions.ts
1587
+ function buildReviewPrompt(target) {
1588
+ switch (target.type) {
1589
+ case "current_changes":
1590
+ return {
1591
+ prompt: "Review the current code changes (staged, unstaged, and untracked files) and provide prioritized findings.",
1592
+ hint: "current changes"
1593
+ };
1594
+ case "branch": {
1595
+ const branch = target.baseBranch;
1596
+ 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.`;
1597
+ return {
1598
+ prompt,
1599
+ hint: `changes against '${branch}'`
1600
+ };
1601
+ }
1602
+ case "commit": {
1603
+ const shortSha = target.sha.slice(0, 7);
1604
+ const subject = target.subject ?? target.sha;
1605
+ return {
1606
+ prompt: `Review the code changes introduced by commit ${target.sha} ("${subject}"). Provide prioritized, actionable findings.`,
1607
+ hint: `commit ${shortSha}`
1608
+ };
1609
+ }
1610
+ case "custom": {
1611
+ const hint = target.hint ?? "custom review";
1612
+ return {
1613
+ prompt: target.prompt,
1614
+ hint
1615
+ };
1616
+ }
1617
+ default: {
1618
+ const exhaustive = target;
1619
+ throw new Error(`Unsupported review target: ${String(exhaustive)}`);
1620
+ }
1621
+ }
1622
+ }
1623
+
1624
+ // src/codex.ts
1625
+ var Codex = class {
1626
+ exec;
1627
+ options;
1628
+ nativeBinding;
1629
+ lspForTools;
1630
+ constructor(options = {}) {
1631
+ const predefinedTools = options.tools ? [...options.tools] : [];
1632
+ const preserveRegisteredTools = options.preserveRegisteredTools === true;
1633
+ this.nativeBinding = getNativeBinding();
1634
+ this.options = { ...options, tools: [] };
1635
+ if (this.nativeBinding) {
1636
+ if (!preserveRegisteredTools && typeof this.nativeBinding.clearRegisteredTools === "function") {
1637
+ this.nativeBinding.clearRegisteredTools();
1638
+ }
1639
+ for (const tool of predefinedTools) {
1640
+ this.registerTool(tool);
1641
+ }
1642
+ }
1643
+ this.lspForTools = this.createLspManagerForTools();
1644
+ if (this.lspForTools && this.nativeBinding) {
1645
+ this.registerDefaultReadFileInterceptor();
1646
+ }
1647
+ this.exec = new CodexExec();
1648
+ }
1649
+ /**
1650
+ * Register a tool for Codex. When `tool.name` matches a built-in Codex tool,
1651
+ * the native implementation is replaced for this Codex instance.
1652
+ */
1653
+ registerTool(tool) {
1654
+ if (!this.nativeBinding) {
1655
+ throw new Error("Native tool registration requires the NAPI binding");
1656
+ }
1657
+ if (typeof this.nativeBinding.registerTool !== "function") {
1658
+ console.warn("registerTool is not available in this build - tools feature may be incomplete");
1659
+ return;
1660
+ }
1661
+ const { handler, ...info } = tool;
1662
+ this.nativeBinding.registerTool(info, handler);
1663
+ if (!this.options.tools) {
1664
+ this.options.tools = [];
1665
+ }
1666
+ this.options.tools.push(tool);
1667
+ }
1668
+ /**
1669
+ * Register a tool interceptor for Codex. Interceptors can modify tool invocations
1670
+ * and results, and can call the built-in implementation.
1671
+ */
1672
+ registerToolInterceptor(toolName, handler) {
1673
+ if (!this.nativeBinding) {
1674
+ throw new Error("Native tool interceptor registration requires the NAPI binding");
1675
+ }
1676
+ if (typeof this.nativeBinding.registerToolInterceptor !== "function" || typeof this.nativeBinding.callToolBuiltin !== "function") {
1677
+ console.warn("registerToolInterceptor is not available in this build - interceptor feature may be incomplete");
1678
+ return;
1679
+ }
1680
+ this.nativeBinding.registerToolInterceptor(toolName, async (...args) => {
1681
+ const context = args.length === 1 ? args[0] : args[1];
1682
+ if (!context || typeof context !== "object") {
1683
+ throw new Error("Native interceptor callback did not receive a context object");
1684
+ }
1685
+ const { invocation, token } = context;
1686
+ const callBuiltin = (override) => this.nativeBinding.callToolBuiltin(token, override ?? invocation);
1687
+ return handler({ invocation, callBuiltin });
1688
+ });
1689
+ }
1690
+ /**
1691
+ * Clear all registered tools, restoring built-in defaults.
1692
+ */
1693
+ clearTools() {
1694
+ if (!this.nativeBinding) {
1695
+ throw new Error("Native tool management requires the NAPI binding");
1696
+ }
1697
+ if (typeof this.nativeBinding.clearRegisteredTools === "function") {
1698
+ this.nativeBinding.clearRegisteredTools();
1699
+ }
1700
+ if (this.options.tools) {
1701
+ this.options.tools = [];
1702
+ }
1703
+ }
1704
+ buildConversationConfig(options = {}) {
1705
+ return {
1706
+ model: options.model ?? this.options.defaultModel,
1707
+ modelProvider: options.modelProvider ?? this.options.modelProvider,
1708
+ oss: options.oss,
1709
+ sandboxMode: options.sandboxMode,
1710
+ approvalMode: options.approvalMode,
1711
+ workspaceWriteOptions: options.workspaceWriteOptions,
1712
+ workingDirectory: options.workingDirectory,
1713
+ skipGitRepoCheck: options.skipGitRepoCheck,
1714
+ reasoningEffort: options.reasoningEffort,
1715
+ reasoningSummary: options.reasoningSummary,
1716
+ fullAuto: options.fullAuto,
1717
+ baseUrl: this.options.baseUrl,
1718
+ apiKey: this.options.apiKey
1719
+ };
1720
+ }
1721
+ createLspManagerForTools() {
1722
+ const cwd = typeof process !== "undefined" && typeof process.cwd === "function" ? process.cwd() : ".";
1723
+ const options = {
1724
+ workingDirectory: cwd,
1725
+ waitForDiagnostics: true
1726
+ };
1727
+ try {
1728
+ return new LspManager(options);
1729
+ } catch {
1730
+ return null;
1731
+ }
1732
+ }
1733
+ registerDefaultReadFileInterceptor() {
1734
+ if (!this.lspForTools) {
1735
+ return;
1736
+ }
1737
+ try {
1738
+ this.registerToolInterceptor("read_file", async ({ invocation, callBuiltin }) => {
1739
+ let base;
1740
+ try {
1741
+ base = await callBuiltin();
1742
+ } catch (err) {
1743
+ return {
1744
+ success: false,
1745
+ error: err instanceof Error ? err.message : String(err),
1746
+ output: void 0
1747
+ };
1748
+ }
1749
+ if (!base.output || base.success === false) {
1750
+ return base;
1751
+ }
1752
+ let filePath;
1753
+ if (invocation.arguments) {
1754
+ try {
1755
+ const args = JSON.parse(invocation.arguments);
1756
+ const candidate = typeof args.file_path === "string" && args.file_path || typeof args.path === "string" && args.path || void 0;
1757
+ if (candidate && candidate.trim().length > 0) {
1758
+ filePath = candidate;
1759
+ }
1760
+ } catch {
1761
+ }
1762
+ }
1763
+ if (!filePath) {
1764
+ return base;
1765
+ }
1766
+ let diagnosticsText = "";
1767
+ try {
1768
+ const results = await this.lspForTools.collectDiagnostics([filePath]);
1769
+ if (!results.length) {
1770
+ return base;
1771
+ }
1772
+ diagnosticsText = formatDiagnosticsForTool(results);
1773
+ } catch {
1774
+ return base;
1775
+ }
1776
+ if (!diagnosticsText) {
1777
+ return base;
1778
+ }
1779
+ const header = `LSP diagnostics for ${filePath}:
1780
+ ${diagnosticsText}`;
1781
+ return prependSystemHintToToolResult(base, header);
1782
+ });
1783
+ } catch {
1784
+ }
1785
+ }
1786
+ /**
1787
+ * Register a programmatic approval callback that Codex will call before executing
1788
+ * sensitive operations (e.g., shell commands, file writes).
1789
+ */
1790
+ setApprovalCallback(handler) {
1791
+ if (!this.nativeBinding || typeof this.nativeBinding.registerApprovalCallback !== "function") {
1792
+ console.warn("Approval callback is not available in this build");
1793
+ return;
1794
+ }
1795
+ this.nativeBinding.registerApprovalCallback(handler);
1796
+ }
1797
+ /**
1798
+ * Starts a new conversation with an agent.
1799
+ * @returns A new thread instance.
1800
+ */
1801
+ startThread(options = {}) {
1802
+ const threadOptions = {
1803
+ ...options,
1804
+ model: options.model ?? this.options.defaultModel
1805
+ };
1806
+ return new Thread(this.exec, this.options, threadOptions);
1807
+ }
1808
+ /**
1809
+ * Resumes a conversation with an agent based on the thread id.
1810
+ * Threads are persisted in ~/.codex/sessions.
1811
+ *
1812
+ * @param id The id of the thread to resume.
1813
+ * @returns A new thread instance.
1814
+ */
1815
+ resumeThread(id, options = {}) {
1816
+ const threadOptions = {
1817
+ ...options,
1818
+ model: options.model ?? this.options.defaultModel
1819
+ };
1820
+ return new Thread(this.exec, this.options, threadOptions, id);
1821
+ }
1822
+ async listConversations(options = {}) {
1823
+ const request = {
1824
+ config: this.buildConversationConfig(options),
1825
+ pageSize: options.pageSize,
1826
+ cursor: options.cursor,
1827
+ modelProviders: options.modelProviders
1828
+ };
1829
+ return this.exec.listConversations(request);
1830
+ }
1831
+ async deleteConversation(id, options = {}) {
1832
+ const result = await this.exec.deleteConversation({
1833
+ id,
1834
+ config: this.buildConversationConfig(options)
1835
+ });
1836
+ return result.deleted;
1837
+ }
1838
+ async resumeConversationFromRollout(rolloutPath, options = {}) {
1839
+ const result = await this.exec.resumeConversationFromRollout({
1840
+ rolloutPath,
1841
+ config: this.buildConversationConfig(options)
1842
+ });
1843
+ const threadOptions = {
1844
+ ...options,
1845
+ model: options.model ?? this.options.defaultModel
1846
+ };
1847
+ return new Thread(this.exec, this.options, threadOptions, result.threadId);
1848
+ }
1849
+ /**
1850
+ * Starts a review task using the built-in Codex review flow.
1851
+ */
1852
+ async review(options) {
1853
+ const generator = this.reviewStreamedInternal(options);
1854
+ const items = [];
1855
+ let finalResponse = "";
1856
+ let usage = null;
1857
+ let turnFailure = null;
1858
+ for await (const event of generator) {
1859
+ if (event === null) continue;
1860
+ if (event.type === "item.completed") {
1861
+ if (event.item.type === "agent_message") {
1862
+ finalResponse = event.item.text;
1863
+ }
1864
+ items.push(event.item);
1865
+ } else if (event.type === "exited_review_mode") {
1866
+ if (event.review_output) {
1867
+ const reviewOutput = event.review_output;
1868
+ let reviewText = "";
1869
+ if (reviewOutput.overall_explanation) {
1870
+ reviewText += reviewOutput.overall_explanation;
1871
+ }
1872
+ if (reviewOutput.findings && reviewOutput.findings.length > 0) {
1873
+ if (reviewText) reviewText += "\n\n";
1874
+ reviewText += "## Review Findings\n\n";
1875
+ reviewOutput.findings.forEach((finding, index) => {
1876
+ reviewText += `### ${index + 1}. ${finding.title}
1877
+ `;
1878
+ reviewText += `${finding.body}
1879
+ `;
1880
+ reviewText += `**Priority:** ${finding.priority} | **Confidence:** ${finding.confidence_score}
1881
+ `;
1882
+ reviewText += `**Location:** ${finding.code_location.absolute_file_path}:${finding.code_location.line_range.start}-${finding.code_location.line_range.end}
1883
+
1884
+ `;
1885
+ });
1886
+ }
1887
+ finalResponse = reviewText;
1888
+ }
1889
+ } else if (event.type === "turn.completed") {
1890
+ usage = event.usage;
1891
+ } else if (event.type === "turn.failed") {
1892
+ turnFailure = event.error;
1893
+ break;
1894
+ }
1895
+ }
1896
+ if (turnFailure) {
1897
+ throw new Error(turnFailure.message);
1898
+ }
1899
+ return { items, finalResponse, usage };
1900
+ }
1901
+ /**
1902
+ * Starts a review task and returns the event stream.
1903
+ */
1904
+ async reviewStreamed(options) {
1905
+ return { events: this.reviewStreamedInternal(options) };
1906
+ }
1907
+ async *reviewStreamedInternal(options) {
1908
+ const { target, threadOptions = {}, turnOptions = {} } = options;
1909
+ const { prompt, hint } = buildReviewPrompt(target);
1910
+ const normalizedSchema = normalizeOutputSchema(turnOptions.outputSchema);
1911
+ const needsSchemaFile = this.exec.requiresOutputSchemaFile();
1912
+ const schemaFile = needsSchemaFile ? await createOutputSchemaFile(normalizedSchema) : { schemaPath: void 0, cleanup: async () => {
1913
+ } };
1914
+ const generator = this.exec.run({
1915
+ input: prompt,
1916
+ baseUrl: this.options.baseUrl,
1917
+ apiKey: this.options.apiKey,
1918
+ model: threadOptions.model,
1919
+ modelProvider: threadOptions.modelProvider ?? this.options.modelProvider,
1920
+ oss: threadOptions.oss,
1921
+ sandboxMode: threadOptions.sandboxMode,
1922
+ approvalMode: threadOptions.approvalMode,
1923
+ workspaceWriteOptions: threadOptions.workspaceWriteOptions,
1924
+ workingDirectory: threadOptions.workingDirectory,
1925
+ skipGitRepoCheck: threadOptions.skipGitRepoCheck,
1926
+ outputSchemaFile: schemaFile.schemaPath,
1927
+ outputSchema: normalizedSchema,
1928
+ fullAuto: threadOptions.fullAuto,
1929
+ review: {
1930
+ userFacingHint: hint
1931
+ }
1932
+ });
1933
+ try {
1934
+ for await (const item of generator) {
1935
+ let parsed;
1936
+ try {
1937
+ parsed = JSON.parse(item);
1938
+ } catch (error) {
1939
+ throw new Error(`Failed to parse item: ${item}`, { cause: error });
1940
+ }
1941
+ yield parsed;
1942
+ }
1943
+ } finally {
1944
+ await schemaFile.cleanup();
1945
+ }
1946
+ }
1947
+ };
1948
+ function prependSystemHintToToolResult(base, hint) {
1949
+ const trimmedHint = hint.trim();
1950
+ if (!trimmedHint) {
1951
+ return base;
1952
+ }
1953
+ const existing = base.output ?? "";
1954
+ const separator = existing.length === 0 || existing.startsWith("\n") ? "\n\n" : "\n\n";
1955
+ const output = existing.length === 0 ? `[SYSTEM_HINT]
1956
+ ${trimmedHint}` : `[SYSTEM_HINT]
1957
+ ${trimmedHint}${separator}${existing}`;
1958
+ return {
1959
+ ...base,
1960
+ output
1961
+ };
1962
+ }
1963
+
1964
+ export {
1965
+ convertRustEventToThreadEvent,
1966
+ getNativeBinding,
1967
+ runApplyPatch,
1968
+ reverieListConversations,
1969
+ reverieSearchConversations,
1970
+ reverieSearchSemantic,
1971
+ reverieIndexSemantic,
1972
+ reverieGetConversationInsights,
1973
+ encodeToToon,
1974
+ fastEmbedInit,
1975
+ fastEmbedEmbed,
1976
+ tokenizerCount,
1977
+ tokenizerEncode,
1978
+ tokenizerDecode,
1979
+ collectRepoDiffSummary,
1980
+ startTui,
1981
+ runTui,
1982
+ DEFAULT_SERVERS,
1983
+ findServerForFile,
1984
+ resolveWorkspaceRoot,
1985
+ LspManager,
1986
+ formatDiagnosticsForTool,
1987
+ formatDiagnosticsForBackgroundEvent,
1988
+ filterBySeverity,
1989
+ summarizeDiagnostics,
1990
+ formatDiagnosticsWithSummary,
1991
+ LspDiagnosticsBridge,
1992
+ attachLspDiagnostics,
1993
+ Thread,
1994
+ Codex
1995
+ };
1996
+ //# sourceMappingURL=chunk-ZTUGAPWF.mjs.map