@kal-elsam/kairo-runtime 0.2.1 → 0.2.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.
@@ -1,5 +1,6 @@
1
1
  import { stdin as input, stdout as output } from "node:process";
2
2
  import { canUseSetupInk } from "./terminal.js";
3
+ import { isActiveRunState, formatTaskLabel } from "../runtime/run-types.js";
3
4
 
4
5
  export function canUseOrchestratorShell({
5
6
  interactive = Boolean(input.isTTY && output.isTTY),
@@ -12,24 +13,137 @@ export function canUseOrchestratorShell({
12
13
 
13
14
  export const ORCHESTRATOR_VIEWS = {
14
15
  HOME: "home",
16
+ ACTIVE_RUNS: "active-runs",
17
+ RECENT_RUNS: "recent-runs",
18
+ RUN_DETAIL: "run-detail",
19
+ PROVIDERS: "providers",
20
+ LAUNCH: "launch",
15
21
  DIAGNOSTICS: "diagnostics",
16
- AGENTS: "agents",
17
- PROFILE: "profile",
18
- PLAN: "plan",
19
- CONFIRM: "confirm",
20
- HELP: "help",
21
- INTELLIGENCE: "intelligence"
22
+ HELP: "help"
22
23
  };
23
24
 
24
25
  export const ORCHESTRATOR_MENU = [
25
- { id: "status", label: "Diagnostics", view: ORCHESTRATOR_VIEWS.DIAGNOSTICS },
26
- { id: "agents", label: "Agents", view: ORCHESTRATOR_VIEWS.AGENTS },
27
- { id: "intelligence", label: "Intelligence", view: ORCHESTRATOR_VIEWS.INTELLIGENCE },
28
- { id: "profile", label: "Profile", view: ORCHESTRATOR_VIEWS.PROFILE },
29
- { id: "plan-setup", label: "Plan setup", view: ORCHESTRATOR_VIEWS.PLAN, action: "setup" },
26
+ { id: "active", label: "Active runs", view: ORCHESTRATOR_VIEWS.ACTIVE_RUNS },
27
+ { id: "recent", label: "Recent runs", view: ORCHESTRATOR_VIEWS.RECENT_RUNS },
28
+ { id: "providers", label: "Providers", view: ORCHESTRATOR_VIEWS.PROVIDERS },
29
+ { id: "launch", label: "Launch run", view: ORCHESTRATOR_VIEWS.LAUNCH, action: "launch" },
30
+ { id: "diagnostics", label: "Diagnostics", view: ORCHESTRATOR_VIEWS.DIAGNOSTICS },
30
31
  { id: "help", label: "Help", view: ORCHESTRATOR_VIEWS.HELP }
31
32
  ];
32
33
 
34
+ export const LAUNCH_WIZARD_STEPS = {
35
+ AGENT: "agent",
36
+ TASK: "task",
37
+ MODEL: "model",
38
+ PERMISSIONS: "permissions",
39
+ CONFIRM: "confirm"
40
+ };
41
+
42
+ export const LAUNCH_PERMISSION_OPTIONS = [
43
+ { id: "default", label: "Default (agent prompts)", permissions: [] },
44
+ { id: "force", label: "Force / auto-approve", permissions: ["force"] },
45
+ { id: "yolo", label: "YOLO / skip permissions", permissions: ["yolo"] }
46
+ ];
47
+
48
+ export function resolveLaunchableAgents(providers = []) {
49
+ return providers
50
+ .filter((provider) => provider.launchable)
51
+ .map((provider) => provider.id);
52
+ }
53
+
54
+ export function createLaunchDraft() {
55
+ return {
56
+ agentId: null,
57
+ task: "",
58
+ model: "",
59
+ permissionIndex: 0
60
+ };
61
+ }
62
+
63
+ export function resolveLaunchPermissions(draft) {
64
+ return LAUNCH_PERMISSION_OPTIONS[draft.permissionIndex]?.permissions ?? [];
65
+ }
66
+
67
+ export function advanceLaunchWizardStep(currentStep) {
68
+ switch (currentStep) {
69
+ case LAUNCH_WIZARD_STEPS.AGENT:
70
+ return LAUNCH_WIZARD_STEPS.TASK;
71
+ case LAUNCH_WIZARD_STEPS.TASK:
72
+ return LAUNCH_WIZARD_STEPS.MODEL;
73
+ case LAUNCH_WIZARD_STEPS.MODEL:
74
+ return LAUNCH_WIZARD_STEPS.PERMISSIONS;
75
+ case LAUNCH_WIZARD_STEPS.PERMISSIONS:
76
+ return LAUNCH_WIZARD_STEPS.CONFIRM;
77
+ default:
78
+ return LAUNCH_WIZARD_STEPS.CONFIRM;
79
+ }
80
+ }
81
+
82
+ export function retreatLaunchWizardStep(currentStep) {
83
+ switch (currentStep) {
84
+ case LAUNCH_WIZARD_STEPS.CONFIRM:
85
+ return LAUNCH_WIZARD_STEPS.PERMISSIONS;
86
+ case LAUNCH_WIZARD_STEPS.PERMISSIONS:
87
+ return LAUNCH_WIZARD_STEPS.MODEL;
88
+ case LAUNCH_WIZARD_STEPS.MODEL:
89
+ return LAUNCH_WIZARD_STEPS.TASK;
90
+ case LAUNCH_WIZARD_STEPS.TASK:
91
+ return LAUNCH_WIZARD_STEPS.AGENT;
92
+ default:
93
+ return LAUNCH_WIZARD_STEPS.AGENT;
94
+ }
95
+ }
96
+
97
+ export function formatLaunchWizardLines({
98
+ step,
99
+ draft,
100
+ launchableAgents,
101
+ agentIndex,
102
+ permissionIndex
103
+ }) {
104
+ const lines = [`Step: ${step}`];
105
+
106
+ if (step === LAUNCH_WIZARD_STEPS.AGENT) {
107
+ lines.push("Select agent:");
108
+ for (const [index, agentId] of launchableAgents.entries()) {
109
+ const marker = index === agentIndex ? "›" : " ";
110
+ lines.push(`${marker} ${agentId}`);
111
+ }
112
+ return lines;
113
+ }
114
+
115
+ if (step === LAUNCH_WIZARD_STEPS.TASK) {
116
+ lines.push(`Agent: ${draft.agentId ?? "—"}`);
117
+ lines.push(`Task: ${draft.task || "(type your task)"}`);
118
+ lines.push("Enter to continue · Backspace to edit");
119
+ return lines;
120
+ }
121
+
122
+ if (step === LAUNCH_WIZARD_STEPS.MODEL) {
123
+ lines.push(`Agent: ${draft.agentId ?? "—"}`);
124
+ lines.push(`Task length: ${draft.task.length} chars`);
125
+ lines.push(`Model: ${draft.model || "(default — press Enter)"}`);
126
+ lines.push("Type model alias or Enter for default");
127
+ return lines;
128
+ }
129
+
130
+ if (step === LAUNCH_WIZARD_STEPS.PERMISSIONS) {
131
+ lines.push("Permissions:");
132
+ for (const [index, option] of LAUNCH_PERMISSION_OPTIONS.entries()) {
133
+ const marker = index === permissionIndex ? "›" : " ";
134
+ lines.push(`${marker} ${option.label}`);
135
+ }
136
+ return lines;
137
+ }
138
+
139
+ lines.push(`Agent: ${draft.agentId ?? "—"}`);
140
+ lines.push(`Task length: ${draft.task.length} chars (content not stored)`);
141
+ lines.push(`Model: ${draft.model || "default"}`);
142
+ lines.push(`Permissions: ${LAUNCH_PERMISSION_OPTIONS[permissionIndex]?.label ?? "default"}`);
143
+ lines.push("Enter to launch · Esc to go back");
144
+ return lines;
145
+ }
146
+
33
147
  export function resolveMenuItem(menuIndex) {
34
148
  return ORCHESTRATOR_MENU[menuIndex] ?? null;
35
149
  }
@@ -43,6 +157,29 @@ export function shiftMenuIndex(currentIndex, direction, menuLength = ORCHESTRATO
43
157
  return Math.min(menuLength - 1, Math.max(0, currentIndex + delta));
44
158
  }
45
159
 
160
+ export function formatRunLines(runs, { emptyMessage = "No runs." } = {}) {
161
+ if (!runs || runs.length === 0) return [emptyMessage];
162
+
163
+ return runs.map((run) => {
164
+ const state = run.state.padEnd(12);
165
+ const agent = run.agentId.padEnd(10);
166
+ return `${run.runId} ${state} ${agent} ${formatTaskLabel(run)}`;
167
+ });
168
+ }
169
+
170
+ export function formatProviderLines(providers) {
171
+ return providers.map((provider) => {
172
+ const status = provider.launchable
173
+ ? "launchable"
174
+ : provider.compatible
175
+ ? "auditable"
176
+ : provider.available
177
+ ? "limited"
178
+ : "missing";
179
+ return `${provider.label.padEnd(14)} ${status.padEnd(12)} ${provider.reason ?? ""}`.trimEnd();
180
+ });
181
+ }
182
+
46
183
  export function formatDiagnosticsLines(diagnostics) {
47
184
  const summary = diagnostics?.diagnostics;
48
185
  const lines = [
@@ -53,9 +190,6 @@ export function formatDiagnosticsLines(diagnostics) {
53
190
  `Unknown: ${summary?.unknown ?? 0}`,
54
191
  `Errors: ${summary?.errors ?? 0}`,
55
192
  "",
56
- "Intelligence availability",
57
- ...formatIntelligenceLines(diagnostics),
58
- "",
59
193
  "Agent capabilities",
60
194
  ...formatAgentStatusLines(diagnostics?.capabilities ?? [])
61
195
  ];
@@ -79,71 +213,68 @@ export function formatAgentStatusLines(capabilities) {
79
213
  });
80
214
  }
81
215
 
82
- export function formatProfileLines(profileJson) {
216
+ export function formatRunDetailLines(run, events = []) {
217
+ if (!run) return ["Run not found."];
218
+
83
219
  const lines = [
84
- `Coordinator: ${profileJson.coordinator ?? "none"}`,
85
- `Default agents: ${formatAgentsLabel(profileJson.defaultAgents)}`,
86
- `Apply mode: ${profileJson.applyMode}`,
87
- `Preferred backend: ${profileJson.preferredBackend ?? "auto"}`,
88
- `Preferred model: ${profileJson.preferredModel ?? "auto"}`,
89
- `Cloud consent preference: ${profileJson.cloudConsent ? "recorded (session --cloud-consent still required)" : "no"}`,
90
- `Token budget: ${profileJson.tokenBudget ?? "none"}`
220
+ `Run: ${run.runId}`,
221
+ `Agent: ${run.agentId} (${run.provider})`,
222
+ `State: ${run.state}`,
223
+ `Model: ${run.model ?? "default"}`,
224
+ `Cwd: ${run.cwd}`,
225
+ `Started: ${run.startedAt}`,
226
+ `Updated: ${run.updatedAt}`
91
227
  ];
92
228
 
93
- if (profileJson.sources.global) {
94
- lines.push(`Global: ${profileJson.sources.global}`);
95
- }
229
+ if (run.completedAt) lines.push(`Completed: ${run.completedAt}`);
230
+ if (run.tokenUsage) lines.push(`Tokens: ${JSON.stringify(run.tokenUsage)}`);
231
+ if (run.error) lines.push(`Error: ${run.error}`);
232
+ if (run.tools?.length) lines.push(`Tools: ${run.tools.join(", ")}`);
96
233
 
97
- if (profileJson.sources.project) {
98
- lines.push(`Project: ${profileJson.sources.project}`);
234
+ if (events.length > 0) {
235
+ lines.push("", "Recent events");
236
+ for (const event of events.slice(-8)) {
237
+ if (event.parseError) continue;
238
+ lines.push(` ${event.type} ${summarizeEvent(event)}`);
239
+ }
99
240
  }
100
241
 
101
- lines.push(`Precedence: ${profileJson.sources.precedence}`);
102
242
  return lines;
103
243
  }
104
244
 
105
- export function formatIntelligenceLines(diagnostics) {
106
- const intelligence = diagnostics?.intelligence;
107
- if (!intelligence) {
108
- return ["Intelligence layer unavailable."];
109
- }
110
-
111
- const lines = [
112
- `Local available: ${intelligence.summary.localAvailable ? "yes" : "no"}`,
113
- `Cloud authenticated: ${intelligence.summary.cloudAuthenticated ? "yes" : "no"}`,
114
- `Routing: ${intelligence.routingPreview?.reason ?? "n/a"}`,
115
- `Can invoke: ${intelligence.routingPreview?.canInvoke ? "yes" : "no"}`,
116
- ""
245
+ export function formatDashboardSnapshot(dashboard) {
246
+ const active = dashboard?.activeRuns?.length ?? 0;
247
+ const recent = dashboard?.recentRuns?.length ?? 0;
248
+ const auditable = (dashboard?.providers ?? []).filter((entry) => entry.compatible).length;
249
+ return [
250
+ `Active runs: ${active}`,
251
+ `Recent runs: ${recent}`,
252
+ `Auditable providers: ${auditable}/${dashboard?.providers?.length ?? 0}`
117
253
  ];
118
-
119
- for (const backend of intelligence.backends ?? []) {
120
- lines.push(
121
- `${backend.label.padEnd(14)} ${backend.state.padEnd(14)} models=${backend.models?.length ?? 0}`
122
- );
123
- }
124
-
125
- return lines;
126
254
  }
127
255
 
128
- export function formatPlanLines(plan) {
129
- const lines = [`Action: ${plan.action}`, ""];
130
- for (const step of plan.steps) {
131
- lines.push(` • ${step}`);
132
- }
256
+ export function selectRunFromList(runs, index) {
257
+ return runs[index] ?? null;
258
+ }
133
259
 
134
- if (plan.warnings.length > 0) {
135
- lines.push("", "Warnings:");
136
- for (const warning of plan.warnings) {
137
- lines.push(` ! ${warning}`);
138
- }
139
- }
260
+ export function filterInspectableRuns(runs) {
261
+ return runs ?? [];
262
+ }
140
263
 
141
- return lines;
264
+ export function isRunCancellable(run) {
265
+ return run && isActiveRunState(run.state);
142
266
  }
143
267
 
144
- function formatAgentsLabel(agents) {
145
- if (agents === "detected") return "detected";
146
- if (agents === "all") return "all";
147
- if (Array.isArray(agents)) return agents.join(", ");
148
- return String(agents);
268
+ function summarizeEvent(event) {
269
+ if (event.type === "agent.tool_call") {
270
+ return event.data?.tool_name ?? event.data?.name ?? "tool";
271
+ }
272
+ if (event.type === "process.stdout" || event.type === "process.stderr") {
273
+ const line = event.data?.line ?? "";
274
+ return line.length > 60 ? `${line.slice(0, 59)}…` : line;
275
+ }
276
+ if (event.type === "run.completed" || event.type === "run.failed") {
277
+ return `exit=${event.data?.exitCode ?? "n/a"}`;
278
+ }
279
+ return "";
149
280
  }
@@ -1,11 +1,10 @@
1
1
  import { stdin as input, stdout as output } from "node:process";
2
2
  import { resolveHomeDir } from "./paths.js";
3
- import { runGlobalSetup } from "./global-cli.js";
4
- import { PLAN_ACTIONS, buildReadOnlyDiagnostics, shouldExecutePlan } from "./action-planner.js";
5
3
  import { canUseOrchestratorShell } from "./ink/orchestrator-state.js";
6
4
  import { runOrchestratorInk as defaultRunOrchestratorInk } from "./ink/run-orchestrator-ink.js";
7
5
  import { formatCliCommand } from "./brand/cli.js";
8
6
  import { BRAND } from "./brand/index.js";
7
+ import { buildReadOnlyDiagnostics, shouldExecutePlan } from "./action-planner.js";
9
8
 
10
9
  export { canUseOrchestratorShell };
11
10
 
@@ -28,13 +27,13 @@ export async function runOrchestratorShell({
28
27
  }) {
29
28
  if (!interactive) {
30
29
  throw new Error(
31
- `Non-interactive shell requires an explicit command. Try ${formatCliCommand("help")} or ${formatCliCommand("setup --yes")}.`
30
+ `Non-interactive shell requires an explicit command. Try ${formatCliCommand("help")} or ${formatCliCommand("runs list")}.`
32
31
  );
33
32
  }
34
33
 
35
34
  if (!canUseOrchestratorShell({ interactive })) {
36
35
  throw new Error(
37
- `Interactive shell requires a capable TTY. Use ${formatCliCommand("setup --simple")} or explicit commands.`
36
+ `Interactive shell requires a capable TTY. Use ${formatCliCommand("runs list")} or explicit commands.`
38
37
  );
39
38
  }
40
39
 
@@ -51,41 +50,11 @@ export async function runOrchestratorShell({
51
50
  throw outcome.error;
52
51
  }
53
52
 
54
- if (outcome.cancelled) {
55
- return { cancelled: true, wrote: false };
56
- }
57
-
58
- if (outcome.confirmed && outcome.action === PLAN_ACTIONS.SETUP) {
59
- const setupOutcome = await runGlobalSetup(
60
- {
61
- cwd: workspaceRoot,
62
- adapters: null,
63
- components: null,
64
- noDefaultComponents: false,
65
- dryRun: false,
66
- yes: false,
67
- confirm: false,
68
- preflight: true,
69
- preflightExplicit: false,
70
- yesExplicit: false,
71
- confirmExplicit: false,
72
- json: false,
73
- interactive: true,
74
- simple: false
75
- },
76
- packageManifest,
77
- packageRoot
78
- );
79
-
80
- return {
81
- cancelled: Boolean(setupOutcome.cancelled),
82
- wrote: !setupOutcome.cancelled && !setupOutcome.result?.dryRun,
83
- action: PLAN_ACTIONS.SETUP,
84
- setupOutcome
85
- };
86
- }
87
-
88
- return { cancelled: false, wrote: false, action: outcome.action ?? null };
53
+ return {
54
+ cancelled: Boolean(outcome.cancelled),
55
+ wrote: false,
56
+ action: outcome.action ?? null
57
+ };
89
58
  }
90
59
 
91
60
  export async function runOrchestratorDiagnostics({
@@ -17,7 +17,20 @@ export function harnessHomePaths(homeDir) {
17
17
  policyPath: join(root, "policy.json"),
18
18
  profilePath: join(root, "profile.json"),
19
19
  historyPath: join(root, "history.jsonl"),
20
+ runsDir: join(root, "runs"),
20
21
  coreDir: join(root, "core"),
21
22
  backupsDir: join(root, "backups")
22
23
  };
23
24
  }
25
+
26
+ export function runPaths(homeDir, runId) {
27
+ const { runsDir } = harnessHomePaths(homeDir);
28
+ const runDir = join(runsDir, runId);
29
+
30
+ return {
31
+ runDir,
32
+ statePath: join(runDir, "state.json"),
33
+ eventsPath: join(runDir, "events.jsonl"),
34
+ transcriptPath: join(runDir, "transcript.jsonl")
35
+ };
36
+ }
@@ -4,6 +4,7 @@ import { join } from "node:path";
4
4
  import { harnessHomePaths } from "./paths.js";
5
5
  import { AGENT_CAPABILITY_IDS } from "./agent-capabilities/index.js";
6
6
  import { classifyCustomBaseUrl, isValidEnvironmentName } from "./intelligence/custom-url.js";
7
+ import { validateRuntimeProfile, RUNTIME_PROFILE_KEYS } from "./runtime/run-profile.js";
7
8
 
8
9
  export const PROFILE_KEYS = new Set([
9
10
  "coordinator",
@@ -16,7 +17,8 @@ export const PROFILE_KEYS = new Set([
16
17
  "tokenBudget",
17
18
  "stableContextBudget",
18
19
  "requestContextBudget",
19
- "customProviders"
20
+ "customProviders",
21
+ ...RUNTIME_PROFILE_KEYS
20
22
  ]);
21
23
 
22
24
  export const DEFAULT_PROFILE = {
@@ -30,7 +32,13 @@ export const DEFAULT_PROFILE = {
30
32
  tokenBudget: null,
31
33
  stableContextBudget: null,
32
34
  requestContextBudget: null,
33
- customProviders: []
35
+ customProviders: [],
36
+ agentAliases: {},
37
+ modelAliases: {},
38
+ defaultPermissions: [],
39
+ defaultRuntimeAgent: null,
40
+ defaultRuntimeModel: null,
41
+ captureTranscript: false
34
42
  };
35
43
 
36
44
  const APPLY_MODES = new Set(["prompt", "confirm"]);
@@ -105,6 +113,12 @@ export function buildProfileJson(resolved) {
105
113
  stableContextBudget: profile.stableContextBudget,
106
114
  requestContextBudget: profile.requestContextBudget,
107
115
  customProviders: sanitizeCustomProviders(profile.customProviders),
116
+ agentAliases: profile.agentAliases ?? {},
117
+ modelAliases: profile.modelAliases ?? {},
118
+ defaultPermissions: profile.defaultPermissions ?? [],
119
+ defaultRuntimeAgent: profile.defaultRuntimeAgent ?? null,
120
+ defaultRuntimeModel: profile.defaultRuntimeModel ?? null,
121
+ captureTranscript: profile.captureTranscript ?? false,
108
122
  sources: {
109
123
  global: sources.global,
110
124
  project: sources.project,
@@ -187,6 +201,7 @@ function validateProfile(profile) {
187
201
 
188
202
  validateNoSecrets(profile);
189
203
  validateCustomProviders(profile.customProviders);
204
+ validateRuntimeProfile(profile);
190
205
  }
191
206
 
192
207
  /**
@@ -0,0 +1,65 @@
1
+ import { createExecutionAdapter, parseNdjsonLine, buildPermissionsArgs } from "./create-execution-adapter.js";
2
+
3
+ const EXECUTABLE = "claude";
4
+
5
+ function buildClaudeLaunch({ task, cwd, model, permissions = [] }) {
6
+ const args = [
7
+ "-p",
8
+ "--output-format",
9
+ "stream-json",
10
+ ...buildPermissionsArgs(permissions),
11
+ task
12
+ ];
13
+
14
+ if (model) {
15
+ args.unshift("--model", model);
16
+ }
17
+
18
+ return {
19
+ command: EXECUTABLE,
20
+ args,
21
+ cwd,
22
+ env: process.env
23
+ };
24
+ }
25
+
26
+ function parseClaudeEventLine(line) {
27
+ const parsed = parseNdjsonLine(line);
28
+ if (!parsed || typeof parsed !== "object") return null;
29
+
30
+ if (parsed.type === "tool_use" || parsed.type === "tool_call") {
31
+ return {
32
+ type: "tool_call",
33
+ tool_name: parsed.name ?? parsed.tool ?? "unknown",
34
+ status: parsed.status ?? "started"
35
+ };
36
+ }
37
+
38
+ if (parsed.type === "usage" || parsed.usage) {
39
+ const usage = parsed.usage ?? parsed;
40
+ return {
41
+ type: "usage",
42
+ inputTokens: usage.input_tokens ?? usage.input ?? null,
43
+ outputTokens: usage.output_tokens ?? usage.output ?? null,
44
+ totalTokens: usage.total_tokens ?? usage.total ?? null,
45
+ cost: usage.cost ?? null
46
+ };
47
+ }
48
+
49
+ return parsed;
50
+ }
51
+
52
+ export default createExecutionAdapter({
53
+ id: "claude",
54
+ label: "Claude Code",
55
+ executable: EXECUTABLE,
56
+ capabilities: {
57
+ structuredEvents: true,
58
+ tokens: true,
59
+ diff: false,
60
+ cancel: true,
61
+ transcript: true
62
+ },
63
+ buildLaunch: buildClaudeLaunch,
64
+ parseEventLine: parseClaudeEventLine
65
+ });
@@ -0,0 +1,78 @@
1
+ import { createExecutionAdapter, parseNdjsonLine } from "./create-execution-adapter.js";
2
+
3
+ const EXECUTABLE = "codex";
4
+
5
+ function buildCodexPermissionsArgs(permissions = []) {
6
+ const normalized = new Set(permissions.map((entry) => String(entry).toLowerCase()));
7
+
8
+ if (
9
+ normalized.has("yolo")
10
+ || normalized.has("dangerously-skip-permissions")
11
+ || normalized.has("dangerously-bypass-approvals-and-sandbox")
12
+ ) {
13
+ return ["--dangerously-bypass-approvals-and-sandbox"];
14
+ }
15
+
16
+ return [];
17
+ }
18
+
19
+ function buildCodexLaunch({ task, cwd, model, permissions = [] }) {
20
+ const args = [
21
+ "exec",
22
+ "--json",
23
+ ...buildCodexPermissionsArgs(permissions),
24
+ task
25
+ ];
26
+
27
+ if (model) {
28
+ args.unshift("--model", model);
29
+ }
30
+
31
+ return {
32
+ command: EXECUTABLE,
33
+ args,
34
+ cwd,
35
+ env: process.env
36
+ };
37
+ }
38
+
39
+ function parseCodexEventLine(line) {
40
+ const parsed = parseNdjsonLine(line);
41
+ if (!parsed || typeof parsed !== "object") return null;
42
+
43
+ if (parsed.type === "tool" || parsed.type === "tool_call") {
44
+ return {
45
+ type: "tool_call",
46
+ tool_name: parsed.tool ?? parsed.name ?? "unknown",
47
+ status: parsed.status ?? "started"
48
+ };
49
+ }
50
+
51
+ if (parsed.usage || parsed.token_usage) {
52
+ const usage = parsed.usage ?? parsed.token_usage;
53
+ return {
54
+ type: "usage",
55
+ inputTokens: usage.input_tokens ?? usage.input ?? null,
56
+ outputTokens: usage.output_tokens ?? usage.output ?? null,
57
+ totalTokens: usage.total_tokens ?? usage.total ?? null,
58
+ cost: usage.cost ?? null
59
+ };
60
+ }
61
+
62
+ return parsed;
63
+ }
64
+
65
+ export default createExecutionAdapter({
66
+ id: "codex",
67
+ label: "Codex",
68
+ executable: EXECUTABLE,
69
+ capabilities: {
70
+ structuredEvents: true,
71
+ tokens: true,
72
+ diff: false,
73
+ cancel: true,
74
+ transcript: true
75
+ },
76
+ buildLaunch: buildCodexLaunch,
77
+ parseEventLine: parseCodexEventLine
78
+ });