@mono-agent/agent-runtime 0.20.10 → 0.20.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +264 -25
- package/README.md +139 -448
- package/package.json +7 -43
- package/src/agent/tools/agent-tool.js +5 -5
- package/src/agent/tools/bash.js +1 -24
- package/src/agent/tools/codex-subscription-search.js +1 -1
- package/src/agent/tools/index.js +7 -0
- package/src/agent/tools/monitor.js +140 -0
- package/src/agent/tools/pi-bridge.js +92 -7
- package/src/agent/tools/shared/bash-environment.js +31 -0
- package/src/agent/tools/shared/monitors.js +274 -0
- package/src/agent/tools/shared/process-runner.js +23 -5
- package/src/ai/cost.js +13 -68
- package/src/ai/index.js +4 -17
- package/src/ai/pi-interop.js +65 -1
- package/src/ai/pi-oauth-compat.js +1 -1
- package/src/ai/providers/codex/app-server-client.js +592 -0
- package/src/ai/providers/pi-models.js +18 -10
- package/src/ai/providers/pi-native/compaction-driver.js +49 -21
- package/src/ai/providers/pi-native/harness-adapter.js +338 -0
- package/src/ai/providers/pi-native/result-builder.js +10 -10
- package/src/ai/providers/pi-native/session-lifecycle.js +95 -40
- package/src/ai/providers/pi-native/stream-subscriber.js +23 -5
- package/src/ai/providers/pi-native/turn-runner.js +34 -15
- package/src/ai/providers/pi-native.js +48 -22
- package/src/ai/runtime/capabilities.js +11 -56
- package/src/ai/runtime/model-refs.js +118 -153
- package/src/ai/runtime/registry.js +22 -56
- package/src/ai/runtime/router.js +48 -408
- package/src/ai/runtime/session-liveness.js +3 -4
- package/src/ai/runtime/sessions.js +4 -5
- package/src/ai/runtime/tool-policy.js +0 -2
- package/src/ai/types.js +12 -108
- package/src/index.js +0 -6
- package/src/runtime.js +8 -14
- package/types/agent/tools/agent-tool.d.ts +1 -2
- package/types/agent/tools/codex-subscription-search.d.ts +1 -1
- package/types/agent/tools/index.d.ts +1 -0
- package/types/agent/tools/monitor.d.ts +44 -0
- package/types/agent/tools/pi-bridge.d.ts +3 -2
- package/types/agent/tools/shared/bash-environment.d.ts +4 -0
- package/types/agent/tools/shared/monitors.d.ts +83 -0
- package/types/agent/tools/shared/process-runner.d.ts +11 -2
- package/types/ai/cost.d.ts +1 -2
- package/types/ai/index.d.ts +1 -4
- package/types/ai/pi-interop.d.ts +20 -0
- package/types/ai/providers/codex/app-server-client.d.ts +37 -0
- package/types/ai/providers/pi-native/harness-adapter.d.ts +56 -0
- package/types/ai/providers/pi-native/result-builder.d.ts +3 -3
- package/types/ai/providers/pi-native/session-lifecycle.d.ts +2 -1
- package/types/ai/providers/pi-native/stream-subscriber.d.ts +2 -2
- package/types/ai/providers/pi-native/turn-runner.d.ts +33 -6
- package/types/ai/providers/pi-native.d.ts +21 -4
- package/types/ai/runtime/capabilities.d.ts +21 -70
- package/types/ai/runtime/model-refs.d.ts +0 -24
- package/types/ai/runtime/router.d.ts +3 -10
- package/types/ai/runtime/tool-policy.d.ts +0 -2
- package/types/ai/types.d.ts +28 -250
- package/types/index.d.ts +0 -1
- package/src/ai/providers/acp-client.js +0 -1149
- package/src/ai/providers/acp-privacy.js +0 -124
- package/src/ai/providers/acp-public.js +0 -21
- package/src/ai/providers/acp-session-tokens.js +0 -282
- package/src/ai/providers/acp-transport.js +0 -356
- package/src/ai/providers/acp.js +0 -543
- package/src/ai/providers/claude-cli.js +0 -883
- package/src/ai/providers/claude-sandbox.js +0 -71
- package/src/ai/providers/claude-sdk-discovery-worker.js +0 -53
- package/src/ai/providers/claude-sdk-discovery.js +0 -352
- package/src/ai/providers/claude-sdk.js +0 -1127
- package/src/ai/providers/claude-subagent-activity.js +0 -719
- package/src/ai/providers/claude-subagents.js +0 -88
- package/src/ai/providers/codex-app.js +0 -2946
- package/src/ai/providers/opencode-app.js +0 -1109
- package/src/ai/providers/opencode-discovery.js +0 -39
- package/src/ai/providers/opencode-server.js +0 -508
- package/src/ai/runtime/context-windows.js +0 -46
- package/src/ai/runtime/fast-mode.js +0 -8
- package/src/ai/streaming/codex-events.js +0 -146
- package/src/ai/streaming/opencode-events.js +0 -59
- package/types/ai/providers/acp-client.d.ts +0 -227
- package/types/ai/providers/acp-privacy.d.ts +0 -25
- package/types/ai/providers/acp-public.d.ts +0 -7
- package/types/ai/providers/acp-session-tokens.d.ts +0 -41
- package/types/ai/providers/acp-transport.d.ts +0 -45
- package/types/ai/providers/acp.d.ts +0 -93
- package/types/ai/providers/claude-cli.d.ts +0 -305
- package/types/ai/providers/claude-sandbox.d.ts +0 -79
- package/types/ai/providers/claude-sdk-discovery-worker.d.ts +0 -1
- package/types/ai/providers/claude-sdk-discovery.d.ts +0 -97
- package/types/ai/providers/claude-sdk.d.ts +0 -138
- package/types/ai/providers/claude-subagent-activity.d.ts +0 -53
- package/types/ai/providers/claude-subagents.d.ts +0 -18
- package/types/ai/providers/codex-app.d.ts +0 -151
- package/types/ai/providers/opencode-app.d.ts +0 -96
- package/types/ai/providers/opencode-discovery.d.ts +0 -4
- package/types/ai/providers/opencode-server.d.ts +0 -20
- package/types/ai/runtime/context-windows.d.ts +0 -9
- package/types/ai/runtime/fast-mode.d.ts +0 -2
- package/types/ai/streaming/codex-events.d.ts +0 -40
- package/types/ai/streaming/opencode-events.d.ts +0 -42
|
@@ -1,883 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { createInterface } from "node:readline";
|
|
6
|
-
import { getSkillAccessDirs } from "../../agent/prompt/skill-index.js";
|
|
7
|
-
import { normalizeCodexItemEvent } from "../streaming/codex-events.js";
|
|
8
|
-
import { createFileChangePayload } from "../file-change-stats.js";
|
|
9
|
-
import { estimateCost } from "../cost.js";
|
|
10
|
-
import { createStderrTail } from "../failure.js";
|
|
11
|
-
import { modelWithContextWindow } from "../runtime/context-windows.js";
|
|
12
|
-
import { readRuntimeBrand } from "../../agent/tools/shared/runtime-context.js";
|
|
13
|
-
import { buildCapabilitiesUsed } from "../runtime/capabilities-used.js";
|
|
14
|
-
import {
|
|
15
|
-
isAllowAllToolPolicy,
|
|
16
|
-
TOOL_POLICY_PROJECTED,
|
|
17
|
-
} from "../runtime/tool-policy.js";
|
|
18
|
-
import {
|
|
19
|
-
claudeNativeAgentDefinitions,
|
|
20
|
-
resolveClaudeAllowedTools,
|
|
21
|
-
} from "./claude-subagents.js";
|
|
22
|
-
import {
|
|
23
|
-
claudeCapabilityMismatchResult,
|
|
24
|
-
claudeSandboxCapabilityMismatchResult,
|
|
25
|
-
claudeSandboxPolicyProblem,
|
|
26
|
-
} from "./claude-sandbox.js";
|
|
27
|
-
import { resolveSandboxPolicy } from "../../agent/tools/shared/tool-context.js";
|
|
28
|
-
import { createClaudeSubagentActivityNormalizer } from "./claude-subagent-activity.js";
|
|
29
|
-
import { toolLifecycleMetadata } from "../tool-lifecycle.js";
|
|
30
|
-
|
|
31
|
-
const CODEX_CLI_SANDBOX_POLICY_UNSUPPORTED =
|
|
32
|
-
"Direct Codex CLI cannot enforce mono-agent's native srt sandbox scopes. Remove the mono-agent sandbox policy or use a Pi runtime for exact readableRoots, writableRoots, denyWrite, and network rules.";
|
|
33
|
-
const CLAUDE_CLI_EMPTY_TOOL_POLICY_UNSUPPORTED =
|
|
34
|
-
"Claude Code CLI cannot enforce an explicit empty allowedTools list: omitting --tools would restore Claude Code's default toolset. Use a specific non-empty allowlist, a denylist, or the Claude SDK for a no-tools run.";
|
|
35
|
-
|
|
36
|
-
function codexCliToolPolicyProblem(options) {
|
|
37
|
-
return isAllowAllToolPolicy(options.allowedTools, options.disallowedTools)
|
|
38
|
-
? null
|
|
39
|
-
: "Direct Codex CLI cannot enforce allowedTools/disallowedTools. Use allow-all-only (omit allowedTools or include \"*\", with no disallowedTools) or another runtime.";
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function codexCliCapabilityMismatchResult(options, error, codexErrorCode, start) {
|
|
43
|
-
const resolved = options.model;
|
|
44
|
-
const providerSessionId = (typeof options.sessionId === "string" && options.sessionId.trim())
|
|
45
|
-
|| (typeof options.providerSessionId === "string" && options.providerSessionId.trim())
|
|
46
|
-
|| null;
|
|
47
|
-
return {
|
|
48
|
-
text: null,
|
|
49
|
-
structuredResult: undefined,
|
|
50
|
-
structuredResultSource: null,
|
|
51
|
-
events: [],
|
|
52
|
-
usage: {},
|
|
53
|
-
durationMs: Date.now() - start,
|
|
54
|
-
numTurns: 0,
|
|
55
|
-
model: resolved?.reference || `codex:${resolved?.model || ""}`,
|
|
56
|
-
effort: options.effort || null,
|
|
57
|
-
sdk: "codex",
|
|
58
|
-
providerSessionId,
|
|
59
|
-
provider_session_id: providerSessionId,
|
|
60
|
-
cancelled: false,
|
|
61
|
-
error,
|
|
62
|
-
failureKind: "skipped_capability_mismatch",
|
|
63
|
-
diagnostics: { codex_error_code: codexErrorCode },
|
|
64
|
-
capabilitiesUsed: buildCapabilitiesUsed({
|
|
65
|
-
promptCacheActive: null,
|
|
66
|
-
thinkingEnabled: null,
|
|
67
|
-
structuredOutputEnforced: !!options.outputSchema,
|
|
68
|
-
subagentInvoked: null,
|
|
69
|
-
mcpServersUsed: [],
|
|
70
|
-
nativeSubagentsUsed: [],
|
|
71
|
-
toolCompactionApplied: false,
|
|
72
|
-
contextCompactionApplied: null,
|
|
73
|
-
}),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const DORMANT_CLI_CAPABILITIES = {
|
|
78
|
-
streaming: true,
|
|
79
|
-
structured_output: true,
|
|
80
|
-
// intelligence-ramp Phase 5.1: claude-cli supports resume via `--resume` and
|
|
81
|
-
// surfaces session_id from init/result events; the bridge wraps the env-var
|
|
82
|
-
// hand-off the coordinator already populates on continuations.
|
|
83
|
-
supports_session_resume: true,
|
|
84
|
-
native_runtime_config: null,
|
|
85
|
-
supports_mcp: true,
|
|
86
|
-
supports_skills: true,
|
|
87
|
-
supports_builtin_tools: true,
|
|
88
|
-
// The one-shot CLI bridge cannot add stdin messages after process launch.
|
|
89
|
-
supports_live_input: false,
|
|
90
|
-
supports_native_subagents: true,
|
|
91
|
-
tool_policy: TOOL_POLICY_PROJECTED,
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
function promptFromMessages(messages) {
|
|
95
|
-
return Array.isArray(messages)
|
|
96
|
-
? messages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n\n")
|
|
97
|
-
: String(messages || "");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const CODEX_REASONING_ITEM_EVENTS = new Set(["item.started", "item.updated", "item.completed"]);
|
|
101
|
-
const CODEX_REASONING_EVENT_TYPES = new Set([
|
|
102
|
-
"agent_reasoning",
|
|
103
|
-
"agent_reasoning_delta",
|
|
104
|
-
"reasoning_content_delta",
|
|
105
|
-
"reasoning_summary_part_added",
|
|
106
|
-
"reasoning_summary_text_delta",
|
|
107
|
-
]);
|
|
108
|
-
const CODEX_RAW_REASONING_EVENT_TYPES = new Set([
|
|
109
|
-
"agent_reasoning_raw_content",
|
|
110
|
-
"agent_reasoning_raw_content_delta",
|
|
111
|
-
"reasoning_raw_content",
|
|
112
|
-
"reasoning_raw_content_delta",
|
|
113
|
-
]);
|
|
114
|
-
|
|
115
|
-
function summaryTextFromValue(value) {
|
|
116
|
-
if (typeof value === "string") return value;
|
|
117
|
-
if (Array.isArray(value)) return value.map(summaryTextFromValue).filter(Boolean).join("");
|
|
118
|
-
if (!value || typeof value !== "object") return "";
|
|
119
|
-
if (value.type && !["summary_text", "reasoning_summary_text"].includes(value.type)) return "";
|
|
120
|
-
return summaryTextFromValue(value.text ?? value.delta ?? value.summary ?? value.content);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function codexReasoningSummaryText(raw) {
|
|
124
|
-
const item = raw?.item || {};
|
|
125
|
-
return [
|
|
126
|
-
raw?.delta,
|
|
127
|
-
raw?.text,
|
|
128
|
-
raw?.summary,
|
|
129
|
-
raw?.content,
|
|
130
|
-
item.delta,
|
|
131
|
-
item.text,
|
|
132
|
-
item.summary,
|
|
133
|
-
item.summaries,
|
|
134
|
-
].map(summaryTextFromValue).find((text) => text.trim()) || "";
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function isCodexReasoningEvent(raw) {
|
|
138
|
-
if (!raw || typeof raw !== "object") return false;
|
|
139
|
-
return CODEX_REASONING_EVENT_TYPES.has(raw.type)
|
|
140
|
-
|| CODEX_RAW_REASONING_EVENT_TYPES.has(raw.type)
|
|
141
|
-
|| (CODEX_REASONING_ITEM_EVENTS.has(raw.type) && raw.item?.type === "reasoning");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const ANTHROPIC_STREAM_EVENT_TYPES = new Set([
|
|
145
|
-
"message_start",
|
|
146
|
-
"message_delta",
|
|
147
|
-
"message_stop",
|
|
148
|
-
"content_block_start",
|
|
149
|
-
"content_block_delta",
|
|
150
|
-
"content_block_stop",
|
|
151
|
-
"ping",
|
|
152
|
-
]);
|
|
153
|
-
|
|
154
|
-
// Claude Code CLI emits one `assistant` event per finalised content block in
|
|
155
|
-
// stream-json mode, but for `thinking` blocks the text is only ever sent via
|
|
156
|
-
// `thinking_delta` chunks under `--include-partial-messages`. The finalised
|
|
157
|
-
// block carries the signature but `thinking: ""`. The buffer accumulates the
|
|
158
|
-
// streamed thinking deltas and splices them back into the finalised assistant
|
|
159
|
-
// event before it is forwarded to the host.
|
|
160
|
-
export function createThinkingBuffer() {
|
|
161
|
-
let currentMessageId = null;
|
|
162
|
-
const byMessage = new Map();
|
|
163
|
-
|
|
164
|
-
function unwrap(raw) {
|
|
165
|
-
return raw?.type === "stream_event" && raw.event ? raw.event : raw;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function isStreamShape(raw) {
|
|
169
|
-
if (!raw || typeof raw !== "object") return false;
|
|
170
|
-
if (raw.type === "stream_event") return true;
|
|
171
|
-
return ANTHROPIC_STREAM_EVENT_TYPES.has(raw.type);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function bufferFor(messageId) {
|
|
175
|
-
if (!messageId) return null;
|
|
176
|
-
let bucket = byMessage.get(messageId);
|
|
177
|
-
if (!bucket) {
|
|
178
|
-
bucket = new Map();
|
|
179
|
-
byMessage.set(messageId, bucket);
|
|
180
|
-
}
|
|
181
|
-
return bucket;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function onStreamEvent(raw) {
|
|
185
|
-
const inner = unwrap(raw);
|
|
186
|
-
if (!inner || typeof inner !== "object") return;
|
|
187
|
-
|
|
188
|
-
if (inner.type === "message_start") {
|
|
189
|
-
currentMessageId = inner.message?.id || null;
|
|
190
|
-
// Defensive: drop any stale state if the same id reappears.
|
|
191
|
-
if (currentMessageId) byMessage.delete(currentMessageId);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (inner.type === "content_block_start") {
|
|
196
|
-
if (inner.content_block?.type !== "thinking") return;
|
|
197
|
-
const bucket = bufferFor(currentMessageId);
|
|
198
|
-
if (bucket) bucket.set(inner.index, { text: "", consumed: false });
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (inner.type === "content_block_delta") {
|
|
203
|
-
const bucket = bufferFor(currentMessageId);
|
|
204
|
-
if (!bucket) return;
|
|
205
|
-
const entry = bucket.get(inner.index);
|
|
206
|
-
if (!entry) return;
|
|
207
|
-
if (inner.delta?.type === "thinking_delta" && typeof inner.delta.thinking === "string") {
|
|
208
|
-
entry.text += inner.delta.thinking;
|
|
209
|
-
}
|
|
210
|
-
// signature_delta is ignored: the signature already lands on the
|
|
211
|
-
// finalised assistant event we will rehydrate.
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function rehydrate(assistantRaw) {
|
|
216
|
-
if (!assistantRaw || typeof assistantRaw !== "object") return assistantRaw;
|
|
217
|
-
const messageId = assistantRaw.message?.id;
|
|
218
|
-
const content = assistantRaw.message?.content;
|
|
219
|
-
if (!messageId || !Array.isArray(content)) return assistantRaw;
|
|
220
|
-
const bucket = byMessage.get(messageId);
|
|
221
|
-
if (!bucket || bucket.size === 0) return assistantRaw;
|
|
222
|
-
|
|
223
|
-
const pending = [...bucket.entries()]
|
|
224
|
-
.filter(([, entry]) => !entry.consumed && entry.text)
|
|
225
|
-
.sort(([a], [b]) => (Number(a) || 0) - (Number(b) || 0));
|
|
226
|
-
if (pending.length === 0) return assistantRaw;
|
|
227
|
-
|
|
228
|
-
let mutated = null;
|
|
229
|
-
let cursor = 0;
|
|
230
|
-
for (let i = 0; i < content.length; i++) {
|
|
231
|
-
const block = content[i];
|
|
232
|
-
if (!block || block.type !== "thinking" || block.thinking) continue;
|
|
233
|
-
if (cursor >= pending.length) break;
|
|
234
|
-
const [, entry] = pending[cursor++];
|
|
235
|
-
entry.consumed = true;
|
|
236
|
-
if (!mutated) {
|
|
237
|
-
mutated = { ...assistantRaw, message: { ...assistantRaw.message, content: [...content] } };
|
|
238
|
-
}
|
|
239
|
-
mutated.message.content[i] = { ...block, thinking: entry.text };
|
|
240
|
-
}
|
|
241
|
-
return mutated || assistantRaw;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return { isStreamShape, onStreamEvent, rehydrate };
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export function normalizeCliEvent(raw, context = {}) {
|
|
248
|
-
if (!raw || typeof raw !== "object") return { type: "cli_event", raw };
|
|
249
|
-
const thinkingBuffer = context.thinkingBuffer;
|
|
250
|
-
if (thinkingBuffer && thinkingBuffer.isStreamShape(raw)) {
|
|
251
|
-
thinkingBuffer.onStreamEvent(raw);
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
|
-
if (CODEX_RAW_REASONING_EVENT_TYPES.has(raw.type)) return null;
|
|
255
|
-
if (CODEX_REASONING_EVENT_TYPES.has(raw.type) || (CODEX_REASONING_ITEM_EVENTS.has(raw.type) && raw.item?.type === "reasoning")) {
|
|
256
|
-
const text = codexReasoningSummaryText(raw).trim();
|
|
257
|
-
return text
|
|
258
|
-
? { type: "assistant", message: { content: [{ type: "thinking", text }] } }
|
|
259
|
-
: null;
|
|
260
|
-
}
|
|
261
|
-
if (raw.type === "assistant") {
|
|
262
|
-
return thinkingBuffer ? thinkingBuffer.rehydrate(raw) : raw;
|
|
263
|
-
}
|
|
264
|
-
if (raw.type === "user") return annotateClaudeCliToolResults(raw);
|
|
265
|
-
if (raw.type === "result" || raw.type === "error") return raw;
|
|
266
|
-
if (raw.type === "message" && raw.message) return { type: "assistant", message: raw.message };
|
|
267
|
-
if (raw.type === "item.completed" && raw.item?.type === "agent_message" && typeof raw.item.text === "string") {
|
|
268
|
-
return { type: "assistant", message: { content: [{ type: "text", text: raw.item.text }] } };
|
|
269
|
-
}
|
|
270
|
-
const codexItem = normalizeCodexItemEvent(raw, {
|
|
271
|
-
fileChangePayload: (event) => createFileChangePayload(event, {
|
|
272
|
-
cwd: context.cwd || process.cwd(),
|
|
273
|
-
snapshots: context.fileChangeSnapshots || new Map(),
|
|
274
|
-
}),
|
|
275
|
-
});
|
|
276
|
-
if (codexItem) return codexItem;
|
|
277
|
-
if (raw.type === "tool_call") {
|
|
278
|
-
return { type: "assistant", message: { content: [{ type: "tool_use", id: raw.id, name: raw.name, input: raw.input || raw.arguments }] } };
|
|
279
|
-
}
|
|
280
|
-
if (raw.type === "tool_result") {
|
|
281
|
-
const isError = raw.is_error === true;
|
|
282
|
-
return {
|
|
283
|
-
type: "user",
|
|
284
|
-
message: {
|
|
285
|
-
content: [{
|
|
286
|
-
type: "tool_result",
|
|
287
|
-
tool_use_id: raw.id || raw.tool_use_id,
|
|
288
|
-
content: raw.output || raw.result || "",
|
|
289
|
-
is_error: isError,
|
|
290
|
-
tool_lifecycle: toolLifecycleMetadata(isError
|
|
291
|
-
? { state: "error", failure_kind: "runtime_error", detail_code: "claude_cli_tool_error" }
|
|
292
|
-
: { state: "success" }),
|
|
293
|
-
}],
|
|
294
|
-
},
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
return { type: "cli_event", raw };
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/** Preserve Claude CLI's only trustworthy terminal distinction: tool_result is_error. */
|
|
301
|
-
function annotateClaudeCliToolResults(raw) {
|
|
302
|
-
if (!raw?.message || !Array.isArray(raw.message.content)) return raw;
|
|
303
|
-
let changed = false;
|
|
304
|
-
const content = raw.message.content.map((block) => {
|
|
305
|
-
if (!block || block.type !== "tool_result") return block;
|
|
306
|
-
changed = true;
|
|
307
|
-
return {
|
|
308
|
-
...block,
|
|
309
|
-
tool_lifecycle: toolLifecycleMetadata(block.is_error === true
|
|
310
|
-
? { state: "error", failure_kind: "runtime_error", detail_code: "claude_cli_tool_error" }
|
|
311
|
-
: { state: "success" }),
|
|
312
|
-
};
|
|
313
|
-
});
|
|
314
|
-
return changed ? { ...raw, message: { ...raw.message, content } } : raw;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function textFromEvent(raw) {
|
|
318
|
-
if (typeof raw?.text === "string") return raw.text;
|
|
319
|
-
if (typeof raw?.item?.text === "string") return raw.item.text;
|
|
320
|
-
if (raw?.type === "result" && raw.result != null) {
|
|
321
|
-
return typeof raw.result === "string" ? raw.result : JSON.stringify(raw.result);
|
|
322
|
-
}
|
|
323
|
-
if (raw?.final_output != null) {
|
|
324
|
-
return typeof raw.final_output === "string" ? raw.final_output : JSON.stringify(raw.final_output);
|
|
325
|
-
}
|
|
326
|
-
if (typeof raw?.message?.content === "string") return raw.message.content;
|
|
327
|
-
if (Array.isArray(raw?.message?.content)) {
|
|
328
|
-
return raw.message.content.filter((part) => part?.type === "text").map((part) => part.text).join("");
|
|
329
|
-
}
|
|
330
|
-
return "";
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function stringifyError(value) {
|
|
334
|
-
if (!value) return "";
|
|
335
|
-
if (typeof value === "string") return value;
|
|
336
|
-
if (typeof value.message === "string") return value.message;
|
|
337
|
-
try { return JSON.stringify(value); } catch { return String(value); }
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function humanizeSubtype(subtype) {
|
|
341
|
-
return String(subtype || "").replace(/^error_/, "").replace(/_/g, " ").trim();
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function resultEventError(raw, command) {
|
|
345
|
-
if (raw?.type !== "result") return null;
|
|
346
|
-
const subtype = typeof raw.subtype === "string" ? raw.subtype : "";
|
|
347
|
-
const errors = Array.isArray(raw.errors) ? raw.errors.filter(Boolean) : [];
|
|
348
|
-
const explicit = stringifyError(raw.error) || stringifyError(raw.message);
|
|
349
|
-
if (!raw.is_error && !subtype.startsWith("error_") && errors.length === 0 && !explicit) return null;
|
|
350
|
-
|
|
351
|
-
const runtime = command === "claude" ? "Claude Code" : command === "codex" ? "Codex" : command || "CLI";
|
|
352
|
-
const detail = explicit || errors.map(stringifyError).filter(Boolean).join("; ");
|
|
353
|
-
const label = humanizeSubtype(subtype);
|
|
354
|
-
const message = subtype === "error_max_turns"
|
|
355
|
-
? `${runtime} stopped before final output: max turns reached`
|
|
356
|
-
: `${runtime} result error${label ? ` (${label})` : ""}${detail ? `: ${detail}` : ""}`;
|
|
357
|
-
return {
|
|
358
|
-
message,
|
|
359
|
-
failureKind: subtype === "error_max_turns" ? "usage_limit" : "provider_unavailable",
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function pushUniqueText(texts, text) {
|
|
364
|
-
const value = typeof text === "string" ? text.trim() : "";
|
|
365
|
-
if (!value) return;
|
|
366
|
-
if (texts.some((existing) => existing.trim() === value)) return;
|
|
367
|
-
texts.push(value);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function parseJsonError(text) {
|
|
371
|
-
const raw = String(text || "").trim();
|
|
372
|
-
if (!raw) return null;
|
|
373
|
-
try {
|
|
374
|
-
const parsed = JSON.parse(raw);
|
|
375
|
-
return parsed?.error || parsed;
|
|
376
|
-
} catch {
|
|
377
|
-
return null;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function formatCliError(message, command) {
|
|
382
|
-
const raw = String(message || "").trim();
|
|
383
|
-
const parsed = parseJsonError(raw);
|
|
384
|
-
const code = parsed?.code || parsed?.error?.code;
|
|
385
|
-
const detail = parsed?.message || parsed?.error?.message;
|
|
386
|
-
const param = parsed?.param || parsed?.error?.param;
|
|
387
|
-
if (code === "invalid_json_schema" || /invalid_json_schema|Invalid schema/i.test(raw)) {
|
|
388
|
-
return `Invalid response schema${param ? ` (${param})` : ""}: ${detail || raw}`;
|
|
389
|
-
}
|
|
390
|
-
if (
|
|
391
|
-
command === "claude" &&
|
|
392
|
-
(/401|Unauthorized|OAuth token is invalid|Please run \/login|auth/i.test(raw))
|
|
393
|
-
) {
|
|
394
|
-
return "Claude Code authentication failed. Run `claude /login` or configure ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, or CLAUDE_CODE_OAUTH_TOKEN.";
|
|
395
|
-
}
|
|
396
|
-
return detail || raw;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function hasEntries(value) {
|
|
400
|
-
return value && typeof value === "object" && Object.keys(value).length > 0;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function shellList(values = []) {
|
|
404
|
-
return values.filter(Boolean).join(" ");
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function tomlValue(value) {
|
|
408
|
-
if (typeof value === "string") return JSON.stringify(value);
|
|
409
|
-
if (typeof value === "boolean") return value ? "true" : "false";
|
|
410
|
-
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
411
|
-
if (Array.isArray(value)) return `[${value.map(tomlValue).join(", ")}]`;
|
|
412
|
-
return JSON.stringify(value);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function codexMcpConfigArgs(mcpServers = {}) {
|
|
416
|
-
const args = [];
|
|
417
|
-
for (const [name, cfg] of Object.entries(mcpServers)) {
|
|
418
|
-
if (!/^[A-Za-z0-9_-]+$/.test(name)) continue;
|
|
419
|
-
const prefix = `mcp_servers.${name}`;
|
|
420
|
-
if (cfg.command) {
|
|
421
|
-
args.push("--config", `${prefix}.command=${tomlValue(cfg.command)}`);
|
|
422
|
-
if (Array.isArray(cfg.args) && cfg.args.length) args.push("--config", `${prefix}.args=${tomlValue(cfg.args)}`);
|
|
423
|
-
if (cfg.cwd && typeof cfg.cwd === "string") args.push("--config", `${prefix}.cwd=${tomlValue(cfg.cwd)}`);
|
|
424
|
-
if (cfg.env && typeof cfg.env === "object") {
|
|
425
|
-
for (const [key, value] of Object.entries(cfg.env)) {
|
|
426
|
-
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
427
|
-
args.push("--config", `${prefix}.env.${key}=${tomlValue(String(value))}`);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
} else if (cfg.url) {
|
|
432
|
-
args.push("--config", `${prefix}.url=${tomlValue(cfg.url)}`);
|
|
433
|
-
const headers = cfg.headers || {};
|
|
434
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
435
|
-
if (/^[A-Za-z0-9_-]+$/.test(key)) {
|
|
436
|
-
args.push("--config", `${prefix}.http_headers.${key}=${tomlValue(String(value))}`);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
args.push("--config", `${prefix}.enabled=true`);
|
|
441
|
-
args.push("--config", `${prefix}.required=false`);
|
|
442
|
-
}
|
|
443
|
-
return args;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
export function buildCliCommand({
|
|
447
|
-
sdk,
|
|
448
|
-
model,
|
|
449
|
-
effort,
|
|
450
|
-
cwd,
|
|
451
|
-
schemaPath,
|
|
452
|
-
outputSchema,
|
|
453
|
-
systemPrompt,
|
|
454
|
-
prompt,
|
|
455
|
-
mcpConfigPath,
|
|
456
|
-
mcpServers,
|
|
457
|
-
allowedTools,
|
|
458
|
-
disallowedTools,
|
|
459
|
-
permissionMode,
|
|
460
|
-
maxTurns,
|
|
461
|
-
skillDirs,
|
|
462
|
-
resumeSessionId,
|
|
463
|
-
nativeSubagents,
|
|
464
|
-
contextWindow,
|
|
465
|
-
}) {
|
|
466
|
-
// Effort arrives pre-normalized (low/medium/high/xhigh/max/none); per-CLI
|
|
467
|
-
// ceilings are clamped below rather than by callers.
|
|
468
|
-
const normalizedEffort = typeof effort === "string" && effort.trim() ? effort : null;
|
|
469
|
-
if (sdk === "claude-code") {
|
|
470
|
-
const nativeAgents = claudeNativeAgentDefinitions(nativeSubagents);
|
|
471
|
-
// `"*"` allow-all → defer to Claude Code's full default toolset (omit
|
|
472
|
-
// --tools); `cliAllowedTools` is the "*"-stripped explicit list so a bare
|
|
473
|
-
// "*" never reaches a flag value.
|
|
474
|
-
const { allowAll: cliAllowAll, tools: cliAllowedTools } = resolveClaudeAllowedTools(allowedTools, nativeSubagents);
|
|
475
|
-
// intelligence-ramp Phase 5.1: when the coordinator hands us a parent
|
|
476
|
-
// session id (recovery continuation, R12), pass --resume so the host
|
|
477
|
-
// CLI can keep its own conversation cache warm. Otherwise stay
|
|
478
|
-
// ephemeral so unrelated runs never bleed into each other.
|
|
479
|
-
const resumeFlag = typeof resumeSessionId === "string" && resumeSessionId.trim().length > 0
|
|
480
|
-
? ["--resume", resumeSessionId.trim()]
|
|
481
|
-
: ["--no-session-persistence"];
|
|
482
|
-
const args = [
|
|
483
|
-
"-p",
|
|
484
|
-
"--output-format", "stream-json",
|
|
485
|
-
"--include-partial-messages",
|
|
486
|
-
"--forward-subagent-text",
|
|
487
|
-
"--verbose",
|
|
488
|
-
...(outputSchema ? ["--json-schema", JSON.stringify(outputSchema)] : []),
|
|
489
|
-
"--model", modelWithContextWindow(model, contextWindow),
|
|
490
|
-
"--append-system-prompt", systemPrompt,
|
|
491
|
-
...resumeFlag,
|
|
492
|
-
];
|
|
493
|
-
if (normalizedEffort) args.push("--effort", normalizedEffort);
|
|
494
|
-
if (permissionMode) args.push("--permission-mode", permissionMode);
|
|
495
|
-
if (Number.isFinite(Number(maxTurns)) && Number(maxTurns) > 0) args.push("--max-turns", String(Number(maxTurns)));
|
|
496
|
-
if (Array.isArray(skillDirs) && skillDirs.length) {
|
|
497
|
-
args.push("--add-dir", ...skillDirs);
|
|
498
|
-
}
|
|
499
|
-
if (nativeAgents) args.push("--agents", JSON.stringify(nativeAgents));
|
|
500
|
-
if (!cliAllowAll && Array.isArray(cliAllowedTools) && cliAllowedTools.length) {
|
|
501
|
-
args.push("--tools", cliAllowedTools.join(","));
|
|
502
|
-
}
|
|
503
|
-
const autoAllowed = [
|
|
504
|
-
...(!cliAllowAll && Array.isArray(cliAllowedTools) ? cliAllowedTools : []),
|
|
505
|
-
...Object.keys(mcpServers || {}).map((name) => `mcp__${name}__*`),
|
|
506
|
-
];
|
|
507
|
-
if (autoAllowed.length) args.push("--allowedTools", shellList(autoAllowed));
|
|
508
|
-
if (Array.isArray(disallowedTools) && disallowedTools.length) {
|
|
509
|
-
args.push("--disallowedTools", shellList(disallowedTools));
|
|
510
|
-
}
|
|
511
|
-
if (mcpConfigPath) args.push("--mcp-config", mcpConfigPath, "--strict-mcp-config");
|
|
512
|
-
args.push("--", prompt);
|
|
513
|
-
return { command: "claude", args, cwd };
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
const args = [
|
|
517
|
-
"exec",
|
|
518
|
-
"--json",
|
|
519
|
-
...(schemaPath ? ["--output-schema", schemaPath] : []),
|
|
520
|
-
"--model", model,
|
|
521
|
-
"--cd", cwd,
|
|
522
|
-
"--ephemeral",
|
|
523
|
-
"--skip-git-repo-check",
|
|
524
|
-
"--config", `service_tier=${tomlValue("fast")}`,
|
|
525
|
-
"--config", "features.fast_mode=true",
|
|
526
|
-
];
|
|
527
|
-
if (permissionMode === "bypassPermissions") args.push("--dangerously-bypass-approvals-and-sandbox");
|
|
528
|
-
else if (permissionMode === "acceptEdits" || permissionMode === "auto") args.push("--full-auto");
|
|
529
|
-
else if (permissionMode === "plan") args.push("--sandbox", "read-only");
|
|
530
|
-
// codex has no "max" reasoning tier; clamp to its ceiling instead of crashing the CLI.
|
|
531
|
-
const codexEffort = normalizedEffort === "max" ? "xhigh" : normalizedEffort;
|
|
532
|
-
if (codexEffort) args.push("--config", `model_reasoning_effort=${codexEffort}`);
|
|
533
|
-
if (codexEffort !== "none") args.push("--config", `model_reasoning_summary=${tomlValue("auto")}`);
|
|
534
|
-
if (hasEntries(mcpServers)) args.push(...codexMcpConfigArgs(mcpServers));
|
|
535
|
-
args.push([systemPrompt, prompt].filter((part) => String(part || "").trim()).join("\n\n"));
|
|
536
|
-
return { command: "codex", args, cwd };
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
export async function generateCliResponse(systemPrompt, options = {}) {
|
|
540
|
-
const start = Date.now();
|
|
541
|
-
const resolved = options.model;
|
|
542
|
-
if (resolved?.sdk === "claude-code" && claudeSandboxPolicyProblem(options)) {
|
|
543
|
-
const providerSessionId = (typeof options.sessionId === "string" && options.sessionId.trim())
|
|
544
|
-
|| (typeof options.providerSessionId === "string" && options.providerSessionId.trim())
|
|
545
|
-
|| null;
|
|
546
|
-
return claudeSandboxCapabilityMismatchResult({
|
|
547
|
-
model: resolved.reference || `claude:${resolved.model}`,
|
|
548
|
-
effort: options.effort,
|
|
549
|
-
sdk: "claude-code",
|
|
550
|
-
providerSessionId,
|
|
551
|
-
durationMs: Date.now() - start,
|
|
552
|
-
outputSchema: options.outputSchema,
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
if (resolved?.sdk === "claude-code" && Array.isArray(options.allowedTools) && options.allowedTools.length === 0) {
|
|
556
|
-
const providerSessionId = (typeof options.sessionId === "string" && options.sessionId.trim())
|
|
557
|
-
|| (typeof options.providerSessionId === "string" && options.providerSessionId.trim())
|
|
558
|
-
|| null;
|
|
559
|
-
return claudeCapabilityMismatchResult({
|
|
560
|
-
model: resolved.reference || `claude:${resolved.model}`,
|
|
561
|
-
effort: options.effort,
|
|
562
|
-
sdk: "claude-code",
|
|
563
|
-
providerSessionId,
|
|
564
|
-
durationMs: Date.now() - start,
|
|
565
|
-
outputSchema: options.outputSchema,
|
|
566
|
-
error: CLAUDE_CLI_EMPTY_TOOL_POLICY_UNSUPPORTED,
|
|
567
|
-
errorCode: "claude_cli_empty_tool_policy_unsupported",
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
if (resolved?.sdk !== "claude-code") {
|
|
571
|
-
if (resolveSandboxPolicy(options.toolContext, options.sandboxPolicy) !== undefined) {
|
|
572
|
-
return codexCliCapabilityMismatchResult(
|
|
573
|
-
options,
|
|
574
|
-
CODEX_CLI_SANDBOX_POLICY_UNSUPPORTED,
|
|
575
|
-
"codex_sandbox_policy_unsupported",
|
|
576
|
-
start,
|
|
577
|
-
);
|
|
578
|
-
}
|
|
579
|
-
const toolPolicyProblem = codexCliToolPolicyProblem(options);
|
|
580
|
-
if (toolPolicyProblem !== null) {
|
|
581
|
-
return codexCliCapabilityMismatchResult(
|
|
582
|
-
options,
|
|
583
|
-
toolPolicyProblem,
|
|
584
|
-
"codex_tool_policy_unsupported",
|
|
585
|
-
start,
|
|
586
|
-
);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
const prompt = promptFromMessages(options.messages);
|
|
590
|
-
const dir = mkdtempSync(join(tmpdir(), (options.toolContext?.runtimeBrand ?? readRuntimeBrand()).tempdirPrefix));
|
|
591
|
-
const schemaPath = options.outputSchema ? join(dir, "output-schema.json") : null;
|
|
592
|
-
if (schemaPath) writeFileSync(schemaPath, JSON.stringify(options.outputSchema));
|
|
593
|
-
const mcpServers = options.mcpServers || {};
|
|
594
|
-
const mcpConfigPath = hasEntries(mcpServers) && resolved.sdk === "claude-code"
|
|
595
|
-
? join(dir, "mcp.json")
|
|
596
|
-
: null;
|
|
597
|
-
// Owner-only mode: this temp config carries full mcpServers env (including a
|
|
598
|
-
// resolved embedding apiKey) and lives under shared /tmp. The 0700 parent dir
|
|
599
|
-
// (mkdtempSync) already blocks cross-user traversal; 0o600 is defense-in-depth
|
|
600
|
-
// matching the idiom in packages/agent-contracts/src/json-source.ts and pi-auth.js.
|
|
601
|
-
if (mcpConfigPath) writeFileSync(mcpConfigPath, JSON.stringify({ mcpServers }, null, 2), { mode: 0o600 });
|
|
602
|
-
const reusableSessionId = (typeof options.sessionId === "string" && options.sessionId.trim())
|
|
603
|
-
|| (typeof options.providerSessionId === "string" && options.providerSessionId.trim())
|
|
604
|
-
|| null;
|
|
605
|
-
let providerSessionId = reusableSessionId || null;
|
|
606
|
-
const commandSpec = buildCliCommand({
|
|
607
|
-
sdk: resolved.sdk,
|
|
608
|
-
model: resolved.model,
|
|
609
|
-
effort: options.effort,
|
|
610
|
-
cwd: options.cwd || process.cwd(),
|
|
611
|
-
schemaPath,
|
|
612
|
-
outputSchema: options.outputSchema,
|
|
613
|
-
systemPrompt,
|
|
614
|
-
prompt,
|
|
615
|
-
mcpConfigPath,
|
|
616
|
-
mcpServers,
|
|
617
|
-
allowedTools: options.allowedTools,
|
|
618
|
-
disallowedTools: options.disallowedTools,
|
|
619
|
-
permissionMode: options.permissionMode,
|
|
620
|
-
maxTurns: options.maxTurns,
|
|
621
|
-
skillDirs: Array.isArray(options.skillDirs)
|
|
622
|
-
? options.skillDirs
|
|
623
|
-
: getSkillAccessDirs(options.skills || []),
|
|
624
|
-
resumeSessionId: reusableSessionId,
|
|
625
|
-
nativeSubagents: options.nativeSubagents,
|
|
626
|
-
contextWindow: options.contextWindow,
|
|
627
|
-
});
|
|
628
|
-
|
|
629
|
-
const events = [];
|
|
630
|
-
const texts = [];
|
|
631
|
-
let errorMessage = null;
|
|
632
|
-
let failureKind = null;
|
|
633
|
-
let usage = {};
|
|
634
|
-
// Claude Code returns structured output via a `StructuredOutput` tool_use
|
|
635
|
-
// block. We capture the latest one we see during the run; if `outputSchema`
|
|
636
|
-
// was supplied the bridge surfaces it as `structuredResult` so the host's
|
|
637
|
-
// result parser can validate it without re-walking the event stream.
|
|
638
|
-
let structuredResult;
|
|
639
|
-
function captureStructuredOutputFromRaw(raw) {
|
|
640
|
-
const blocks = raw?.message?.content || raw?.content;
|
|
641
|
-
if (!Array.isArray(blocks)) return;
|
|
642
|
-
for (const block of blocks) {
|
|
643
|
-
if (block?.type === "tool_use" && block?.name === "StructuredOutput" && block?.input !== undefined) {
|
|
644
|
-
structuredResult = block.input;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
const cliEventContext = {
|
|
649
|
-
cwd: commandSpec.cwd,
|
|
650
|
-
fileChangeSnapshots: new Map(),
|
|
651
|
-
thinkingBuffer: createThinkingBuffer(),
|
|
652
|
-
};
|
|
653
|
-
const subagentNormalizer = resolved.sdk === "claude-code"
|
|
654
|
-
? createClaudeSubagentActivityNormalizer()
|
|
655
|
-
: null;
|
|
656
|
-
function emitSubagentEvents(activityEvents) {
|
|
657
|
-
for (const activity of activityEvents) {
|
|
658
|
-
events.push(activity);
|
|
659
|
-
options.onEvent?.(activity);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
function drainSubagents(reason) {
|
|
663
|
-
if (!subagentNormalizer) return;
|
|
664
|
-
emitSubagentEvents(subagentNormalizer.drain(reason));
|
|
665
|
-
}
|
|
666
|
-
function observedSubagentCapabilities() {
|
|
667
|
-
if (!subagentNormalizer) return { invoked: null, names: [] };
|
|
668
|
-
return {
|
|
669
|
-
invoked: subagentNormalizer.subagentInvoked(),
|
|
670
|
-
names: subagentNormalizer.nativeSubagentsUsed(),
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
const stderrTail = createStderrTail({ limit: 8 * 1024 });
|
|
675
|
-
let abortHandler = null;
|
|
676
|
-
try {
|
|
677
|
-
const child = spawn(commandSpec.command, commandSpec.args, {
|
|
678
|
-
cwd: commandSpec.cwd,
|
|
679
|
-
env: process.env,
|
|
680
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
681
|
-
});
|
|
682
|
-
|
|
683
|
-
child.stderr.on("data", (chunk) => stderrTail.push(chunk));
|
|
684
|
-
|
|
685
|
-
const rl = createInterface({ input: child.stdout });
|
|
686
|
-
rl.on("line", (line) => {
|
|
687
|
-
if (!line.trim()) return;
|
|
688
|
-
let raw;
|
|
689
|
-
try {
|
|
690
|
-
raw = JSON.parse(line);
|
|
691
|
-
} catch {
|
|
692
|
-
const ev = { type: "cli_stdout", text: line };
|
|
693
|
-
events.push(ev);
|
|
694
|
-
options.onEvent?.(ev);
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
// Capture the provider session before a child event is consumed. Claude
|
|
698
|
-
// uses the same session id on parent and child records.
|
|
699
|
-
const candidateSessionId = raw.session_id ?? raw.sessionId ?? raw.thread_id ?? null;
|
|
700
|
-
if (typeof candidateSessionId === "string" && candidateSessionId.trim().length > 0) {
|
|
701
|
-
providerSessionId = candidateSessionId.trim();
|
|
702
|
-
}
|
|
703
|
-
const observation = subagentNormalizer?.observe(raw);
|
|
704
|
-
if (observation) emitSubagentEvents(observation.events);
|
|
705
|
-
// Child messages must not be normalized, added to parent text, counted as
|
|
706
|
-
// parent usage, or considered for the parent's StructuredOutput result.
|
|
707
|
-
if (observation?.consumed) return;
|
|
708
|
-
// A root user message may batch a background Agent launch acknowledgement
|
|
709
|
-
// with unrelated tool results. The normalizer removes only that launch
|
|
710
|
-
// block so the remaining parent activity still flows normally.
|
|
711
|
-
const parentRaw = observation?.forwarded ?? raw;
|
|
712
|
-
const ev = normalizeCliEvent(parentRaw, cliEventContext);
|
|
713
|
-
if (ev) {
|
|
714
|
-
events.push(ev);
|
|
715
|
-
options.onEvent?.(ev);
|
|
716
|
-
}
|
|
717
|
-
if (!isCodexReasoningEvent(parentRaw)) {
|
|
718
|
-
const text = textFromEvent(parentRaw);
|
|
719
|
-
pushUniqueText(texts, text);
|
|
720
|
-
}
|
|
721
|
-
captureStructuredOutputFromRaw(parentRaw);
|
|
722
|
-
if (parentRaw.usage) usage = parentRaw.usage;
|
|
723
|
-
if (parentRaw.type === "error") {
|
|
724
|
-
const rawError = parentRaw.message || parentRaw.error || "cli error";
|
|
725
|
-
errorMessage = typeof rawError === "string" ? rawError : JSON.stringify(rawError);
|
|
726
|
-
failureKind = "provider_unavailable";
|
|
727
|
-
drainSubagents("subagent stopped because the Claude CLI stream failed");
|
|
728
|
-
}
|
|
729
|
-
const resultError = resultEventError(parentRaw, commandSpec.command);
|
|
730
|
-
if (resultError) {
|
|
731
|
-
errorMessage = resultError.message;
|
|
732
|
-
failureKind = resultError.failureKind;
|
|
733
|
-
drainSubagents("subagent stopped because the Claude CLI stream failed");
|
|
734
|
-
}
|
|
735
|
-
});
|
|
736
|
-
|
|
737
|
-
child.on("error", (error) => {
|
|
738
|
-
if (!errorMessage) errorMessage = error?.message || String(error);
|
|
739
|
-
failureKind ||= "provider_unavailable";
|
|
740
|
-
drainSubagents("subagent stopped because the Claude CLI process failed");
|
|
741
|
-
});
|
|
742
|
-
|
|
743
|
-
if (options.abortSignal) {
|
|
744
|
-
abortHandler = () => {
|
|
745
|
-
drainSubagents("subagent cancelled with the parent run");
|
|
746
|
-
child.kill("SIGTERM");
|
|
747
|
-
};
|
|
748
|
-
if (options.abortSignal.aborted) abortHandler();
|
|
749
|
-
else options.abortSignal.addEventListener("abort", abortHandler, { once: true });
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
const exitCode = await new Promise((resolve) => child.on("close", resolve));
|
|
753
|
-
drainSubagents(options.abortSignal?.aborted
|
|
754
|
-
? "subagent cancelled with the parent run"
|
|
755
|
-
: exitCode === 0
|
|
756
|
-
? "subagent stream closed before completion"
|
|
757
|
-
: "subagent stopped because the Claude CLI process failed");
|
|
758
|
-
const stderrText = stderrTail.toString().trim();
|
|
759
|
-
let cliErrorCode = null;
|
|
760
|
-
if (exitCode !== 0 && !errorMessage) errorMessage = stderrText || `${commandSpec.command} exited ${exitCode}`;
|
|
761
|
-
const text = texts[texts.length - 1] || "";
|
|
762
|
-
const hadPartialProgress = events.length > 0 || texts.length > 0;
|
|
763
|
-
if (exitCode === 0 && !errorMessage && !text.trim() && structuredResult === undefined) {
|
|
764
|
-
errorMessage = `${commandSpec.command} completed without final output`;
|
|
765
|
-
failureKind = failureKind || "provider_unavailable";
|
|
766
|
-
cliErrorCode = "cli_stream_terminated";
|
|
767
|
-
}
|
|
768
|
-
const reference = `${resolved.sdk}:${resolved.model}`;
|
|
769
|
-
const inputTokens = usage?.input_tokens ?? usage?.inputTokens ?? 0;
|
|
770
|
-
const outputTokens = usage?.output_tokens ?? usage?.outputTokens ?? 0;
|
|
771
|
-
const cachedTokens = usage?.cache_read_tokens ?? usage?.cache_read_input_tokens ?? 0;
|
|
772
|
-
const cacheCreationTokens = usage?.cache_creation_tokens ?? usage?.cache_creation_input_tokens ?? 0;
|
|
773
|
-
const costUsd = estimateCost({
|
|
774
|
-
resolveCustomPricing: options.resolveCustomPricing,
|
|
775
|
-
model: reference,
|
|
776
|
-
inputTokens,
|
|
777
|
-
outputTokens,
|
|
778
|
-
cachedTokens,
|
|
779
|
-
cacheWriteTokens: cacheCreationTokens,
|
|
780
|
-
});
|
|
781
|
-
const enrichedUsage = {
|
|
782
|
-
...usage,
|
|
783
|
-
input_tokens: inputTokens || null,
|
|
784
|
-
output_tokens: outputTokens || null,
|
|
785
|
-
cache_read_tokens: cachedTokens || null,
|
|
786
|
-
cache_creation_tokens: cacheCreationTokens || null,
|
|
787
|
-
cost_usd: costUsd,
|
|
788
|
-
};
|
|
789
|
-
const subagentCapabilities = observedSubagentCapabilities();
|
|
790
|
-
return {
|
|
791
|
-
text,
|
|
792
|
-
structuredResult,
|
|
793
|
-
structuredResultSource: structuredResult === undefined ? null : "StructuredOutput",
|
|
794
|
-
events,
|
|
795
|
-
usage: enrichedUsage,
|
|
796
|
-
durationMs: Date.now() - start,
|
|
797
|
-
numTurns: texts.length || (events.length ? 1 : 0),
|
|
798
|
-
model: reference,
|
|
799
|
-
effort: options.effort || null,
|
|
800
|
-
sdk: resolved.sdk,
|
|
801
|
-
providerSessionId: providerSessionId || null,
|
|
802
|
-
provider_session_id: providerSessionId || null,
|
|
803
|
-
cancelled: !!options.abortSignal?.aborted,
|
|
804
|
-
error: errorMessage ? formatCliError(errorMessage, commandSpec.command) : null,
|
|
805
|
-
failureKind,
|
|
806
|
-
stderrTail: stderrText || null,
|
|
807
|
-
diagnostics: {
|
|
808
|
-
...(cliErrorCode ? { pi_error_code: cliErrorCode } : {}),
|
|
809
|
-
...(hadPartialProgress && failureKind === "provider_unavailable"
|
|
810
|
-
? { had_partial_progress: true }
|
|
811
|
-
: {}),
|
|
812
|
-
},
|
|
813
|
-
capabilitiesUsed: buildCapabilitiesUsed({
|
|
814
|
-
promptCacheActive: (cachedTokens || 0) > 0 || (cacheCreationTokens || 0) > 0,
|
|
815
|
-
thinkingEnabled: null,
|
|
816
|
-
structuredOutputEnforced: !!options.outputSchema,
|
|
817
|
-
subagentInvoked: subagentCapabilities.invoked,
|
|
818
|
-
mcpServersUsed: Object.keys(options.mcpServers || {}),
|
|
819
|
-
nativeSubagentsUsed: subagentCapabilities.names,
|
|
820
|
-
toolCompactionApplied: false,
|
|
821
|
-
contextCompactionApplied: null,
|
|
822
|
-
}),
|
|
823
|
-
};
|
|
824
|
-
} catch (err) {
|
|
825
|
-
drainSubagents(options.abortSignal?.aborted
|
|
826
|
-
? "subagent cancelled with the parent run"
|
|
827
|
-
: "subagent stopped because the Claude CLI process failed");
|
|
828
|
-
const subagentCapabilities = observedSubagentCapabilities();
|
|
829
|
-
return {
|
|
830
|
-
text: texts[texts.length - 1] || null,
|
|
831
|
-
structuredResult,
|
|
832
|
-
structuredResultSource: structuredResult === undefined ? null : "StructuredOutput",
|
|
833
|
-
events,
|
|
834
|
-
usage: {},
|
|
835
|
-
durationMs: Date.now() - start,
|
|
836
|
-
numTurns: texts.length || (events.length ? 1 : 0),
|
|
837
|
-
model: resolved?.reference || null,
|
|
838
|
-
effort: options.effort || null,
|
|
839
|
-
sdk: resolved?.sdk || "cli",
|
|
840
|
-
providerSessionId: providerSessionId || null,
|
|
841
|
-
provider_session_id: providerSessionId || null,
|
|
842
|
-
cancelled: !!options.abortSignal?.aborted,
|
|
843
|
-
error: err.message || String(err),
|
|
844
|
-
failureKind: failureKind || "provider_unavailable",
|
|
845
|
-
stderrTail: stderrTail.toString() || null,
|
|
846
|
-
diagnostics: {
|
|
847
|
-
...(err?.code ? { pi_error_code: String(err.code) } : {}),
|
|
848
|
-
...(events.length > 0 || texts.length > 0 ? { had_partial_progress: true } : {}),
|
|
849
|
-
},
|
|
850
|
-
capabilitiesUsed: buildCapabilitiesUsed({
|
|
851
|
-
promptCacheActive: null,
|
|
852
|
-
thinkingEnabled: null,
|
|
853
|
-
structuredOutputEnforced: !!options.outputSchema,
|
|
854
|
-
subagentInvoked: subagentCapabilities.invoked,
|
|
855
|
-
mcpServersUsed: Object.keys(options.mcpServers || {}),
|
|
856
|
-
nativeSubagentsUsed: subagentCapabilities.names,
|
|
857
|
-
toolCompactionApplied: false,
|
|
858
|
-
contextCompactionApplied: null,
|
|
859
|
-
}),
|
|
860
|
-
};
|
|
861
|
-
} finally {
|
|
862
|
-
if (abortHandler && options.abortSignal) {
|
|
863
|
-
options.abortSignal.removeEventListener?.("abort", abortHandler);
|
|
864
|
-
}
|
|
865
|
-
try { rmSync(dir, { recursive: true, force: true }); } catch {}
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// CLI bridge for sdk='claude' agents that opt into execution_mode='cli'.
|
|
870
|
-
// generateCliResponse internally branches on resolved.sdk; the SDK shape
|
|
871
|
-
// from parseModelReference uses 'claude', the CLI builder expects
|
|
872
|
-
// 'claude-code', so we shim the model ref here rather than scattering
|
|
873
|
-
// translation logic across the registry.
|
|
874
|
-
export const claudeCodeRuntimeBridge = {
|
|
875
|
-
id: "claude-code",
|
|
876
|
-
kind: "claude-code",
|
|
877
|
-
capabilities: { kind: "claude-code", runtime: "cli", ...DORMANT_CLI_CAPABILITIES },
|
|
878
|
-
supports: (ref, options) => ref?.sdk === "claude" && options?.executionMode === "cli",
|
|
879
|
-
execute: (systemPrompt, options) => generateCliResponse(systemPrompt, {
|
|
880
|
-
...options,
|
|
881
|
-
model: { ...options.model, sdk: "claude-code" },
|
|
882
|
-
}),
|
|
883
|
-
};
|