@smithers-orchestrator/agents 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +65 -0
  3. package/src/AgentLike.ts +28 -0
  4. package/src/AmpAgent.js +232 -0
  5. package/src/AmpAgentOptions.ts +26 -0
  6. package/src/AnthropicAgent.js +54 -0
  7. package/src/AnthropicAgentOptions.ts +8 -0
  8. package/src/BaseCliAgent/AgentCliActionKind.ts +10 -0
  9. package/src/BaseCliAgent/AgentCliEvent.ts +44 -0
  10. package/src/BaseCliAgent/BaseCliAgent.js +874 -0
  11. package/src/BaseCliAgent/BaseCliAgentOptions.ts +13 -0
  12. package/src/BaseCliAgent/CliOutputInterpreter.ts +8 -0
  13. package/src/BaseCliAgent/CliUsageInfo.ts +7 -0
  14. package/src/BaseCliAgent/CodexConfigOverrides.ts +3 -0
  15. package/src/BaseCliAgent/PiExtensionUiRequest.ts +10 -0
  16. package/src/BaseCliAgent/PiExtensionUiResponse.ts +7 -0
  17. package/src/BaseCliAgent/RunCommandResult.ts +5 -0
  18. package/src/BaseCliAgent/buildGenerateResult.js +57 -0
  19. package/src/BaseCliAgent/combineNonEmpty.js +8 -0
  20. package/src/BaseCliAgent/createAgentStdoutTextEmitter.js +198 -0
  21. package/src/BaseCliAgent/extractPrompt.js +88 -0
  22. package/src/BaseCliAgent/extractTextFromJsonValue.js +46 -0
  23. package/src/BaseCliAgent/index.js +32 -0
  24. package/src/BaseCliAgent/normalizeCodexConfig.js +22 -0
  25. package/src/BaseCliAgent/parseHelpers.js +111 -0
  26. package/src/BaseCliAgent/pushFlag.js +18 -0
  27. package/src/BaseCliAgent/pushList.js +10 -0
  28. package/src/BaseCliAgent/resolveTimeouts.js +24 -0
  29. package/src/BaseCliAgent/runCommandEffect.js +32 -0
  30. package/src/BaseCliAgent/runRpcCommandEffect.js +365 -0
  31. package/src/BaseCliAgent/truncateToBytes.js +13 -0
  32. package/src/BaseCliAgent/tryParseJson.js +18 -0
  33. package/src/ClaudeCodeAgent.js +455 -0
  34. package/src/ClaudeCodeAgentOptions.ts +52 -0
  35. package/src/CodexAgent.js +593 -0
  36. package/src/CodexAgentOptions.ts +23 -0
  37. package/src/ForgeAgent.js +128 -0
  38. package/src/ForgeAgentOptions.ts +14 -0
  39. package/src/GeminiAgent.js +273 -0
  40. package/src/GeminiAgentOptions.ts +20 -0
  41. package/src/KimiAgent.js +260 -0
  42. package/src/KimiAgentOptions.ts +21 -0
  43. package/src/OpenAIAgent.js +54 -0
  44. package/src/OpenAIAgentOptions.ts +8 -0
  45. package/src/PiAgent.js +468 -0
  46. package/src/PiAgentOptions.ts +40 -0
  47. package/src/SdkAgentOptions.ts +16 -0
  48. package/src/agent-contract/SmithersAgentContract.ts +10 -0
  49. package/src/agent-contract/SmithersAgentContractTool.ts +8 -0
  50. package/src/agent-contract/SmithersAgentToolCategory.ts +6 -0
  51. package/src/agent-contract/SmithersListedTool.ts +4 -0
  52. package/src/agent-contract/SmithersToolSurface.ts +1 -0
  53. package/src/agent-contract/createSmithersAgentContract.js +188 -0
  54. package/src/agent-contract/index.js +10 -0
  55. package/src/agent-contract/renderSmithersAgentPromptGuidance.js +81 -0
  56. package/src/capability-registry/AgentCapabilityRegistry.ts +22 -0
  57. package/src/capability-registry/AgentToolDescriptor.ts +4 -0
  58. package/src/capability-registry/hashCapabilityRegistry.js +43 -0
  59. package/src/capability-registry/index.js +8 -0
  60. package/src/capability-registry/normalizeCapabilityRegistry.js +52 -0
  61. package/src/capability-registry/normalizeCapabilityStringList.js +9 -0
  62. package/src/cli-capabilities/CliAgentCapabilityAdapterId.ts +6 -0
  63. package/src/cli-capabilities/CliAgentCapabilityDoctorReport.ts +18 -0
  64. package/src/cli-capabilities/CliAgentCapabilityReportEntry.ts +9 -0
  65. package/src/cli-capabilities/formatCliAgentCapabilityDoctorReport.js +24 -0
  66. package/src/cli-capabilities/getCliAgentCapabilityDoctorReport.js +92 -0
  67. package/src/cli-capabilities/getCliAgentCapabilityReport.js +52 -0
  68. package/src/cli-capabilities/index.js +11 -0
  69. package/src/diagnostics/DiagnosticCheck.ts +11 -0
  70. package/src/diagnostics/DiagnosticCheckId.ts +4 -0
  71. package/src/diagnostics/DiagnosticContext.ts +4 -0
  72. package/src/diagnostics/DiagnosticReport.ts +9 -0
  73. package/src/diagnostics/enrichReportWithErrorAnalysis.js +34 -0
  74. package/src/diagnostics/formatDiagnosticSummary.js +17 -0
  75. package/src/diagnostics/getDiagnosticStrategy.js +503 -0
  76. package/src/diagnostics/index.js +13 -0
  77. package/src/diagnostics/launchDiagnostics.js +16 -0
  78. package/src/diagnostics/runDiagnostics.js +52 -0
  79. package/src/index.d.ts +872 -0
  80. package/src/index.js +39 -0
  81. package/src/resolveSdkModel.js +9 -0
  82. package/src/sanitizeForOpenAI.js +47 -0
  83. package/src/streamResultToGenerateResult.js +70 -0
  84. package/src/zodToOpenAISchema.js +16 -0
@@ -0,0 +1,593 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { randomUUID } from "node:crypto";
5
+ import { BaseCliAgent, normalizeCodexConfig, pushFlag, pushList, isRecord, asString, asNumber, truncate, shouldSurfaceUnparsedStdout, createSyntheticIdGenerator, } from "./BaseCliAgent/index.js";
6
+ import { normalizeCapabilityStringList, } from "./capability-registry/index.js";
7
+ import { sanitizeForOpenAI } from "./sanitizeForOpenAI.js";
8
+ /** @typedef {import("./BaseCliAgent/BaseCliAgentOptions.ts").BaseCliAgentOptions} BaseCliAgentOptions */
9
+ /** @typedef {import("./BaseCliAgent/CodexConfigOverrides.ts").CodexConfigOverrides} CodexConfigOverrides */
10
+ /** @typedef {import("./capability-registry/AgentCapabilityRegistry.ts").AgentCapabilityRegistry} AgentCapabilityRegistry */
11
+ /** @typedef {import("./BaseCliAgent/CliOutputInterpreter.ts").CliOutputInterpreter} CliOutputInterpreter */
12
+ /** @typedef {import("./CodexAgentOptions.ts").CodexAgentOptions} CodexAgentOptions */
13
+
14
+ /**
15
+ * @param {CodexAgentOptions} opts
16
+ */
17
+ function resolveCodexBuiltIns(opts) {
18
+ if (opts.enable?.length || opts.disable?.length) {
19
+ return normalizeCapabilityStringList([
20
+ ...(opts.enable ?? []).map((feature) => `enable:${feature}`),
21
+ ...(opts.disable ?? []).map((feature) => `disable:${feature}`),
22
+ ]);
23
+ }
24
+ return ["default"];
25
+ }
26
+ /**
27
+ * @param {CodexAgentOptions} [opts]
28
+ * @returns {AgentCapabilityRegistry}
29
+ */
30
+ export function createCodexCapabilityRegistry(opts = {}) {
31
+ return {
32
+ version: 1,
33
+ engine: "codex",
34
+ runtimeTools: {},
35
+ mcp: {
36
+ bootstrap: "inline-config",
37
+ supportsProjectScope: true,
38
+ supportsUserScope: false,
39
+ },
40
+ skills: {
41
+ supportsSkills: false,
42
+ smithersSkillIds: [],
43
+ },
44
+ humanInteraction: {
45
+ supportsUiRequests: false,
46
+ methods: [],
47
+ },
48
+ builtIns: resolveCodexBuiltIns(opts),
49
+ };
50
+ }
51
+ export class CodexAgent extends BaseCliAgent {
52
+ opts;
53
+ capabilities;
54
+ cliEngine = "codex";
55
+ /**
56
+ * @param {CodexAgentOptions} [opts]
57
+ */
58
+ constructor(opts = {}) {
59
+ super(opts);
60
+ this.opts = opts;
61
+ this.capabilities = createCodexCapabilityRegistry(opts);
62
+ }
63
+ /**
64
+ * @returns {CliOutputInterpreter}
65
+ */
66
+ createOutputInterpreter() {
67
+ let turnIndex = 0;
68
+ let threadId;
69
+ let finalAnswer = "";
70
+ let didEmitCompleted = false;
71
+ const nextSyntheticId = createSyntheticIdGenerator();
72
+ /**
73
+ * @param {Record<string, unknown>} item
74
+ * @param {"started" | "updated" | "completed"} phase
75
+ * @returns {AgentCliEvent | null}
76
+ */
77
+ const actionForItem = (item, phase) => {
78
+ const itemId = asString(item.id) ?? nextSyntheticId("item");
79
+ const itemType = asString(item.type) ?? "note";
80
+ if (itemType === "agent_message") {
81
+ if (phase === "completed") {
82
+ const text = asString(item.text)?.trim();
83
+ if (text) {
84
+ finalAnswer = text;
85
+ return {
86
+ type: "action",
87
+ engine: this.cliEngine,
88
+ phase: "completed",
89
+ entryType: "message",
90
+ action: {
91
+ id: itemId,
92
+ kind: "note",
93
+ title: "assistant",
94
+ detail: { type: itemType },
95
+ },
96
+ message: text,
97
+ ok: true,
98
+ level: "info",
99
+ };
100
+ }
101
+ }
102
+ return null;
103
+ }
104
+ if (itemType === "reasoning") {
105
+ return {
106
+ type: "action",
107
+ engine: this.cliEngine,
108
+ phase,
109
+ entryType: "thought",
110
+ action: {
111
+ id: itemId,
112
+ kind: "reasoning",
113
+ title: "reasoning",
114
+ detail: { type: itemType },
115
+ },
116
+ message: asString(item.text),
117
+ ok: phase === "completed" ? true : undefined,
118
+ level: "info",
119
+ };
120
+ }
121
+ if (itemType === "command_execution") {
122
+ const status = asString(item.status);
123
+ const exitCode = asNumber(item.exit_code);
124
+ const command = asString(item.command) ?? "command";
125
+ return {
126
+ type: "action",
127
+ engine: this.cliEngine,
128
+ phase,
129
+ entryType: "thought",
130
+ action: {
131
+ id: itemId,
132
+ kind: "command",
133
+ title: truncate(command, 160),
134
+ detail: {
135
+ type: itemType,
136
+ status,
137
+ exitCode,
138
+ },
139
+ },
140
+ message: phase === "started" ? `Running ${truncate(command, 120)}` : undefined,
141
+ ok: phase === "completed"
142
+ ? status === "completed" && (exitCode === undefined || exitCode === 0)
143
+ : undefined,
144
+ level: phase === "completed" && status === "failed" ? "warning" : "info",
145
+ };
146
+ }
147
+ if (itemType === "file_change") {
148
+ const rawChanges = Array.isArray(item.changes) ? item.changes : [];
149
+ const files = rawChanges
150
+ .map((entry) => {
151
+ if (!isRecord(entry))
152
+ return null;
153
+ const pathValue = asString(entry.path);
154
+ const kindValue = asString(entry.kind);
155
+ if (!pathValue || !kindValue)
156
+ return null;
157
+ return `${kindValue} ${pathValue}`;
158
+ })
159
+ .filter((entry) => Boolean(entry));
160
+ const message = files.length > 0 ? files.slice(0, 4).join(", ") : "Updated files";
161
+ return {
162
+ type: "action",
163
+ engine: this.cliEngine,
164
+ phase: "completed",
165
+ entryType: "thought",
166
+ action: {
167
+ id: itemId,
168
+ kind: "file_change",
169
+ title: "file changes",
170
+ detail: {
171
+ type: itemType,
172
+ changes: rawChanges,
173
+ },
174
+ },
175
+ message,
176
+ ok: asString(item.status) !== "failed",
177
+ level: "info",
178
+ };
179
+ }
180
+ if (itemType === "mcp_tool_call") {
181
+ const server = asString(item.server) ?? "mcp";
182
+ const tool = asString(item.tool) ?? "tool";
183
+ const status = asString(item.status);
184
+ const errorMessage = isRecord(item.error) ? asString(item.error.message) : undefined;
185
+ return {
186
+ type: "action",
187
+ engine: this.cliEngine,
188
+ phase,
189
+ entryType: "thought",
190
+ action: {
191
+ id: itemId,
192
+ kind: "tool",
193
+ title: `${server}.${tool}`,
194
+ detail: {
195
+ type: itemType,
196
+ server,
197
+ tool,
198
+ status,
199
+ arguments: item.arguments,
200
+ },
201
+ },
202
+ message: errorMessage,
203
+ ok: phase === "completed" ? status !== "failed" : undefined,
204
+ level: phase === "completed" && status === "failed" ? "warning" : "info",
205
+ };
206
+ }
207
+ if (itemType === "web_search") {
208
+ const query = asString(item.query) ?? "";
209
+ return {
210
+ type: "action",
211
+ engine: this.cliEngine,
212
+ phase: "completed",
213
+ entryType: "thought",
214
+ action: {
215
+ id: itemId,
216
+ kind: "web_search",
217
+ title: "web search",
218
+ detail: {
219
+ type: itemType,
220
+ query,
221
+ },
222
+ },
223
+ message: query ? `Web search: ${truncate(query, 120)}` : undefined,
224
+ ok: true,
225
+ level: "info",
226
+ };
227
+ }
228
+ if (itemType === "todo_list") {
229
+ const items = Array.isArray(item.items) ? item.items : [];
230
+ const completedCount = items.filter((entry) => isRecord(entry) && entry.completed === true).length;
231
+ const message = `${completedCount}/${items.length} tasks complete`;
232
+ return {
233
+ type: "action",
234
+ engine: this.cliEngine,
235
+ phase,
236
+ entryType: "thought",
237
+ action: {
238
+ id: itemId,
239
+ kind: "todo_list",
240
+ title: "todo list",
241
+ detail: {
242
+ type: itemType,
243
+ items,
244
+ },
245
+ },
246
+ message,
247
+ ok: phase === "completed" ? true : undefined,
248
+ level: "info",
249
+ };
250
+ }
251
+ if (itemType === "error") {
252
+ return {
253
+ type: "action",
254
+ engine: this.cliEngine,
255
+ phase: "completed",
256
+ entryType: "thought",
257
+ action: {
258
+ id: itemId,
259
+ kind: "warning",
260
+ title: "warning",
261
+ detail: { type: itemType },
262
+ },
263
+ message: asString(item.message) ?? "Codex reported a warning",
264
+ ok: true,
265
+ level: "warning",
266
+ };
267
+ }
268
+ return {
269
+ type: "action",
270
+ engine: this.cliEngine,
271
+ phase,
272
+ entryType: "thought",
273
+ action: {
274
+ id: itemId,
275
+ kind: "note",
276
+ title: itemType,
277
+ detail: { item },
278
+ },
279
+ level: "debug",
280
+ };
281
+ };
282
+ /**
283
+ * @param {string} line
284
+ * @returns {AgentCliEvent[]}
285
+ */
286
+ const parseLine = (line) => {
287
+ const trimmedLine = line.trim();
288
+ if (!trimmedLine) {
289
+ return [];
290
+ }
291
+ let payload;
292
+ try {
293
+ payload = JSON.parse(trimmedLine);
294
+ }
295
+ catch {
296
+ if (!shouldSurfaceUnparsedStdout(trimmedLine)) {
297
+ return [];
298
+ }
299
+ return [
300
+ {
301
+ type: "action",
302
+ engine: this.cliEngine,
303
+ phase: "completed",
304
+ entryType: "thought",
305
+ action: {
306
+ id: nextSyntheticId("codex-line"),
307
+ kind: "warning",
308
+ title: "stdout",
309
+ detail: {},
310
+ },
311
+ message: truncate(trimmedLine, 220),
312
+ ok: true,
313
+ level: "warning",
314
+ },
315
+ ];
316
+ }
317
+ if (!isRecord(payload)) {
318
+ return [];
319
+ }
320
+ const payloadType = asString(payload.type);
321
+ if (!payloadType) {
322
+ return [];
323
+ }
324
+ if (payloadType === "thread.started") {
325
+ const parsedThreadId = asString(payload.thread_id);
326
+ if (parsedThreadId) {
327
+ threadId = parsedThreadId;
328
+ }
329
+ return [
330
+ {
331
+ type: "started",
332
+ engine: this.cliEngine,
333
+ title: "Codex",
334
+ resume: threadId,
335
+ detail: threadId ? { threadId } : undefined,
336
+ },
337
+ ];
338
+ }
339
+ if (payloadType === "turn.started") {
340
+ turnIndex += 1;
341
+ return [
342
+ {
343
+ type: "action",
344
+ engine: this.cliEngine,
345
+ phase: "started",
346
+ entryType: "thought",
347
+ action: {
348
+ id: `turn-${turnIndex}`,
349
+ kind: "turn",
350
+ title: `turn ${turnIndex}`,
351
+ detail: {},
352
+ },
353
+ message: `Turn ${turnIndex} started`,
354
+ level: "info",
355
+ },
356
+ ];
357
+ }
358
+ if (payloadType === "item.started" ||
359
+ payloadType === "item.updated" ||
360
+ payloadType === "item.completed") {
361
+ const item = isRecord(payload.item) ? payload.item : null;
362
+ if (!item) {
363
+ return [];
364
+ }
365
+ const phase = payloadType === "item.started"
366
+ ? "started"
367
+ : payloadType === "item.updated"
368
+ ? "updated"
369
+ : "completed";
370
+ const action = actionForItem(item, phase);
371
+ return action ? [action] : [];
372
+ }
373
+ if (payloadType === "turn.completed") {
374
+ if (didEmitCompleted) {
375
+ return [];
376
+ }
377
+ didEmitCompleted = true;
378
+ return [
379
+ {
380
+ type: "completed",
381
+ engine: this.cliEngine,
382
+ ok: true,
383
+ answer: finalAnswer,
384
+ resume: threadId,
385
+ usage: isRecord(payload.usage) ? payload.usage : undefined,
386
+ },
387
+ ];
388
+ }
389
+ if (payloadType === "turn.failed") {
390
+ if (didEmitCompleted) {
391
+ return [];
392
+ }
393
+ didEmitCompleted = true;
394
+ const errorMessage = isRecord(payload.error)
395
+ ? asString(payload.error.message)
396
+ : undefined;
397
+ return [
398
+ {
399
+ type: "completed",
400
+ engine: this.cliEngine,
401
+ ok: false,
402
+ answer: finalAnswer || undefined,
403
+ error: errorMessage ?? "Codex turn failed",
404
+ resume: threadId,
405
+ },
406
+ ];
407
+ }
408
+ if (payloadType === "error") {
409
+ const message = asString(payload.message) ?? "Codex stream error";
410
+ if (/reconnecting/i.test(message)) {
411
+ return [
412
+ {
413
+ type: "action",
414
+ engine: this.cliEngine,
415
+ phase: "updated",
416
+ entryType: "thought",
417
+ action: {
418
+ id: nextSyntheticId("codex-reconnect"),
419
+ kind: "warning",
420
+ title: "stream reconnect",
421
+ detail: { message },
422
+ },
423
+ message,
424
+ ok: true,
425
+ level: "warning",
426
+ },
427
+ ];
428
+ }
429
+ if (didEmitCompleted) {
430
+ return [
431
+ {
432
+ type: "action",
433
+ engine: this.cliEngine,
434
+ phase: "completed",
435
+ entryType: "thought",
436
+ action: {
437
+ id: nextSyntheticId("codex-error"),
438
+ kind: "warning",
439
+ title: "stream error",
440
+ detail: { message },
441
+ },
442
+ message,
443
+ ok: false,
444
+ level: "error",
445
+ },
446
+ ];
447
+ }
448
+ didEmitCompleted = true;
449
+ return [
450
+ {
451
+ type: "completed",
452
+ engine: this.cliEngine,
453
+ ok: false,
454
+ answer: finalAnswer || undefined,
455
+ error: message,
456
+ resume: threadId,
457
+ },
458
+ ];
459
+ }
460
+ return [];
461
+ };
462
+ return {
463
+ onStdoutLine: parseLine,
464
+ onStderrLine: (line) => {
465
+ const trimmedLine = line.trim();
466
+ if (!trimmedLine) {
467
+ return [];
468
+ }
469
+ return [
470
+ {
471
+ type: "action",
472
+ engine: this.cliEngine,
473
+ phase: "completed",
474
+ entryType: "thought",
475
+ action: {
476
+ id: nextSyntheticId("codex-stderr"),
477
+ kind: "warning",
478
+ title: "stderr",
479
+ detail: {},
480
+ },
481
+ message: truncate(trimmedLine, 220),
482
+ ok: true,
483
+ level: "warning",
484
+ },
485
+ ];
486
+ },
487
+ onExit: (result) => {
488
+ if (didEmitCompleted) {
489
+ return [];
490
+ }
491
+ const isSuccess = (result.exitCode ?? 0) === 0;
492
+ didEmitCompleted = true;
493
+ return [
494
+ {
495
+ type: "completed",
496
+ engine: this.cliEngine,
497
+ ok: isSuccess,
498
+ answer: finalAnswer || undefined,
499
+ error: isSuccess ? undefined : `Codex exited with code ${result.exitCode ?? -1}`,
500
+ resume: threadId,
501
+ },
502
+ ];
503
+ },
504
+ };
505
+ }
506
+ /**
507
+ * @param {{ prompt: string; systemPrompt?: string; cwd: string; options: any; }} params
508
+ */
509
+ async buildCommand(params) {
510
+ const resumeSession = typeof params.options?.resumeSession === "string"
511
+ ? params.options.resumeSession
512
+ : undefined;
513
+ const args = resumeSession ? ["exec", "resume", resumeSession] : ["exec"];
514
+ const yoloEnabled = this.opts.yolo ?? this.yolo;
515
+ const configOverrides = normalizeCodexConfig(this.opts.config);
516
+ for (const entry of configOverrides) {
517
+ args.push("-c", entry);
518
+ }
519
+ pushList(args, "--enable", this.opts.enable);
520
+ pushList(args, "--disable", this.opts.disable);
521
+ pushList(args, "--image", this.opts.image);
522
+ pushFlag(args, "--model", this.opts.model ?? this.model);
523
+ if (this.opts.oss)
524
+ args.push("--oss");
525
+ pushFlag(args, "--local-provider", this.opts.localProvider);
526
+ pushFlag(args, "--sandbox", this.opts.sandbox);
527
+ pushFlag(args, "--profile", this.opts.profile);
528
+ if (this.opts.fullAuto) {
529
+ args.push("--full-auto");
530
+ }
531
+ else if (yoloEnabled || this.opts.dangerouslyBypassApprovalsAndSandbox) {
532
+ args.push("--dangerously-bypass-approvals-and-sandbox");
533
+ }
534
+ pushFlag(args, "--cd", this.opts.cd);
535
+ if (this.opts.skipGitRepoCheck)
536
+ args.push("--skip-git-repo-check");
537
+ pushList(args, "--add-dir", this.opts.addDir);
538
+ if (!resumeSession) {
539
+ pushFlag(args, "--output-schema", this.opts.outputSchema);
540
+ }
541
+ pushFlag(args, "--color", this.opts.color);
542
+ // Always enable JSON output to capture JSONL events including
543
+ // turn.completed with token usage for metrics. extractUsageFromOutput
544
+ // in BaseCliAgent will parse these automatically.
545
+ args.push("--json");
546
+ // Auto-wire output schema from task context if not explicitly set.
547
+ // Skip when resuming — `codex exec resume` does not accept --output-schema.
548
+ let schemaCleanupFile = null;
549
+ if (!resumeSession && !this.opts.outputSchema && params.options?.outputSchema) {
550
+ const schema = params.options.outputSchema;
551
+ const { z } = await import("zod");
552
+ let jsonSchema = z.toJSONSchema(schema);
553
+ // Sanitize for OpenAI structured output compatibility
554
+ sanitizeForOpenAI(jsonSchema);
555
+ const schemaFile = join(tmpdir(), `smithers-schema-${randomUUID()}.json`);
556
+ await fs.writeFile(schemaFile, JSON.stringify(jsonSchema), "utf8");
557
+ pushFlag(args, "--output-schema", schemaFile);
558
+ schemaCleanupFile = schemaFile;
559
+ }
560
+ const outputFile = this.opts.outputLastMessage ??
561
+ join(tmpdir(), `smithers-codex-${randomUUID()}.txt`);
562
+ pushFlag(args, "--output-last-message", outputFile);
563
+ if (this.extraArgs?.length)
564
+ args.push(...this.extraArgs);
565
+ const systemPrefix = params.systemPrompt
566
+ ? `${params.systemPrompt}\n\n`
567
+ : "";
568
+ const fullPrompt = `${systemPrefix}${params.prompt ?? ""}`;
569
+ args.push("-");
570
+ return {
571
+ command: "codex",
572
+ args,
573
+ stdin: fullPrompt,
574
+ outputFile,
575
+ outputFormat: "stream-json",
576
+ stdoutBannerPatterns: [
577
+ // Codex CLI prints a startup banner like:
578
+ // "OpenAI Codex v0.99.0-alpha.13 (research preview)"
579
+ /^OpenAI Codex v[^\n]*$/gm,
580
+ ],
581
+ cleanup: async () => {
582
+ if (!this.opts.outputLastMessage) {
583
+ await fs.rm(outputFile, { force: true }).catch(() => undefined);
584
+ }
585
+ if (schemaCleanupFile) {
586
+ await fs
587
+ .rm(schemaCleanupFile, { force: true })
588
+ .catch(() => undefined);
589
+ }
590
+ },
591
+ };
592
+ }
593
+ }
@@ -0,0 +1,23 @@
1
+ import type { BaseCliAgentOptions } from "./BaseCliAgent/BaseCliAgentOptions";
2
+ import type { CodexConfigOverrides } from "./BaseCliAgent/CodexConfigOverrides";
3
+
4
+ export type CodexAgentOptions = BaseCliAgentOptions & {
5
+ config?: CodexConfigOverrides;
6
+ enable?: string[];
7
+ disable?: string[];
8
+ image?: string[];
9
+ model?: string;
10
+ oss?: boolean;
11
+ localProvider?: string;
12
+ sandbox?: "read-only" | "workspace-write" | "danger-full-access";
13
+ profile?: string;
14
+ fullAuto?: boolean;
15
+ dangerouslyBypassApprovalsAndSandbox?: boolean;
16
+ cd?: string;
17
+ skipGitRepoCheck?: boolean;
18
+ addDir?: string[];
19
+ outputSchema?: string;
20
+ color?: "always" | "never" | "auto";
21
+ json?: boolean;
22
+ outputLastMessage?: string;
23
+ };