@peakflo/agent-harness 0.0.0-stage → 0.2.1-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -2
  3. package/dist/adapters/acp-adapter.d.ts +177 -0
  4. package/dist/adapters/acp-adapter.js +1732 -0
  5. package/dist/adapters/claude-code-adapter.d.ts +171 -0
  6. package/dist/adapters/claude-code-adapter.js +1918 -0
  7. package/dist/adapters/codex-adapter.d.ts +67 -0
  8. package/dist/adapters/codex-adapter.js +681 -0
  9. package/dist/adapters/codex-app-server-adapter.d.ts +105 -0
  10. package/dist/adapters/codex-app-server-adapter.js +1309 -0
  11. package/dist/adapters/coding-agent-adapter.d.ts +289 -0
  12. package/dist/adapters/coding-agent-adapter.js +31 -0
  13. package/dist/adapters/index.d.ts +17 -0
  14. package/dist/adapters/index.js +35 -0
  15. package/dist/adapters/model-gateway.d.ts +84 -0
  16. package/dist/adapters/model-gateway.js +270 -0
  17. package/dist/adapters/opencode-adapter.d.ts +403 -0
  18. package/dist/adapters/opencode-adapter.js +2710 -0
  19. package/dist/adapters/opencode-config.d.ts +41 -0
  20. package/dist/adapters/opencode-config.js +147 -0
  21. package/dist/adapters/opencode-server-policy.d.ts +82 -0
  22. package/dist/adapters/opencode-server-policy.js +148 -0
  23. package/dist/adapters/pi-adapter.d.ts +168 -0
  24. package/dist/adapters/pi-adapter.js +1468 -0
  25. package/dist/capabilities.d.ts +58 -0
  26. package/dist/capabilities.js +161 -0
  27. package/dist/cli.d.ts +2 -0
  28. package/dist/cli.js +231 -0
  29. package/dist/contract.d.ts +273 -0
  30. package/dist/contract.js +357 -0
  31. package/dist/control-plane.d.ts +178 -0
  32. package/dist/control-plane.js +431 -0
  33. package/dist/credential-store.d.ts +110 -0
  34. package/dist/credential-store.js +181 -0
  35. package/dist/enrol/browser-enrol.d.ts +85 -0
  36. package/dist/enrol/browser-enrol.js +387 -0
  37. package/dist/github-command-guards.d.ts +20 -0
  38. package/dist/github-command-guards.js +330 -0
  39. package/dist/github-proxy-certificates.d.ts +25 -0
  40. package/dist/github-proxy-certificates.js +145 -0
  41. package/dist/github-proxy.d.ts +37 -0
  42. package/dist/github-proxy.js +476 -0
  43. package/dist/github-workspace.d.ts +55 -0
  44. package/dist/github-workspace.js +537 -0
  45. package/dist/harness.d.ts +333 -0
  46. package/dist/harness.js +1414 -0
  47. package/dist/health.d.ts +45 -0
  48. package/dist/health.js +116 -0
  49. package/dist/index.d.ts +53 -0
  50. package/dist/index.js +123 -0
  51. package/dist/model-probe.d.ts +96 -0
  52. package/dist/model-probe.js +195 -0
  53. package/dist/options.d.ts +15 -0
  54. package/dist/options.js +212 -0
  55. package/dist/ports-default.d.ts +212 -0
  56. package/dist/ports-default.js +333 -0
  57. package/dist/ports.d.ts +436 -0
  58. package/dist/ports.js +27 -0
  59. package/dist/runtime/attachments.d.ts +92 -0
  60. package/dist/runtime/attachments.js +377 -0
  61. package/dist/runtime/child-stream-guards.d.ts +45 -0
  62. package/dist/runtime/child-stream-guards.js +111 -0
  63. package/dist/runtime/conversation.d.ts +87 -0
  64. package/dist/runtime/conversation.js +156 -0
  65. package/dist/runtime/lease-renewal.d.ts +169 -0
  66. package/dist/runtime/lease-renewal.js +433 -0
  67. package/dist/runtime/mcp-auth-proxy.d.ts +82 -0
  68. package/dist/runtime/mcp-auth-proxy.js +300 -0
  69. package/dist/runtime/permission-mode.d.ts +116 -0
  70. package/dist/runtime/permission-mode.js +146 -0
  71. package/dist/runtime/projection.d.ts +71 -0
  72. package/dist/runtime/projection.js +211 -0
  73. package/dist/runtime/session-runner.d.ts +520 -0
  74. package/dist/runtime/session-runner.js +2122 -0
  75. package/dist/shared/reasoning-effort.d.ts +11 -0
  76. package/dist/shared/reasoning-effort.js +20 -0
  77. package/dist/store/conversation-store.d.ts +100 -0
  78. package/dist/store/conversation-store.js +305 -0
  79. package/dist/store/sqlite-store.d.ts +120 -0
  80. package/dist/store/sqlite-store.js +346 -0
  81. package/dist/types.d.ts +952 -0
  82. package/dist/types.js +55 -0
  83. package/dist/version.d.ts +32 -0
  84. package/dist/version.js +77 -0
  85. package/package.json +53 -4
@@ -0,0 +1,681 @@
1
+ "use strict";
2
+ /**
3
+ * Codex Adapter
4
+ *
5
+ * Implements CodingAgentAdapter for OpenAI Codex via direct CLI integration.
6
+ *
7
+ * Key differences from Claude Code:
8
+ * - Uses JSON-RPC over stdio (like MCP protocol)
9
+ * - Spawns `codex` CLI process per session
10
+ * - Thread-based session model (thread_id instead of session_id)
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.CodexAdapter = exports.DEFAULT_CODEX_MODEL = void 0;
47
+ /**
48
+ * Extracted verbatim from 20x `src/main/adapters/codex-adapter.ts`.
49
+ * CHANGED: imports only — `../child-stream-guards` ->
50
+ * `../runtime/child-stream-guards`. Source formatting (no semicolons)
51
+ * preserved.
52
+ */
53
+ const child_process_1 = require("child_process");
54
+ const fs_1 = require("fs");
55
+ const child_stream_guards_1 = require("../runtime/child-stream-guards");
56
+ const coding_agent_adapter_1 = require("./coding-agent-adapter");
57
+ exports.DEFAULT_CODEX_MODEL = 'gpt-5.6-sol';
58
+ class CodexAdapter {
59
+ sessions = new Map();
60
+ codexExecutablePath = null;
61
+ /** Callback set by agent-manager to trigger an immediate poll cycle */
62
+ onDataAvailable;
63
+ async initialize() {
64
+ // Find codex CLI executable
65
+ this.codexExecutablePath = await this.findCodexExecutable();
66
+ console.log(`[CodexAdapter] Initialized with executable: ${this.codexExecutablePath}`);
67
+ }
68
+ /**
69
+ * Find the Codex CLI executable path
70
+ */
71
+ async findCodexExecutable() {
72
+ if (this.codexExecutablePath) {
73
+ return this.codexExecutablePath;
74
+ }
75
+ try {
76
+ const { execFile } = await Promise.resolve().then(() => __importStar(require('child_process')));
77
+ const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
78
+ const execFileAsync = promisify(execFile);
79
+ // Try to find codex in PATH
80
+ const isWin = process.platform === 'win32';
81
+ const whichCmd = isWin ? 'where' : 'which';
82
+ const binaryName = isWin ? 'codex.cmd' : 'codex';
83
+ const { stdout } = await execFileAsync(whichCmd, [binaryName]);
84
+ this.codexExecutablePath = stdout.trim().split(/\r?\n/)[0];
85
+ console.log(`[CodexAdapter] Found codex executable at: ${this.codexExecutablePath}`);
86
+ return this.codexExecutablePath;
87
+ }
88
+ catch {
89
+ // Common installation locations — must cover Homebrew, npm globals,
90
+ // pnpm globals, Volta, and NVM paths so the binary is found even when
91
+ // the shell PATH wasn't fully resolved (packaged macOS GUI app).
92
+ const home = process.env.HOME || process.env.USERPROFILE || '';
93
+ const commonPaths = process.platform === 'win32'
94
+ ? [
95
+ `${home}\\AppData\\Roaming\\npm\\codex.cmd`,
96
+ `${home}\\.local\\bin\\codex.cmd`
97
+ ]
98
+ : [
99
+ '/usr/local/bin/codex',
100
+ '/opt/homebrew/bin/codex',
101
+ `${home}/.local/bin/codex`,
102
+ `${home}/.npm-global/bin/codex`,
103
+ `${home}/Library/pnpm/codex`,
104
+ `${home}/.volta/bin/codex`,
105
+ ];
106
+ // Dynamically detect NVM-managed npm global bin (avoids hardcoding a Node version)
107
+ if (process.platform !== 'win32' && home) {
108
+ try {
109
+ const { join } = await Promise.resolve().then(() => __importStar(require('path')));
110
+ const { readdirSync } = await Promise.resolve().then(() => __importStar(require('fs')));
111
+ const nvmDir = join(home, '.nvm', 'versions', 'node');
112
+ const versions = readdirSync(nvmDir);
113
+ versions.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
114
+ for (const v of versions) {
115
+ commonPaths.push(join(nvmDir, v, 'bin', 'codex'));
116
+ }
117
+ }
118
+ catch {
119
+ // NVM not installed — skip
120
+ }
121
+ }
122
+ for (const path of commonPaths) {
123
+ if ((0, fs_1.existsSync)(path)) {
124
+ this.codexExecutablePath = path;
125
+ console.log(`[CodexAdapter] Found codex executable at: ${this.codexExecutablePath}`);
126
+ return this.codexExecutablePath;
127
+ }
128
+ }
129
+ throw new Error('Codex CLI not found. Install it from OpenAI: https://platform.openai.com/docs/codex');
130
+ }
131
+ }
132
+ /**
133
+ * Build environment variables for Codex process
134
+ */
135
+ buildCodexEnvironment() {
136
+ const env = { ...process.env };
137
+ // Ensure API key is available
138
+ if (!env.OPENAI_API_KEY && !env.CODEX_API_KEY) {
139
+ console.warn('[CodexAdapter] No OPENAI_API_KEY or CODEX_API_KEY found in environment');
140
+ }
141
+ return env;
142
+ }
143
+ buildCodexArgs(config) {
144
+ const args = [
145
+ '--model',
146
+ config.model || exports.DEFAULT_CODEX_MODEL,
147
+ ];
148
+ if (config.reasoningEffort && config.reasoningEffort !== 'max') {
149
+ args.push('-c', `model_reasoning_effort="${config.reasoningEffort}"`);
150
+ }
151
+ args.push('--json-rpc');
152
+ return args;
153
+ }
154
+ async createSession(config) {
155
+ if (!this.codexExecutablePath) {
156
+ throw new Error('Codex executable not found');
157
+ }
158
+ const abortController = new AbortController();
159
+ // Spawn codex process
160
+ // On Windows, .cmd wrappers need shell:true to resolve
161
+ const needsShell = process.platform === 'win32' && /\.(cmd|bat)$/i.test(this.codexExecutablePath);
162
+ const codexProcess = (0, child_process_1.spawn)(this.codexExecutablePath, this.buildCodexArgs(config), {
163
+ cwd: config.workspaceDir,
164
+ stdio: ['pipe', 'pipe', 'pipe'],
165
+ env: this.buildCodexEnvironment(),
166
+ ...(needsShell ? { shell: true } : {}),
167
+ });
168
+ // Every pipe needs an error listener before the first write. Codex can exit
169
+ // at any moment, and an unhandled EPIPE on its stdin crashes the main
170
+ // process.
171
+ (0, child_stream_guards_1.guardChildStreams)(codexProcess, 'CodexAdapter');
172
+ // Create session state
173
+ const session = {
174
+ sessionId: '', // Will be set to threadId after thread.create
175
+ threadId: '', // Will be set after thread.create
176
+ process: codexProcess,
177
+ workspaceDir: config.workspaceDir,
178
+ messageBuffer: [],
179
+ status: 'idle',
180
+ abortController,
181
+ config,
182
+ lastError: null,
183
+ nextRpcId: 1,
184
+ pendingRpcCalls: new Map(),
185
+ stdoutBuffer: '',
186
+ };
187
+ // Set up stdout parser
188
+ this.setupStdoutParser(codexProcess, session);
189
+ // Set up stderr logging
190
+ codexProcess.stderr?.on('data', (chunk) => {
191
+ console.error('[CodexAdapter] Codex stderr:', chunk.toString());
192
+ });
193
+ // Handle process exit
194
+ codexProcess.on('exit', (code, signal) => {
195
+ console.log(`[CodexAdapter] Codex process exited: code=${code}, signal=${signal}`);
196
+ if (code !== 0 && code !== null) {
197
+ session.status = 'error';
198
+ session.lastError = `Codex process exited with code ${code}`;
199
+ }
200
+ });
201
+ // Initialize JSON-RPC session
202
+ try {
203
+ const apiKey = process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY;
204
+ await this.sendRpcRequest(session, 'initialize', {
205
+ api_key: apiKey,
206
+ model: config.model || exports.DEFAULT_CODEX_MODEL,
207
+ });
208
+ // Create thread (Codex's session concept)
209
+ const threadResponse = (await this.sendRpcRequest(session, 'thread.create', {}));
210
+ session.threadId = threadResponse.thread_id;
211
+ session.sessionId = session.threadId;
212
+ console.log(`[CodexAdapter] Created Codex thread: ${session.threadId}`);
213
+ // Store session
214
+ this.sessions.set(session.threadId, session);
215
+ return session.threadId;
216
+ }
217
+ catch (error) {
218
+ // Cleanup on error
219
+ codexProcess.kill();
220
+ throw new Error(`Failed to create Codex session: ${error instanceof Error ? error.message : String(error)}`);
221
+ }
222
+ }
223
+ async resumeSession(sessionId, config) {
224
+ if (!this.codexExecutablePath) {
225
+ throw new Error('Codex executable not found');
226
+ }
227
+ // Validate session ID format (Codex uses thread_xxx format)
228
+ if (!sessionId.startsWith('thread_')) {
229
+ throw new Error('INCOMPATIBLE_SESSION_ID: This session was created with a different coding agent and cannot be resumed with Codex.');
230
+ }
231
+ const abortController = new AbortController();
232
+ // Spawn new codex process
233
+ // On Windows, .cmd wrappers need shell:true to resolve
234
+ const needsShellResume = process.platform === 'win32' && /\.(cmd|bat)$/i.test(this.codexExecutablePath);
235
+ const codexProcess = (0, child_process_1.spawn)(this.codexExecutablePath, this.buildCodexArgs(config), {
236
+ cwd: config.workspaceDir,
237
+ stdio: ['pipe', 'pipe', 'pipe'],
238
+ env: this.buildCodexEnvironment(),
239
+ ...(needsShellResume ? { shell: true } : {}),
240
+ });
241
+ // Every pipe needs an error listener before the first write. Codex can exit
242
+ // at any moment, and an unhandled EPIPE on its stdin crashes the main
243
+ // process.
244
+ (0, child_stream_guards_1.guardChildStreams)(codexProcess, 'CodexAdapter');
245
+ // Create session state
246
+ const session = {
247
+ sessionId,
248
+ threadId: sessionId,
249
+ process: codexProcess,
250
+ workspaceDir: config.workspaceDir,
251
+ messageBuffer: [],
252
+ status: 'idle',
253
+ abortController,
254
+ config,
255
+ lastError: null,
256
+ nextRpcId: 1,
257
+ pendingRpcCalls: new Map(),
258
+ stdoutBuffer: '',
259
+ };
260
+ // Set up stdout parser
261
+ this.setupStdoutParser(codexProcess, session);
262
+ // Set up stderr logging
263
+ codexProcess.stderr?.on('data', (chunk) => {
264
+ console.error('[CodexAdapter] Codex stderr:', chunk.toString());
265
+ });
266
+ // Handle process exit
267
+ codexProcess.on('exit', (code, signal) => {
268
+ console.log(`[CodexAdapter] Codex process exited: code=${code}, signal=${signal}`);
269
+ if (code !== 0 && code !== null) {
270
+ session.status = 'error';
271
+ session.lastError = `Codex process exited with code ${code}`;
272
+ }
273
+ });
274
+ try {
275
+ // Initialize JSON-RPC session
276
+ const apiKey = process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY;
277
+ await this.sendRpcRequest(session, 'initialize', {
278
+ api_key: apiKey,
279
+ model: config.model || exports.DEFAULT_CODEX_MODEL,
280
+ });
281
+ // Fetch thread history
282
+ const historyResponse = (await this.sendRpcRequest(session, 'thread.messages', {
283
+ thread_id: sessionId,
284
+ }));
285
+ // Store session
286
+ this.sessions.set(sessionId, session);
287
+ // Convert history to SessionMessage format
288
+ const messages = historyResponse.messages.map((msg) => ({
289
+ id: msg.id,
290
+ role: msg.role === 'user' ? coding_agent_adapter_1.MessageRole.USER : coding_agent_adapter_1.MessageRole.ASSISTANT,
291
+ parts: [
292
+ {
293
+ id: `${msg.id}-text`,
294
+ type: coding_agent_adapter_1.MessagePartType.TEXT,
295
+ text: msg.content,
296
+ },
297
+ ],
298
+ }));
299
+ console.log(`[CodexAdapter] Resumed session ${sessionId} with ${messages.length} messages`);
300
+ return messages;
301
+ }
302
+ catch (error) {
303
+ // Check if thread not found
304
+ const errMsg = error instanceof Error ? error.message : String(error);
305
+ if (errMsg.includes('thread not found') || errMsg.includes('Thread not found')) {
306
+ codexProcess.kill();
307
+ throw new Error('INCOMPATIBLE_SESSION_ID: This session does not exist on Codex servers. It may have been created with a different coding agent or has expired.');
308
+ }
309
+ // Cleanup on error
310
+ codexProcess.kill();
311
+ throw error;
312
+ }
313
+ }
314
+ async sendPrompt(sessionId, parts, _config) {
315
+ const session = this.sessions.get(sessionId);
316
+ if (!session) {
317
+ throw new Error(`Session not found: ${sessionId}`);
318
+ }
319
+ // Extract text from parts
320
+ const promptText = parts
321
+ .filter((p) => p.type === coding_agent_adapter_1.MessagePartType.TEXT && p.text)
322
+ .map((p) => p.text)
323
+ .join('\n');
324
+ if (!promptText) {
325
+ throw new Error('No text content in prompt parts');
326
+ }
327
+ // Add user message to buffer
328
+ const userMessage = {
329
+ type: 'text',
330
+ id: `user-${Date.now()}`,
331
+ role: 'user',
332
+ content: promptText,
333
+ };
334
+ session.messageBuffer.push(userMessage);
335
+ session.status = 'busy';
336
+ try {
337
+ // Send message via JSON-RPC
338
+ await this.sendRpcRequest(session, 'message.create', {
339
+ thread_id: session.threadId,
340
+ role: 'user',
341
+ content: promptText,
342
+ });
343
+ console.log(`[CodexAdapter] Sent prompt to thread ${session.threadId}`);
344
+ }
345
+ catch (error) {
346
+ session.status = 'error';
347
+ session.lastError = error instanceof Error ? error.message : String(error);
348
+ throw error;
349
+ }
350
+ }
351
+ async getStatus(sessionId, _config) {
352
+ const session = this.sessions.get(sessionId);
353
+ if (!session) {
354
+ return { type: coding_agent_adapter_1.SessionStatusType.ERROR, message: 'Session not found' };
355
+ }
356
+ if (session.lastError) {
357
+ return { type: coding_agent_adapter_1.SessionStatusType.ERROR, message: session.lastError };
358
+ }
359
+ return {
360
+ type: session.status === 'busy' ? coding_agent_adapter_1.SessionStatusType.BUSY : coding_agent_adapter_1.SessionStatusType.IDLE,
361
+ };
362
+ }
363
+ async pollMessages(sessionId, seenMessageIds, seenPartIds, partContentLengths, _config) {
364
+ const session = this.sessions.get(sessionId);
365
+ if (!session) {
366
+ return [];
367
+ }
368
+ const newParts = [];
369
+ // Process buffered messages
370
+ for (const codexMsg of session.messageBuffer) {
371
+ if (seenMessageIds.has(codexMsg.id))
372
+ continue;
373
+ seenMessageIds.add(codexMsg.id);
374
+ // Convert CodexMessage to MessagePart
375
+ const parts = this.convertCodexMessageToParts(codexMsg, seenPartIds, partContentLengths);
376
+ newParts.push(...parts);
377
+ }
378
+ // Clear processed messages from buffer
379
+ session.messageBuffer = [];
380
+ return newParts;
381
+ }
382
+ async abortPrompt(sessionId, _config) {
383
+ const session = this.sessions.get(sessionId);
384
+ if (!session) {
385
+ throw new Error(`Session not found: ${sessionId}`);
386
+ }
387
+ // Send abort request
388
+ try {
389
+ await this.sendRpcRequest(session, 'message.abort', {
390
+ thread_id: session.threadId,
391
+ });
392
+ }
393
+ catch (error) {
394
+ console.warn(`[CodexAdapter] Failed to abort: ${error instanceof Error ? error.message : String(error)}`);
395
+ }
396
+ session.status = 'idle';
397
+ }
398
+ async destroySession(sessionId, _config) {
399
+ const session = this.sessions.get(sessionId);
400
+ if (!session) {
401
+ return;
402
+ }
403
+ // Kill process
404
+ session.process.kill('SIGTERM');
405
+ // Wait a bit, then force kill if needed
406
+ setTimeout(() => {
407
+ if (!session.process.killed) {
408
+ session.process.kill('SIGKILL');
409
+ }
410
+ }, 1000);
411
+ // Remove session
412
+ this.sessions.delete(sessionId);
413
+ }
414
+ async registerMcpServer(_serverName, _mcpConfig, _workspaceDir) {
415
+ // Codex doesn't support MCP servers yet
416
+ // This is a no-op for now
417
+ console.warn('[CodexAdapter] MCP servers are not yet supported by Codex');
418
+ }
419
+ async checkHealth() {
420
+ try {
421
+ const execPath = await this.findCodexExecutable();
422
+ if (!execPath) {
423
+ return { available: false, reason: 'Codex CLI not found. Install from: https://openai.com/codex' };
424
+ }
425
+ // Check if API key is available
426
+ if (!process.env.OPENAI_API_KEY && !process.env.CODEX_API_KEY) {
427
+ return { available: false, reason: 'OPENAI_API_KEY not set in environment' };
428
+ }
429
+ // Verify Codex CLI is executable by checking version
430
+ try {
431
+ const { execFile } = await Promise.resolve().then(() => __importStar(require('child_process')));
432
+ const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
433
+ const execFileAsync = promisify(execFile);
434
+ await execFileAsync(execPath, ['--version'], { timeout: 5000 });
435
+ return { available: true };
436
+ }
437
+ catch (error) {
438
+ return { available: false, reason: `Codex CLI found but not executable: ${error instanceof Error ? error.message : String(error)}` };
439
+ }
440
+ }
441
+ catch (error) {
442
+ return { available: false, reason: error instanceof Error ? error.message : String(error) };
443
+ }
444
+ }
445
+ // ========================================================================
446
+ // Private Helper Methods
447
+ // ========================================================================
448
+ /**
449
+ * Set up stdout parser for JSON-RPC messages
450
+ */
451
+ setupStdoutParser(process, session) {
452
+ process.stdout?.on('data', (chunk) => {
453
+ session.stdoutBuffer += chunk.toString();
454
+ // Parse newline-delimited JSON messages
455
+ let newlineIndex;
456
+ while ((newlineIndex = session.stdoutBuffer.indexOf('\n')) !== -1) {
457
+ const line = session.stdoutBuffer.slice(0, newlineIndex).trim();
458
+ session.stdoutBuffer = session.stdoutBuffer.slice(newlineIndex + 1);
459
+ if (!line)
460
+ continue;
461
+ try {
462
+ const message = JSON.parse(line);
463
+ this.handleRpcMessage(session, message);
464
+ }
465
+ catch {
466
+ console.error('[CodexAdapter] Failed to parse JSON-RPC message:', line);
467
+ }
468
+ }
469
+ });
470
+ }
471
+ /**
472
+ * Handle incoming JSON-RPC message
473
+ */
474
+ handleRpcMessage(session, message) {
475
+ // Response to our RPC request
476
+ if ('id' in message && message.id !== undefined) {
477
+ const pending = session.pendingRpcCalls.get(message.id);
478
+ if (pending) {
479
+ session.pendingRpcCalls.delete(message.id);
480
+ // Type guard for JsonRpcResponse
481
+ const response = message;
482
+ if ('error' in response && response.error) {
483
+ pending.reject(new Error(response.error.message));
484
+ }
485
+ else if ('result' in response) {
486
+ pending.resolve(response.result);
487
+ }
488
+ else {
489
+ pending.reject(new Error('Invalid RPC response'));
490
+ }
491
+ }
492
+ return;
493
+ }
494
+ // Notification (streaming event)
495
+ if ('method' in message) {
496
+ this.handleNotification(session, message);
497
+ }
498
+ }
499
+ /**
500
+ * Handle JSON-RPC notification (streaming events)
501
+ */
502
+ handleNotification(session, notification) {
503
+ const params = notification.params;
504
+ const delta = params.delta;
505
+ switch (notification.method) {
506
+ case 'message.delta':
507
+ // Text content streaming
508
+ session.messageBuffer.push({
509
+ type: 'text',
510
+ id: params.message_id || `msg-${Date.now()}`,
511
+ role: params.role || 'assistant',
512
+ delta: delta?.content || '',
513
+ content: delta?.content || '',
514
+ });
515
+ break;
516
+ case 'message.completed':
517
+ // Message finished
518
+ session.status = 'idle';
519
+ session.messageBuffer.push({
520
+ type: 'text',
521
+ id: params.message_id || `msg-${Date.now()}`,
522
+ role: 'assistant',
523
+ finished: true,
524
+ });
525
+ break;
526
+ case 'tool.call':
527
+ // Tool call started
528
+ session.messageBuffer.push({
529
+ type: 'tool_call',
530
+ id: params.tool_call_id || `tool-${Date.now()}`,
531
+ tool_name: params.tool_name,
532
+ tool_call_id: params.tool_call_id,
533
+ arguments: params.arguments,
534
+ });
535
+ break;
536
+ case 'tool.result':
537
+ // Tool call completed
538
+ session.messageBuffer.push({
539
+ type: 'tool_result',
540
+ id: params.tool_call_id || `tool-${Date.now()}`,
541
+ tool_call_id: params.tool_call_id,
542
+ result: params.result,
543
+ });
544
+ break;
545
+ case 'error':
546
+ // Error occurred
547
+ session.status = 'error';
548
+ session.lastError = params.message || 'Unknown error';
549
+ session.messageBuffer.push({
550
+ type: 'error',
551
+ id: `error-${Date.now()}`,
552
+ error: params.message,
553
+ });
554
+ break;
555
+ default:
556
+ console.log(`[CodexAdapter] Unknown notification: ${notification.method}`);
557
+ }
558
+ // Notify the polling coordinator that new data is available
559
+ this.onDataAvailable?.(session.sessionId);
560
+ }
561
+ /**
562
+ * Send JSON-RPC request and wait for response
563
+ */
564
+ sendRpcRequest(session, method, params) {
565
+ return new Promise((resolve, reject) => {
566
+ const id = session.nextRpcId++;
567
+ const request = {
568
+ jsonrpc: '2.0',
569
+ id,
570
+ method,
571
+ params,
572
+ };
573
+ // Store pending call
574
+ session.pendingRpcCalls.set(id, { resolve, reject });
575
+ // Send request
576
+ const requestJson = JSON.stringify(request) + '\n';
577
+ session.process.stdin?.write(requestJson, (error) => {
578
+ if (error) {
579
+ session.pendingRpcCalls.delete(id);
580
+ reject(error);
581
+ }
582
+ });
583
+ // Timeout after 30 seconds
584
+ setTimeout(() => {
585
+ if (session.pendingRpcCalls.has(id)) {
586
+ session.pendingRpcCalls.delete(id);
587
+ reject(new Error(`RPC request timed out: ${method}`));
588
+ }
589
+ }, 30000);
590
+ });
591
+ }
592
+ /**
593
+ * Convert CodexMessage to MessagePart format
594
+ */
595
+ convertCodexMessageToParts(msg, seenPartIds, partContentLengths) {
596
+ const parts = [];
597
+ switch (msg.type) {
598
+ case 'text':
599
+ const textPartId = `${msg.id}-text`;
600
+ if (!seenPartIds.has(textPartId)) {
601
+ seenPartIds.add(textPartId);
602
+ const content = msg.content || msg.delta || '';
603
+ partContentLengths.set(textPartId, String(content.length));
604
+ parts.push({
605
+ id: textPartId,
606
+ type: coding_agent_adapter_1.MessagePartType.TEXT,
607
+ text: content,
608
+ role: msg.role,
609
+ });
610
+ }
611
+ break;
612
+ case 'tool_call':
613
+ const toolCallPartId = `tool-${msg.tool_call_id}`;
614
+ if (!seenPartIds.has(toolCallPartId)) {
615
+ seenPartIds.add(toolCallPartId);
616
+ const input = msg.arguments ? JSON.stringify(msg.arguments, null, 2) : undefined;
617
+ partContentLengths.set(toolCallPartId, `pending:${msg.tool_name}`);
618
+ parts.push({
619
+ id: toolCallPartId,
620
+ type: coding_agent_adapter_1.MessagePartType.TOOL,
621
+ content: `Tool: ${msg.tool_name}`,
622
+ tool: {
623
+ name: msg.tool_name || 'unknown',
624
+ status: 'pending',
625
+ input,
626
+ },
627
+ });
628
+ }
629
+ break;
630
+ case 'tool_result':
631
+ const toolResultPartId = `tool-${msg.tool_call_id}`;
632
+ const previousContent = partContentLengths.get(toolResultPartId);
633
+ if (previousContent) {
634
+ // Update existing tool part
635
+ partContentLengths.set(toolResultPartId, `success:${msg.result?.length || 0}`);
636
+ parts.push({
637
+ id: toolResultPartId,
638
+ type: coding_agent_adapter_1.MessagePartType.TOOL,
639
+ content: 'Tool completed',
640
+ tool: {
641
+ name: previousContent.split(':')[1] || 'tool',
642
+ status: 'success',
643
+ output: msg.result?.slice(0, 2000),
644
+ },
645
+ update: true,
646
+ });
647
+ }
648
+ else {
649
+ // First time seeing this tool (result before call)
650
+ if (!seenPartIds.has(toolResultPartId)) {
651
+ seenPartIds.add(toolResultPartId);
652
+ partContentLengths.set(toolResultPartId, `success:${msg.result?.length || 0}`);
653
+ parts.push({
654
+ id: toolResultPartId,
655
+ type: coding_agent_adapter_1.MessagePartType.TOOL,
656
+ content: 'Tool result',
657
+ tool: {
658
+ name: 'tool',
659
+ status: 'success',
660
+ output: msg.result?.slice(0, 2000),
661
+ },
662
+ });
663
+ }
664
+ }
665
+ break;
666
+ case 'error':
667
+ const errorPartId = `${msg.id}-error`;
668
+ if (!seenPartIds.has(errorPartId)) {
669
+ seenPartIds.add(errorPartId);
670
+ parts.push({
671
+ id: errorPartId,
672
+ type: coding_agent_adapter_1.MessagePartType.ERROR,
673
+ content: msg.error || 'Unknown error',
674
+ });
675
+ }
676
+ break;
677
+ }
678
+ return parts;
679
+ }
680
+ }
681
+ exports.CodexAdapter = CodexAdapter;