@mono-agent/agent-runtime 0.1.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.
- package/ARCHITECTURE.md +219 -0
- package/LICENSE +674 -0
- package/README.md +430 -0
- package/package.json +46 -0
- package/src/agent/allowlists.js +49 -0
- package/src/agent/approval.js +211 -0
- package/src/agent/compaction.js +752 -0
- package/src/agent/index.js +40 -0
- package/src/agent/prompt/skill-index.js +66 -0
- package/src/agent/tool-bloat.js +164 -0
- package/src/agent/tools/bash.js +156 -0
- package/src/agent/tools/edit.js +15 -0
- package/src/agent/tools/glob.js +71 -0
- package/src/agent/tools/grep.js +84 -0
- package/src/agent/tools/index.js +17 -0
- package/src/agent/tools/pi-bridge.js +638 -0
- package/src/agent/tools/read.js +39 -0
- package/src/agent/tools/shared/constants.js +21 -0
- package/src/agent/tools/shared/dedup.js +31 -0
- package/src/agent/tools/shared/output-truncation.js +54 -0
- package/src/agent/tools/shared/path-resolver.js +156 -0
- package/src/agent/tools/shared/ripgrep.js +130 -0
- package/src/agent/tools/shared/runtime-context.js +69 -0
- package/src/agent/tools/web-fetch.js +59 -0
- package/src/agent/tools/web-search.js +21 -0
- package/src/agent/tools/write.js +14 -0
- package/src/agent/transcript.js +227 -0
- package/src/ai/backend.js +17 -0
- package/src/ai/cost.js +164 -0
- package/src/ai/failure.js +165 -0
- package/src/ai/file-change-stats.js +234 -0
- package/src/ai/index.js +16 -0
- package/src/ai/live-input-prompt.js +15 -0
- package/src/ai/observer.js +233 -0
- package/src/ai/providers/claude-cli.js +694 -0
- package/src/ai/providers/claude-sdk.js +864 -0
- package/src/ai/providers/claude-subagents.js +67 -0
- package/src/ai/providers/codex-app.js +1045 -0
- package/src/ai/providers/opencode-app.js +356 -0
- package/src/ai/providers/opencode-discovery.js +39 -0
- package/src/ai/providers/pi-events.js +62 -0
- package/src/ai/providers/pi-messages.js +68 -0
- package/src/ai/providers/pi-models.js +111 -0
- package/src/ai/providers/pi-sdk.js +1310 -0
- package/src/ai/registry.js +5 -0
- package/src/ai/runtime/capabilities-used.js +56 -0
- package/src/ai/runtime/capabilities.js +44 -0
- package/src/ai/runtime/context-windows.js +38 -0
- package/src/ai/runtime/fast-mode.js +8 -0
- package/src/ai/runtime/model-refs.js +144 -0
- package/src/ai/runtime/registry.js +57 -0
- package/src/ai/runtime/router.js +214 -0
- package/src/ai/runtime/sessions.js +126 -0
- package/src/ai/streaming/codex-events.js +139 -0
- package/src/ai/streaming/opencode-events.js +54 -0
- package/src/ai/types.js +70 -0
- package/src/index.js +23 -0
- package/src/pi-auth.js +80 -0
- package/src/runtime-brand.js +32 -0
- package/src/runtime.js +104 -0
|
@@ -0,0 +1,864 @@
|
|
|
1
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
createFileEditToolResultEvent,
|
|
6
|
+
createFileEditToolUseEvent,
|
|
7
|
+
fileChangeSummary,
|
|
8
|
+
readFileChangeSnapshot,
|
|
9
|
+
statsForCompletedChange,
|
|
10
|
+
} from "../file-change-stats.js";
|
|
11
|
+
import { formatLiveInputGuidance } from "../live-input-prompt.js";
|
|
12
|
+
import { estimateCost } from "../cost.js";
|
|
13
|
+
import { modelWithContextWindow } from "../runtime/context-windows.js";
|
|
14
|
+
import { runtimeCapabilities } from "../runtime/capabilities.js";
|
|
15
|
+
import { buildCapabilitiesUsed, toolCompactionAppliedFromWarnings } from "../runtime/capabilities-used.js";
|
|
16
|
+
import { MAX_TOOL_RESULT_BYTES, summarisePayload } from "../../agent/tool-bloat.js";
|
|
17
|
+
import { normalizeMcpToolParams } from "../../agent/tools/pi-bridge.js";
|
|
18
|
+
import { readRuntimeBrand } from "../../agent/tools/shared/runtime-context.js";
|
|
19
|
+
import { createApprovalManager } from "../../agent/approval.js";
|
|
20
|
+
import {
|
|
21
|
+
claudeNativeAgentDefinitions,
|
|
22
|
+
claudeToolsWithNativeSubagents,
|
|
23
|
+
} from "./claude-subagents.js";
|
|
24
|
+
|
|
25
|
+
function thinkingForEffort(effort) {
|
|
26
|
+
if (effort === "low") return { thinking: { type: "disabled" } };
|
|
27
|
+
return { thinking: { type: "adaptive" }, effort };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function extractText(event) {
|
|
31
|
+
if (event.type !== "assistant" || !event.message?.content) return "";
|
|
32
|
+
let out = "";
|
|
33
|
+
for (const block of event.message.content) {
|
|
34
|
+
if (block.type === "text") out += block.text;
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function assistantToolNames(event) {
|
|
40
|
+
if (event.type !== "assistant" || !Array.isArray(event.message?.content)) return [];
|
|
41
|
+
return event.message.content
|
|
42
|
+
.filter((block) => block?.type === "tool_use" && block.name)
|
|
43
|
+
.map((block) => block.name);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function extractResultText(event) {
|
|
47
|
+
if (event.type !== "result") return "";
|
|
48
|
+
if (typeof event.result === "string") return event.result;
|
|
49
|
+
if (event.result != null) return JSON.stringify(event.result);
|
|
50
|
+
if (typeof event.final_output === "string") return event.final_output;
|
|
51
|
+
if (event.final_output != null) return JSON.stringify(event.final_output);
|
|
52
|
+
return "";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function stringifyError(value) {
|
|
56
|
+
if (!value) return "";
|
|
57
|
+
if (typeof value === "string") return value;
|
|
58
|
+
if (typeof value.message === "string") return value.message;
|
|
59
|
+
try { return JSON.stringify(value); } catch { return String(value); }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function humanizeSubtype(subtype) {
|
|
63
|
+
return String(subtype || "").replace(/^error_/, "").replace(/_/g, " ").trim();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resultEventError(event) {
|
|
67
|
+
if (event.type !== "result") return null;
|
|
68
|
+
const subtype = typeof event.subtype === "string" ? event.subtype : "";
|
|
69
|
+
const errors = Array.isArray(event.errors) ? event.errors.filter(Boolean) : [];
|
|
70
|
+
const explicit = stringifyError(event.error) || stringifyError(event.message);
|
|
71
|
+
if (!event.is_error && !subtype.startsWith("error_") && errors.length === 0 && !explicit) return null;
|
|
72
|
+
|
|
73
|
+
const detail = explicit || errors.map(stringifyError).filter(Boolean).join("; ");
|
|
74
|
+
const label = humanizeSubtype(subtype);
|
|
75
|
+
const message = subtype === "error_max_turns"
|
|
76
|
+
? "Claude stopped before final output: max turns reached"
|
|
77
|
+
: `Claude result error${label ? ` (${label})` : ""}${detail ? `: ${detail}` : ""}`;
|
|
78
|
+
return {
|
|
79
|
+
message,
|
|
80
|
+
failureKind: subtype === "error_max_turns"
|
|
81
|
+
? "usage_limit"
|
|
82
|
+
: subtype === "error_max_structured_output_retries"
|
|
83
|
+
? "invalid_result"
|
|
84
|
+
: "provider_unavailable",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function makeRuntimeWarning(message, warningKind = "claude_post_success_error") {
|
|
89
|
+
return {
|
|
90
|
+
warning_kind: warningKind,
|
|
91
|
+
message,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function extractStructuredOutput(event) {
|
|
96
|
+
if (event?.type === "result" && Object.prototype.hasOwnProperty.call(event, "structured_output")) {
|
|
97
|
+
return event.structured_output;
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function structuredOutputEvent(value) {
|
|
103
|
+
return {
|
|
104
|
+
type: "structured_output",
|
|
105
|
+
source: "claude_sdk_output_format",
|
|
106
|
+
value,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function structuredOutputToolUses(event) {
|
|
111
|
+
if (event?.type !== "assistant" || !Array.isArray(event.message?.content)) return [];
|
|
112
|
+
return event.message.content
|
|
113
|
+
.filter((block) => (
|
|
114
|
+
block?.type === "tool_use"
|
|
115
|
+
&& block?.name === "StructuredOutput"
|
|
116
|
+
&& block.input !== undefined
|
|
117
|
+
&& (block.id || block.tool_use_id)
|
|
118
|
+
))
|
|
119
|
+
.map((block) => ({
|
|
120
|
+
id: block.id || block.tool_use_id,
|
|
121
|
+
input: block.input,
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function acceptedStructuredOutputValues(event, pendingStructuredOutputById) {
|
|
126
|
+
if (event?.type !== "user" || !Array.isArray(event.message?.content)) return [];
|
|
127
|
+
const values = [];
|
|
128
|
+
for (const block of event.message.content) {
|
|
129
|
+
if (block?.type !== "tool_result") continue;
|
|
130
|
+
const id = block.tool_use_id || block.toolUseId;
|
|
131
|
+
if (!id || !pendingStructuredOutputById.has(id)) continue;
|
|
132
|
+
const value = pendingStructuredOutputById.get(id);
|
|
133
|
+
pendingStructuredOutputById.delete(id);
|
|
134
|
+
if (block.is_error === true) continue;
|
|
135
|
+
values.push(value);
|
|
136
|
+
}
|
|
137
|
+
return values;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function pickSessionId(...values) {
|
|
141
|
+
for (const value of values) {
|
|
142
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function sessionIdFromEvent(event) {
|
|
148
|
+
return pickSessionId(event?.session_id, event?.sessionId);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function lastTextSnippet(texts, limit = 200) {
|
|
152
|
+
for (let i = texts.length - 1; i >= 0; i -= 1) {
|
|
153
|
+
const text = texts[i];
|
|
154
|
+
if (typeof text === "string" && text.trim()) {
|
|
155
|
+
const trimmed = text.trim();
|
|
156
|
+
return trimmed.length > limit ? trimmed.slice(-limit) : trimmed;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function buildClaudeErrorDetails({
|
|
163
|
+
event = null,
|
|
164
|
+
subtype = null,
|
|
165
|
+
providerSessionId = null,
|
|
166
|
+
assistantTexts = [],
|
|
167
|
+
lastToolName = null,
|
|
168
|
+
toolResultsSeen = 0,
|
|
169
|
+
numTurns = 0,
|
|
170
|
+
lastStructuredOutputRejection = null,
|
|
171
|
+
}) {
|
|
172
|
+
const resolvedSubtype = subtype || event?.subtype || event?.type || null;
|
|
173
|
+
const turnCount = Number(event?.num_turns ?? numTurns) || 0;
|
|
174
|
+
const excerpt = lastTextSnippet(assistantTexts);
|
|
175
|
+
return {
|
|
176
|
+
claude_error_subtype: resolvedSubtype,
|
|
177
|
+
last_text_excerpt: excerpt,
|
|
178
|
+
last_tool_name: lastToolName || null,
|
|
179
|
+
had_partial_progress: !!(excerpt || lastToolName || toolResultsSeen > 0),
|
|
180
|
+
tool_results_seen: toolResultsSeen,
|
|
181
|
+
turn_count: turnCount,
|
|
182
|
+
max_turns_hit: resolvedSubtype === "error_max_turns",
|
|
183
|
+
structured_output_retry_exhausted: resolvedSubtype === "error_max_structured_output_retries",
|
|
184
|
+
last_structured_output_rejection: lastStructuredOutputRejection || null,
|
|
185
|
+
provider_session_id: providerSessionId || null,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function toolResultText(block) {
|
|
190
|
+
const content = block?.content;
|
|
191
|
+
if (typeof content === "string") return content;
|
|
192
|
+
if (Array.isArray(content)) {
|
|
193
|
+
return content.map((item) => item?.text || item?.content || "").filter(Boolean).join("\n");
|
|
194
|
+
}
|
|
195
|
+
if (content == null) return "";
|
|
196
|
+
try { return JSON.stringify(content); } catch { return String(content); }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function structuredOutputRejectionFromEvent(event) {
|
|
200
|
+
if (event?.type !== "user" || !Array.isArray(event.message?.content)) return null;
|
|
201
|
+
for (const block of event.message.content) {
|
|
202
|
+
if (block?.type === "tool_result" && block.is_error) {
|
|
203
|
+
const text = toolResultText(block);
|
|
204
|
+
if (/structured output|required schema|did not match schema|schema violation/i.test(text)) return text;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const result = event.is_error === true ? stringifyError(event.tool_use_result) : null;
|
|
208
|
+
return /structured output|required schema|did not match schema|schema violation/i.test(result) ? result : null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const CLAUDE_FILE_EDIT_MATCHER = "Edit|Write|NotebookEdit";
|
|
212
|
+
|
|
213
|
+
function mergeHookMatchers(existing = {}, additions = {}) {
|
|
214
|
+
const merged = {};
|
|
215
|
+
for (const [name, groups] of Object.entries(existing || {})) {
|
|
216
|
+
if (Array.isArray(groups)) merged[name] = [...groups];
|
|
217
|
+
}
|
|
218
|
+
for (const [name, groups] of Object.entries(additions || {})) {
|
|
219
|
+
if (!Array.isArray(groups) || !groups.length) continue;
|
|
220
|
+
merged[name] = [...(merged[name] || []), ...groups];
|
|
221
|
+
}
|
|
222
|
+
return merged;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function objectInput(value) {
|
|
226
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function parseMcpToolName(toolName) {
|
|
230
|
+
const name = String(toolName || "");
|
|
231
|
+
if (!name.startsWith("mcp__")) return null;
|
|
232
|
+
const rest = name.slice(5);
|
|
233
|
+
const sep = rest.indexOf("__");
|
|
234
|
+
if (sep <= 0) return null;
|
|
235
|
+
return {
|
|
236
|
+
serverName: rest.slice(0, sep),
|
|
237
|
+
toolName: rest.slice(sep + 2),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function normalizeClaudeMcpInput(input, qaOutputDir) {
|
|
242
|
+
const parsed = parseMcpToolName(input?.tool_name);
|
|
243
|
+
if (!parsed) return null;
|
|
244
|
+
const current = objectInput(input?.tool_input);
|
|
245
|
+
const normalized = normalizeMcpToolParams(parsed.serverName, parsed.toolName, current, { qaOutputDir });
|
|
246
|
+
if (normalized === current) return null;
|
|
247
|
+
try {
|
|
248
|
+
if (JSON.stringify(normalized) === JSON.stringify(current)) return null;
|
|
249
|
+
} catch {
|
|
250
|
+
// Non-serializable input is unexpected, but returning an SDK override is
|
|
251
|
+
// still safe when the normalizer produced a different object.
|
|
252
|
+
}
|
|
253
|
+
return normalized;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function claudeToolResponseBlocks(toolResponse) {
|
|
257
|
+
if (toolResponse && typeof toolResponse === "object" && Array.isArray(toolResponse.content)) {
|
|
258
|
+
return toolResponse.content;
|
|
259
|
+
}
|
|
260
|
+
if (typeof toolResponse === "string") return [{ type: "text", text: toolResponse }];
|
|
261
|
+
if (toolResponse == null) return [];
|
|
262
|
+
try {
|
|
263
|
+
return [{ type: "text", text: JSON.stringify(toolResponse) }];
|
|
264
|
+
} catch {
|
|
265
|
+
return [{ type: "text", text: String(toolResponse) }];
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function toolPayloadLimit(options) {
|
|
270
|
+
const explicit = Number(options.toolPayloadMaxBytes);
|
|
271
|
+
if (Number.isFinite(explicit) && explicit > 0) return Math.floor(explicit);
|
|
272
|
+
const configured = Number(options.settings?.agent_tool_payload_max_bytes);
|
|
273
|
+
if (Number.isFinite(configured) && configured > 0) return Math.floor(configured);
|
|
274
|
+
return MAX_TOOL_RESULT_BYTES;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function claudeEditPath(toolName, toolInput) {
|
|
278
|
+
const input = objectInput(toolInput);
|
|
279
|
+
if (toolName === "NotebookEdit") return input.notebook_path || input.file_path || "";
|
|
280
|
+
return input.file_path || "";
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function claudeEditKind(toolName, before) {
|
|
284
|
+
if (toolName === "Write" && before && !before.exists) return "add";
|
|
285
|
+
return "update";
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function fileEditStateKey(input, toolUseID, path) {
|
|
289
|
+
return toolUseID || input?.tool_use_id || input?.toolUseID || `${input?.tool_name || "file_edit"}:${path}`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function fileEditPayload(change, { status, before, after, error } = {}) {
|
|
293
|
+
const lineStats = statsForCompletedChange(change, before, after);
|
|
294
|
+
const completedChange = lineStats ? { ...change, line_stats: lineStats } : change;
|
|
295
|
+
const summary = fileChangeSummary([completedChange]);
|
|
296
|
+
return {
|
|
297
|
+
changes: [completedChange],
|
|
298
|
+
status,
|
|
299
|
+
...(summary ? { summary } : {}),
|
|
300
|
+
...(error ? { error } : {}),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function createClaudeFileEditHooks({ cwd, emitEvent }) {
|
|
305
|
+
const edits = new Map();
|
|
306
|
+
const runCwd = cwd || process.cwd();
|
|
307
|
+
|
|
308
|
+
function createState(input, toolUseID, { readBefore = true } = {}) {
|
|
309
|
+
const toolName = input?.tool_name;
|
|
310
|
+
const path = claudeEditPath(toolName, input?.tool_input);
|
|
311
|
+
if (!path) return null;
|
|
312
|
+
const resolvedPath = resolve(runCwd, path);
|
|
313
|
+
const key = fileEditStateKey(input, toolUseID, resolvedPath);
|
|
314
|
+
const before = readBefore ? readFileChangeSnapshot(resolvedPath) : null;
|
|
315
|
+
return {
|
|
316
|
+
key,
|
|
317
|
+
id: `file_edit:${key}`,
|
|
318
|
+
path: resolvedPath,
|
|
319
|
+
change: { path: resolvedPath, kind: claudeEditKind(toolName, before) },
|
|
320
|
+
before,
|
|
321
|
+
started: false,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function emitStart(state) {
|
|
326
|
+
if (!state || state.started) return;
|
|
327
|
+
emitEvent(createFileEditToolUseEvent(state.id, {
|
|
328
|
+
changes: [state.change],
|
|
329
|
+
status: "in_progress",
|
|
330
|
+
}));
|
|
331
|
+
state.started = true;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function complete(input, toolUseID, { status, error } = {}) {
|
|
335
|
+
const directKey = toolUseID || input?.tool_use_id || input?.toolUseID;
|
|
336
|
+
const fallback = createState(input, toolUseID, { readBefore: false });
|
|
337
|
+
const state = (directKey && edits.get(directKey)) || (fallback?.key && edits.get(fallback.key)) || fallback;
|
|
338
|
+
if (!state) return;
|
|
339
|
+
emitStart(state);
|
|
340
|
+
const after = readFileChangeSnapshot(state.path);
|
|
341
|
+
const payload = fileEditPayload(state.change, {
|
|
342
|
+
status,
|
|
343
|
+
before: state.before,
|
|
344
|
+
after,
|
|
345
|
+
error,
|
|
346
|
+
});
|
|
347
|
+
emitEvent(createFileEditToolResultEvent(state.id, payload, { isError: status === "failed" }));
|
|
348
|
+
edits.delete(state.key);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
PreToolUse: [{
|
|
353
|
+
matcher: CLAUDE_FILE_EDIT_MATCHER,
|
|
354
|
+
hooks: [async (input, toolUseID) => {
|
|
355
|
+
const state = createState(input, toolUseID);
|
|
356
|
+
if (!state) return {};
|
|
357
|
+
edits.set(state.key, state);
|
|
358
|
+
emitStart(state);
|
|
359
|
+
return {};
|
|
360
|
+
}],
|
|
361
|
+
}],
|
|
362
|
+
PostToolUse: [{
|
|
363
|
+
matcher: CLAUDE_FILE_EDIT_MATCHER,
|
|
364
|
+
hooks: [async (input, toolUseID) => {
|
|
365
|
+
complete(input, toolUseID, { status: "completed" });
|
|
366
|
+
return {};
|
|
367
|
+
}],
|
|
368
|
+
}],
|
|
369
|
+
PostToolUseFailure: [{
|
|
370
|
+
matcher: CLAUDE_FILE_EDIT_MATCHER,
|
|
371
|
+
hooks: [async (input, toolUseID) => {
|
|
372
|
+
complete(input, toolUseID, {
|
|
373
|
+
status: "failed",
|
|
374
|
+
error: stringifyError(input?.error) || "tool failed",
|
|
375
|
+
});
|
|
376
|
+
return {};
|
|
377
|
+
}],
|
|
378
|
+
}],
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function createClaudeRuntimeHooks({
|
|
383
|
+
cwd,
|
|
384
|
+
emitEvent,
|
|
385
|
+
persistArtifact,
|
|
386
|
+
qaOutputDir,
|
|
387
|
+
toolPayloadMaxBytes,
|
|
388
|
+
onToolUse,
|
|
389
|
+
onToolResult,
|
|
390
|
+
}) {
|
|
391
|
+
return mergeHookMatchers(createClaudeFileEditHooks({ cwd, emitEvent }), {
|
|
392
|
+
PreToolUse: [{
|
|
393
|
+
matcher: "*",
|
|
394
|
+
hooks: [async (input) => {
|
|
395
|
+
onToolUse?.(input?.tool_name);
|
|
396
|
+
const updatedInput = normalizeClaudeMcpInput(input, qaOutputDir);
|
|
397
|
+
if (!updatedInput) return {};
|
|
398
|
+
return {
|
|
399
|
+
continue: true,
|
|
400
|
+
hookSpecificOutput: {
|
|
401
|
+
hookEventName: "PreToolUse",
|
|
402
|
+
updatedInput,
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
}],
|
|
406
|
+
}],
|
|
407
|
+
PostToolUse: [{
|
|
408
|
+
matcher: "*",
|
|
409
|
+
hooks: [async (input, toolUseID) => {
|
|
410
|
+
const toolName = input?.tool_name || "tool";
|
|
411
|
+
onToolUse?.(toolName);
|
|
412
|
+
onToolResult?.(toolName);
|
|
413
|
+
const blocks = claudeToolResponseBlocks(input?.tool_response);
|
|
414
|
+
if (!blocks.length) return {};
|
|
415
|
+
const summary = summarisePayload(toolName, blocks, persistArtifact, {
|
|
416
|
+
maxBytes: toolPayloadMaxBytes,
|
|
417
|
+
toolUseId: toolUseID || input?.tool_use_id || input?.toolUseID || null,
|
|
418
|
+
});
|
|
419
|
+
if (!summary.truncated) return {};
|
|
420
|
+
emitEvent({
|
|
421
|
+
type: "runtime_warning",
|
|
422
|
+
warning_kind: "tool_payload_truncated",
|
|
423
|
+
source: "tool_bloat_guard",
|
|
424
|
+
tool: toolName,
|
|
425
|
+
tool_use_id: toolUseID || input?.tool_use_id || input?.toolUseID || null,
|
|
426
|
+
original_bytes: summary.originalBytes,
|
|
427
|
+
max_bytes: toolPayloadMaxBytes,
|
|
428
|
+
saved_paths: summary.savedPaths,
|
|
429
|
+
});
|
|
430
|
+
return {
|
|
431
|
+
continue: true,
|
|
432
|
+
hookSpecificOutput: {
|
|
433
|
+
hookEventName: "PostToolUse",
|
|
434
|
+
updatedMCPToolOutput: summary.rewrittenBlocks,
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
}],
|
|
438
|
+
}],
|
|
439
|
+
PostToolUseFailure: [{
|
|
440
|
+
matcher: "*",
|
|
441
|
+
hooks: [async (input) => {
|
|
442
|
+
onToolUse?.(input?.tool_name);
|
|
443
|
+
return {};
|
|
444
|
+
}],
|
|
445
|
+
}],
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function promptStringFromMessages(messages) {
|
|
450
|
+
return Array.isArray(messages)
|
|
451
|
+
? messages.filter(m => m.role === "user").map(m => typeof m.content === "string" ? m.content : JSON.stringify(m.content)).join("\n")
|
|
452
|
+
: String(messages || "");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function makeSdkUserMessage(body, sessionId, uuid = randomUUID()) {
|
|
456
|
+
return {
|
|
457
|
+
type: "user",
|
|
458
|
+
session_id: sessionId,
|
|
459
|
+
parent_tool_use_id: null,
|
|
460
|
+
uuid,
|
|
461
|
+
message: {
|
|
462
|
+
role: "user",
|
|
463
|
+
content: body,
|
|
464
|
+
},
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function createClaudeCanUseTool(approvalManager, modelName) {
|
|
469
|
+
return async function canUseTool(toolName, input, context = {}) {
|
|
470
|
+
const decision = await approvalManager.request({
|
|
471
|
+
toolName,
|
|
472
|
+
input,
|
|
473
|
+
model: modelName,
|
|
474
|
+
toolUseId: context?.toolUseId || context?.tool_use_id || null,
|
|
475
|
+
});
|
|
476
|
+
if (decision.decision === "deny") {
|
|
477
|
+
return {
|
|
478
|
+
behavior: "deny",
|
|
479
|
+
message: `Tool ${toolName} denied by host approval gate (${decision.reason || "no reason"})`,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
return { behavior: "allow", updatedInput: input };
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
async function* livePromptMessages({ initialPrompt, liveInput, sessionId }) {
|
|
487
|
+
yield makeSdkUserMessage(initialPrompt, sessionId);
|
|
488
|
+
for await (const message of liveInput) {
|
|
489
|
+
yield makeSdkUserMessage(formatLiveInputGuidance(message.body), sessionId, message.id || randomUUID());
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export async function generateClaudeResponse(systemPrompt, options) {
|
|
494
|
+
const {
|
|
495
|
+
messages,
|
|
496
|
+
model,
|
|
497
|
+
effort = "medium",
|
|
498
|
+
cwd,
|
|
499
|
+
mcpServers,
|
|
500
|
+
allowedTools,
|
|
501
|
+
disallowedTools,
|
|
502
|
+
hooks,
|
|
503
|
+
permissionMode = "bypassPermissions",
|
|
504
|
+
maxTurns,
|
|
505
|
+
abortSignal,
|
|
506
|
+
onEvent = () => {},
|
|
507
|
+
} = options;
|
|
508
|
+
|
|
509
|
+
const thinkingOpts = thinkingForEffort(effort);
|
|
510
|
+
|
|
511
|
+
const promptString = promptStringFromMessages(messages);
|
|
512
|
+
const runtimeWarnings = [];
|
|
513
|
+
const capturedEvents = [];
|
|
514
|
+
const assistantTextFragments = [];
|
|
515
|
+
const reusableProviderSessionId = pickSessionId(options.sessionId, options.providerSessionId);
|
|
516
|
+
const persistArtifact = options.persistArtifact || null;
|
|
517
|
+
const qaOutputDir = options.qaOutputDir || options.runArtifactDir || null;
|
|
518
|
+
const toolPayloadMaxBytes = toolPayloadLimit(options);
|
|
519
|
+
let providerSessionId = reusableProviderSessionId;
|
|
520
|
+
let lastToolName = null;
|
|
521
|
+
let toolResultsSeen = 0;
|
|
522
|
+
|
|
523
|
+
function emitEvent(event) {
|
|
524
|
+
if (!event) return;
|
|
525
|
+
capturedEvents.push(event);
|
|
526
|
+
onEvent(event);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function noteToolUse(toolName) {
|
|
530
|
+
if (toolName) lastToolName = toolName;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function noteToolResult(toolName) {
|
|
534
|
+
if (toolName) lastToolName = toolName;
|
|
535
|
+
toolResultsSeen += 1;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const approvalManager = options.onToolApprovalRequest
|
|
539
|
+
? createApprovalManager({
|
|
540
|
+
onToolApprovalRequest: options.onToolApprovalRequest,
|
|
541
|
+
defaultRiskTier: options.approvalDefaultRiskTier,
|
|
542
|
+
timeoutMs: options.approvalTimeoutMs,
|
|
543
|
+
onEvent: emitEvent,
|
|
544
|
+
riskTiersByTool: options.toolRiskTiers,
|
|
545
|
+
alwaysAllowTools: options.approvalAlwaysAllowTools,
|
|
546
|
+
})
|
|
547
|
+
: null;
|
|
548
|
+
// The Claude SDK invokes `canUseTool` only when permissionMode opts out of
|
|
549
|
+
// bypass. When the host enabled approval gates, force the SDK out of
|
|
550
|
+
// bypass; otherwise the callback would be skipped silently.
|
|
551
|
+
const effectivePermissionMode = approvalManager && permissionMode === "bypassPermissions"
|
|
552
|
+
? "default"
|
|
553
|
+
: permissionMode;
|
|
554
|
+
const nativeAgents = claudeNativeAgentDefinitions(options.nativeSubagents);
|
|
555
|
+
const queryOptions = {
|
|
556
|
+
systemPrompt,
|
|
557
|
+
model: modelWithContextWindow(model.model, options.contextWindow),
|
|
558
|
+
cwd,
|
|
559
|
+
permissionMode: effectivePermissionMode,
|
|
560
|
+
...(effectivePermissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {}),
|
|
561
|
+
allowedTools: claudeToolsWithNativeSubagents(allowedTools, options.nativeSubagents),
|
|
562
|
+
disallowedTools,
|
|
563
|
+
mcpServers,
|
|
564
|
+
...(approvalManager ? { canUseTool: createClaudeCanUseTool(approvalManager, model.model) } : {}),
|
|
565
|
+
...(nativeAgents ? { agents: nativeAgents } : {}),
|
|
566
|
+
hooks: mergeHookMatchers(hooks, createClaudeRuntimeHooks({
|
|
567
|
+
cwd,
|
|
568
|
+
emitEvent,
|
|
569
|
+
persistArtifact,
|
|
570
|
+
qaOutputDir,
|
|
571
|
+
toolPayloadMaxBytes,
|
|
572
|
+
onToolUse: noteToolUse,
|
|
573
|
+
onToolResult: noteToolResult,
|
|
574
|
+
})),
|
|
575
|
+
...thinkingOpts,
|
|
576
|
+
};
|
|
577
|
+
if (options.outputSchema) {
|
|
578
|
+
queryOptions.outputFormat = {
|
|
579
|
+
type: "json_schema",
|
|
580
|
+
schema: options.outputSchema,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
if (reusableProviderSessionId) {
|
|
584
|
+
queryOptions.resume = reusableProviderSessionId;
|
|
585
|
+
}
|
|
586
|
+
if (Number.isFinite(Number(maxTurns)) && Number(maxTurns) > 0) {
|
|
587
|
+
queryOptions.maxTurns = Number(maxTurns);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const prompt = options.liveInput
|
|
591
|
+
? livePromptMessages({ initialPrompt: promptString, liveInput: options.liveInput, sessionId: reusableProviderSessionId || randomUUID() })
|
|
592
|
+
: promptString;
|
|
593
|
+
const providerRequestStartedAt = Date.now();
|
|
594
|
+
emitEvent({
|
|
595
|
+
type: "provider_request_started",
|
|
596
|
+
sdk: "claude",
|
|
597
|
+
model: model.model,
|
|
598
|
+
runtime: "sdk",
|
|
599
|
+
timestamp: providerRequestStartedAt,
|
|
600
|
+
});
|
|
601
|
+
const stream = query({ prompt, options: queryOptions });
|
|
602
|
+
|
|
603
|
+
let text = "";
|
|
604
|
+
let usage = {};
|
|
605
|
+
let durationMs = 0;
|
|
606
|
+
let numTurns = 0;
|
|
607
|
+
let resultText = "";
|
|
608
|
+
let cancelled = false;
|
|
609
|
+
let errorMessage = null;
|
|
610
|
+
let failureKind = null;
|
|
611
|
+
let successfulResultSeen = false;
|
|
612
|
+
let postSuccessErrorSeen = false;
|
|
613
|
+
let structuredResultSource = null;
|
|
614
|
+
let structuredResult = undefined;
|
|
615
|
+
let errorDetails = null;
|
|
616
|
+
let lastStructuredOutputRejection = null;
|
|
617
|
+
const pendingStructuredOutputById = new Map();
|
|
618
|
+
|
|
619
|
+
const rawFinalText = () => resultText || text;
|
|
620
|
+
|
|
621
|
+
function hasUsableFinalOutput() {
|
|
622
|
+
return structuredResult !== undefined || String(rawFinalText() || "").trim().length > 0;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function hasPreservableFinalOutput() {
|
|
626
|
+
return successfulResultSeen ? hasUsableFinalOutput() : structuredResult !== undefined;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function preservePostSuccessError(message) {
|
|
630
|
+
if (postSuccessErrorSeen) return;
|
|
631
|
+
postSuccessErrorSeen = true;
|
|
632
|
+
runtimeWarnings.push(makeRuntimeWarning(message));
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const abortHandler = async () => {
|
|
636
|
+
cancelled = true;
|
|
637
|
+
if (stream.return) await stream.return();
|
|
638
|
+
};
|
|
639
|
+
if (abortSignal) {
|
|
640
|
+
if (abortSignal.aborted) await abortHandler();
|
|
641
|
+
else abortSignal.addEventListener("abort", abortHandler, { once: true });
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
try {
|
|
645
|
+
for await (const event of stream) {
|
|
646
|
+
const nextSessionId = sessionIdFromEvent(event);
|
|
647
|
+
if (nextSessionId) providerSessionId = nextSessionId;
|
|
648
|
+
emitEvent(event);
|
|
649
|
+
if (event?.type === "tool_progress" && event.tool_name) noteToolUse(event.tool_name);
|
|
650
|
+
for (const toolUse of structuredOutputToolUses(event)) {
|
|
651
|
+
pendingStructuredOutputById.set(toolUse.id, toolUse.input);
|
|
652
|
+
}
|
|
653
|
+
const structuredOutputRejection = structuredOutputRejectionFromEvent(event);
|
|
654
|
+
if (structuredOutputRejection) lastStructuredOutputRejection = structuredOutputRejection;
|
|
655
|
+
for (const acceptedStructuredOutput of acceptedStructuredOutputValues(event, pendingStructuredOutputById)) {
|
|
656
|
+
structuredResult = acceptedStructuredOutput;
|
|
657
|
+
structuredResultSource = "StructuredOutput";
|
|
658
|
+
emitEvent({
|
|
659
|
+
...structuredOutputEvent(acceptedStructuredOutput),
|
|
660
|
+
source: "StructuredOutput",
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
const eventStructuredOutput = extractStructuredOutput(event);
|
|
664
|
+
if (eventStructuredOutput !== undefined) {
|
|
665
|
+
structuredResult = eventStructuredOutput;
|
|
666
|
+
structuredResultSource = "structured_output";
|
|
667
|
+
emitEvent(structuredOutputEvent(eventStructuredOutput));
|
|
668
|
+
}
|
|
669
|
+
if (event.type === "assistant") {
|
|
670
|
+
const delta = extractText(event);
|
|
671
|
+
if (delta) assistantTextFragments.push(delta);
|
|
672
|
+
text += delta;
|
|
673
|
+
for (const toolName of assistantToolNames(event)) noteToolUse(toolName);
|
|
674
|
+
}
|
|
675
|
+
else if (event.type === "error") {
|
|
676
|
+
const message = event.error?.message || event.error || "sdk stream error";
|
|
677
|
+
if (hasPreservableFinalOutput()) {
|
|
678
|
+
preservePostSuccessError(`Claude SDK emitted an error after final output; preserved final result. ${message}`);
|
|
679
|
+
} else {
|
|
680
|
+
errorMessage = message;
|
|
681
|
+
failureKind = "provider_unavailable";
|
|
682
|
+
errorDetails = buildClaudeErrorDetails({
|
|
683
|
+
event,
|
|
684
|
+
subtype: "error",
|
|
685
|
+
providerSessionId,
|
|
686
|
+
assistantTexts: assistantTextFragments,
|
|
687
|
+
lastToolName,
|
|
688
|
+
toolResultsSeen,
|
|
689
|
+
numTurns,
|
|
690
|
+
lastStructuredOutputRejection,
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
break;
|
|
694
|
+
} else if (event.type === "result") {
|
|
695
|
+
const resultError = resultEventError(event);
|
|
696
|
+
if (resultError) {
|
|
697
|
+
if (!successfulResultSeen) {
|
|
698
|
+
usage = event.usage || usage;
|
|
699
|
+
durationMs = event.duration_ms || durationMs;
|
|
700
|
+
numTurns = event.num_turns || numTurns;
|
|
701
|
+
}
|
|
702
|
+
if (hasPreservableFinalOutput()) {
|
|
703
|
+
preservePostSuccessError(`Claude SDK emitted an error after final output; preserved final result. ${resultError.message}`);
|
|
704
|
+
successfulResultSeen = true;
|
|
705
|
+
} else {
|
|
706
|
+
usage = event.usage || usage;
|
|
707
|
+
durationMs = event.duration_ms || durationMs;
|
|
708
|
+
numTurns = event.num_turns || numTurns;
|
|
709
|
+
errorMessage = resultError.message;
|
|
710
|
+
failureKind = resultError.failureKind;
|
|
711
|
+
if (failureKind === "invalid_result") {
|
|
712
|
+
runtimeWarnings.push(makeRuntimeWarning(
|
|
713
|
+
resultError.message,
|
|
714
|
+
`${readRuntimeBrand().schemaPrefix}_result_validation`,
|
|
715
|
+
));
|
|
716
|
+
}
|
|
717
|
+
errorDetails = buildClaudeErrorDetails({
|
|
718
|
+
event,
|
|
719
|
+
subtype: event.subtype || "result_error",
|
|
720
|
+
providerSessionId,
|
|
721
|
+
assistantTexts: assistantTextFragments,
|
|
722
|
+
lastToolName,
|
|
723
|
+
toolResultsSeen,
|
|
724
|
+
numTurns,
|
|
725
|
+
lastStructuredOutputRejection,
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
} else {
|
|
729
|
+
usage = event.usage || usage;
|
|
730
|
+
durationMs = event.duration_ms || durationMs;
|
|
731
|
+
numTurns = event.num_turns || numTurns;
|
|
732
|
+
resultText = extractResultText(event) || resultText;
|
|
733
|
+
successfulResultSeen = true;
|
|
734
|
+
}
|
|
735
|
+
if (options.liveInput) break;
|
|
736
|
+
}
|
|
737
|
+
if (cancelled) break;
|
|
738
|
+
}
|
|
739
|
+
} catch (err) {
|
|
740
|
+
if (!cancelled) {
|
|
741
|
+
const message = err?.message || String(err);
|
|
742
|
+
if (successfulResultSeen && hasUsableFinalOutput()) {
|
|
743
|
+
preservePostSuccessError(`Claude SDK stream failed after final output; preserved final result. ${message}`);
|
|
744
|
+
} else {
|
|
745
|
+
errorMessage = message;
|
|
746
|
+
failureKind = "provider_unavailable";
|
|
747
|
+
errorDetails = buildClaudeErrorDetails({
|
|
748
|
+
subtype: "exception",
|
|
749
|
+
providerSessionId,
|
|
750
|
+
assistantTexts: assistantTextFragments,
|
|
751
|
+
lastToolName,
|
|
752
|
+
toolResultsSeen,
|
|
753
|
+
numTurns,
|
|
754
|
+
lastStructuredOutputRejection,
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
} finally {
|
|
759
|
+
if (abortSignal) abortSignal.removeEventListener?.("abort", abortHandler);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const reference = model.reference || `claude:${model.model}`;
|
|
763
|
+
const inputTokens = usage?.input_tokens ?? usage?.inputTokens ?? 0;
|
|
764
|
+
const outputTokens = usage?.output_tokens ?? usage?.outputTokens ?? 0;
|
|
765
|
+
const cachedTokens = usage?.cache_read_input_tokens ?? usage?.cache_read_tokens ?? 0;
|
|
766
|
+
const cacheCreationTokens = usage?.cache_creation_input_tokens ?? usage?.cache_creation_tokens ?? 0;
|
|
767
|
+
const costUsd = estimateCost({
|
|
768
|
+
resolveCustomPricing: options.resolveCustomPricing,
|
|
769
|
+
model: reference,
|
|
770
|
+
inputTokens,
|
|
771
|
+
outputTokens,
|
|
772
|
+
cachedTokens,
|
|
773
|
+
cacheWriteTokens: cacheCreationTokens,
|
|
774
|
+
});
|
|
775
|
+
const enrichedUsage = {
|
|
776
|
+
...usage,
|
|
777
|
+
input_tokens: inputTokens || null,
|
|
778
|
+
output_tokens: outputTokens || null,
|
|
779
|
+
cache_read_tokens: cachedTokens || null,
|
|
780
|
+
cache_creation_tokens: cacheCreationTokens || null,
|
|
781
|
+
cost_usd: costUsd,
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
emitEvent({
|
|
785
|
+
type: "provider_request_completed",
|
|
786
|
+
sdk: "claude",
|
|
787
|
+
model: model.model,
|
|
788
|
+
runtime: "sdk",
|
|
789
|
+
timestamp: Date.now(),
|
|
790
|
+
durationMs: Date.now() - providerRequestStartedAt,
|
|
791
|
+
failureKind,
|
|
792
|
+
cancelled,
|
|
793
|
+
});
|
|
794
|
+
if (cachedTokens > 0) {
|
|
795
|
+
emitEvent({ type: "cache_hit", sdk: "claude", model: model.model, tokens: cachedTokens, source: "anthropic_prompt_cache" });
|
|
796
|
+
}
|
|
797
|
+
if (cacheCreationTokens > 0) {
|
|
798
|
+
emitEvent({ type: "cache_miss", sdk: "claude", model: model.model, tokens: cacheCreationTokens, source: "anthropic_prompt_cache" });
|
|
799
|
+
}
|
|
800
|
+
emitEvent({
|
|
801
|
+
type: "cost_accumulated",
|
|
802
|
+
sdk: "claude",
|
|
803
|
+
model: model.model,
|
|
804
|
+
cumulativeUsd: costUsd ?? 0,
|
|
805
|
+
tokens: {
|
|
806
|
+
input: inputTokens,
|
|
807
|
+
output: outputTokens,
|
|
808
|
+
cacheReadTokens: cachedTokens,
|
|
809
|
+
cacheCreationTokens,
|
|
810
|
+
},
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
const configuredSubagents = Array.isArray(options.nativeSubagents?.teammates)
|
|
814
|
+
? options.nativeSubagents.teammates.map((entry) => entry?.name).filter(Boolean)
|
|
815
|
+
: [];
|
|
816
|
+
const capabilitiesUsed = buildCapabilitiesUsed({
|
|
817
|
+
promptCacheActive: cachedTokens > 0 || cacheCreationTokens > 0,
|
|
818
|
+
thinkingEnabled: effort !== "low",
|
|
819
|
+
structuredOutputEnforced: !!options.outputSchema,
|
|
820
|
+
// Claude SDK doesn't surface a per-call "subagent was invoked" signal,
|
|
821
|
+
// so we report null when subagents were configured (unknown) and false
|
|
822
|
+
// when none were configured (definitely not).
|
|
823
|
+
subagentInvoked: configuredSubagents.length > 0 ? null : false,
|
|
824
|
+
mcpServersUsed: Object.keys(mcpServers || {}),
|
|
825
|
+
nativeSubagentsUsed: configuredSubagents,
|
|
826
|
+
toolCompactionApplied: toolCompactionAppliedFromWarnings(runtimeWarnings),
|
|
827
|
+
contextCompactionApplied: null, // Claude SDK doesn't use the runtime compaction layer
|
|
828
|
+
});
|
|
829
|
+
emitEvent({ type: "capabilities_resolved", sdk: "claude", model: model.model, capabilitiesUsed });
|
|
830
|
+
|
|
831
|
+
return {
|
|
832
|
+
text: rawFinalText(),
|
|
833
|
+
structuredResult,
|
|
834
|
+
structuredResultSource,
|
|
835
|
+
events: capturedEvents,
|
|
836
|
+
usage: enrichedUsage,
|
|
837
|
+
durationMs,
|
|
838
|
+
numTurns,
|
|
839
|
+
model: model.model,
|
|
840
|
+
effort,
|
|
841
|
+
sdk: "claude",
|
|
842
|
+
cancelled,
|
|
843
|
+
error: errorMessage,
|
|
844
|
+
errorDetails,
|
|
845
|
+
failureKind,
|
|
846
|
+
providerSessionId,
|
|
847
|
+
runtimeWarnings,
|
|
848
|
+
capabilitiesUsed,
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
export const claudeSdkBackend = {
|
|
853
|
+
kind: "claude",
|
|
854
|
+
capabilities: runtimeCapabilities("claude"),
|
|
855
|
+
execute: generateClaudeResponse,
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
export const claudeRuntimeBridge = {
|
|
859
|
+
id: "claude",
|
|
860
|
+
kind: "claude",
|
|
861
|
+
capabilities: runtimeCapabilities("claude"),
|
|
862
|
+
supports: (ref) => ref?.sdk === "claude",
|
|
863
|
+
execute: generateClaudeResponse,
|
|
864
|
+
};
|