@mono-agent/agent-runtime 0.20.11 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/ARCHITECTURE.md +50 -11
  2. package/MIGRATION.md +288 -26
  3. package/README.md +352 -477
  4. package/package.json +13 -44
  5. package/src/agent/tool-bloat.js +145 -9
  6. package/src/agent/tools/agent-tool.js +108 -9
  7. package/src/agent/tools/bash.js +11 -26
  8. package/src/agent/tools/codex-subscription-search.js +123 -29
  9. package/src/agent/tools/exec.js +10 -2
  10. package/src/agent/tools/index.js +7 -0
  11. package/src/agent/tools/monitor.js +149 -0
  12. package/src/agent/tools/pi-bridge.js +123 -19
  13. package/src/agent/tools/shared/bash-environment.js +31 -0
  14. package/src/agent/tools/shared/monitors.js +293 -0
  15. package/src/agent/tools/shared/path-resolver.js +25 -6
  16. package/src/agent/tools/shared/process-jobs.js +6 -1
  17. package/src/agent/tools/shared/process-runner.js +26 -6
  18. package/src/agent/tools/shared/tool-context.js +8 -0
  19. package/src/agent/tools/web-access-interstitial.js +70 -0
  20. package/src/agent/tools/web-browser-render.js +83 -58
  21. package/src/agent/tools/web-controller.js +112 -21
  22. package/src/agent/tools/web-document-extractor.js +379 -0
  23. package/src/agent/tools/web-fetch.js +271 -243
  24. package/src/agent/tools/web-request.js +65 -0
  25. package/src/agent/tools/web-search-output.js +165 -0
  26. package/src/agent/tools/web-search-state.js +75 -0
  27. package/src/agent/tools/web-search.js +532 -71
  28. package/src/ai/cost.js +13 -68
  29. package/src/ai/failure.js +3 -3
  30. package/src/ai/index.js +5 -17
  31. package/src/ai/observer.js +8 -0
  32. package/src/ai/pi-interop.js +221 -1
  33. package/src/ai/pi-oauth-compat.js +1 -1
  34. package/src/ai/provider-check.js +131 -0
  35. package/src/ai/providers/codex/app-server-client.js +592 -0
  36. package/src/ai/providers/pi-models.js +18 -10
  37. package/src/ai/providers/pi-native/compaction-driver.js +94 -42
  38. package/src/ai/providers/pi-native/compaction-summary.js +140 -0
  39. package/src/ai/providers/pi-native/harness-adapter.js +376 -0
  40. package/src/ai/providers/pi-native/prompt-cache-diagnostics.js +103 -0
  41. package/src/ai/providers/pi-native/provider-attribution.js +102 -0
  42. package/src/ai/providers/pi-native/result-builder.js +38 -14
  43. package/src/ai/providers/pi-native/session-lifecycle.js +253 -55
  44. package/src/ai/providers/pi-native/stream-subscriber.js +52 -6
  45. package/src/ai/providers/pi-native/terminal-recovery.js +40 -0
  46. package/src/ai/providers/pi-native/turn-runner.js +279 -28
  47. package/src/ai/providers/pi-native.js +206 -61
  48. package/src/ai/runtime/capabilities.js +11 -56
  49. package/src/ai/runtime/live-input-events.js +250 -54
  50. package/src/ai/runtime/model-refs.js +118 -153
  51. package/src/ai/runtime/registry.js +22 -56
  52. package/src/ai/runtime/router.js +76 -417
  53. package/src/ai/runtime/session-liveness.js +3 -4
  54. package/src/ai/runtime/sessions.js +4 -5
  55. package/src/ai/runtime/tool-policy.js +0 -2
  56. package/src/ai/tool-lifecycle.js +32 -18
  57. package/src/ai/types.js +37 -112
  58. package/src/index.js +0 -6
  59. package/src/runtime.js +29 -16
  60. package/types/agent/tool-bloat.d.ts +1 -1
  61. package/types/agent/tools/agent-tool.d.ts +4 -2
  62. package/types/agent/tools/bash.d.ts +5 -3
  63. package/types/agent/tools/codex-subscription-search.d.ts +7 -3
  64. package/types/agent/tools/exec.d.ts +5 -3
  65. package/types/agent/tools/index.d.ts +1 -0
  66. package/types/agent/tools/monitor.d.ts +47 -0
  67. package/types/agent/tools/pi-bridge.d.ts +7 -4
  68. package/types/agent/tools/shared/bash-environment.d.ts +4 -0
  69. package/types/agent/tools/shared/monitors.d.ts +98 -0
  70. package/types/agent/tools/shared/process-jobs.d.ts +5 -1
  71. package/types/agent/tools/shared/process-runner.d.ts +14 -4
  72. package/types/agent/tools/shared/tool-context.d.ts +2 -0
  73. package/types/agent/tools/web-access-interstitial.d.ts +23 -0
  74. package/types/agent/tools/web-browser-render.d.ts +4 -1
  75. package/types/agent/tools/web-controller.d.ts +4 -2
  76. package/types/agent/tools/web-document-extractor.d.ts +27 -0
  77. package/types/agent/tools/web-fetch.d.ts +19 -24
  78. package/types/agent/tools/web-request.d.ts +20 -0
  79. package/types/agent/tools/web-search-output.d.ts +31 -0
  80. package/types/agent/tools/web-search-state.d.ts +21 -0
  81. package/types/agent/tools/web-search.d.ts +10 -45
  82. package/types/ai/cost.d.ts +1 -2
  83. package/types/ai/index.d.ts +2 -4
  84. package/types/ai/observer.d.ts +6 -0
  85. package/types/ai/pi-interop.d.ts +81 -0
  86. package/types/ai/provider-check.d.ts +53 -0
  87. package/types/ai/providers/codex/app-server-client.d.ts +37 -0
  88. package/types/ai/providers/pi-native/compaction-driver.d.ts +2 -1
  89. package/types/ai/providers/pi-native/compaction-summary.d.ts +19 -0
  90. package/types/ai/providers/pi-native/harness-adapter.d.ts +58 -0
  91. package/types/ai/providers/pi-native/prompt-cache-diagnostics.d.ts +3 -0
  92. package/types/ai/providers/pi-native/provider-attribution.d.ts +26 -0
  93. package/types/ai/providers/pi-native/result-builder.d.ts +14 -4
  94. package/types/ai/providers/pi-native/session-lifecycle.d.ts +25 -6
  95. package/types/ai/providers/pi-native/stream-subscriber.d.ts +2 -2
  96. package/types/ai/providers/pi-native/terminal-recovery.d.ts +2 -0
  97. package/types/ai/providers/pi-native/turn-runner.d.ts +68 -10
  98. package/types/ai/providers/pi-native.d.ts +21 -4
  99. package/types/ai/runtime/capabilities.d.ts +21 -70
  100. package/types/ai/runtime/live-input-events.d.ts +32 -8
  101. package/types/ai/runtime/model-refs.d.ts +0 -24
  102. package/types/ai/runtime/router.d.ts +3 -10
  103. package/types/ai/runtime/tool-policy.d.ts +0 -2
  104. package/types/ai/tool-lifecycle.d.ts +4 -3
  105. package/types/ai/types.d.ts +162 -256
  106. package/types/index.d.ts +0 -1
  107. package/src/ai/providers/acp-client.js +0 -1149
  108. package/src/ai/providers/acp-privacy.js +0 -124
  109. package/src/ai/providers/acp-public.js +0 -21
  110. package/src/ai/providers/acp-session-tokens.js +0 -282
  111. package/src/ai/providers/acp-transport.js +0 -356
  112. package/src/ai/providers/acp.js +0 -543
  113. package/src/ai/providers/claude-cli.js +0 -883
  114. package/src/ai/providers/claude-sandbox.js +0 -71
  115. package/src/ai/providers/claude-sdk-discovery-worker.js +0 -53
  116. package/src/ai/providers/claude-sdk-discovery.js +0 -352
  117. package/src/ai/providers/claude-sdk.js +0 -1127
  118. package/src/ai/providers/claude-subagent-activity.js +0 -719
  119. package/src/ai/providers/claude-subagents.js +0 -88
  120. package/src/ai/providers/codex-app.js +0 -2946
  121. package/src/ai/providers/opencode-app.js +0 -1109
  122. package/src/ai/providers/opencode-discovery.js +0 -39
  123. package/src/ai/providers/opencode-server.js +0 -508
  124. package/src/ai/runtime/context-windows.js +0 -46
  125. package/src/ai/runtime/fast-mode.js +0 -8
  126. package/src/ai/streaming/codex-events.js +0 -146
  127. package/src/ai/streaming/opencode-events.js +0 -59
  128. package/types/ai/providers/acp-client.d.ts +0 -227
  129. package/types/ai/providers/acp-privacy.d.ts +0 -25
  130. package/types/ai/providers/acp-public.d.ts +0 -7
  131. package/types/ai/providers/acp-session-tokens.d.ts +0 -41
  132. package/types/ai/providers/acp-transport.d.ts +0 -45
  133. package/types/ai/providers/acp.d.ts +0 -93
  134. package/types/ai/providers/claude-cli.d.ts +0 -305
  135. package/types/ai/providers/claude-sandbox.d.ts +0 -79
  136. package/types/ai/providers/claude-sdk-discovery-worker.d.ts +0 -1
  137. package/types/ai/providers/claude-sdk-discovery.d.ts +0 -97
  138. package/types/ai/providers/claude-sdk.d.ts +0 -138
  139. package/types/ai/providers/claude-subagent-activity.d.ts +0 -53
  140. package/types/ai/providers/claude-subagents.d.ts +0 -18
  141. package/types/ai/providers/codex-app.d.ts +0 -151
  142. package/types/ai/providers/opencode-app.d.ts +0 -96
  143. package/types/ai/providers/opencode-discovery.d.ts +0 -4
  144. package/types/ai/providers/opencode-server.d.ts +0 -20
  145. package/types/ai/runtime/context-windows.d.ts +0 -9
  146. package/types/ai/runtime/fast-mode.d.ts +0 -2
  147. package/types/ai/streaming/codex-events.d.ts +0 -40
  148. package/types/ai/streaming/opencode-events.d.ts +0 -42
@@ -1,1127 +0,0 @@
1
- import { query } from "@anthropic-ai/claude-agent-sdk";
2
- import { randomUUID } from "node:crypto";
3
- import { formatLiveInputGuidance } from "../live-input-prompt.js";
4
- import { estimateCost } from "../cost.js";
5
- import { modelWithContextWindow } from "../runtime/context-windows.js";
6
- import { runtimeCapabilities } from "../runtime/capabilities.js";
7
- import { buildCapabilitiesUsed, toolCompactionAppliedFromWarnings } from "../runtime/capabilities-used.js";
8
- import { MAX_TOOL_RESULT_BYTES, summarisePayload } from "../../agent/tool-bloat.js";
9
- import { normalizeMcpToolParams } from "../../agent/tools/pi-bridge.js";
10
- import { deprecatedSettingsWarning } from "../../agent/compaction.js";
11
- import { readRuntimeBrand } from "../../agent/tools/shared/runtime-context.js";
12
- import { createApprovalManager } from "../../agent/approval.js";
13
- import {
14
- claudeNativeAgentDefinitions,
15
- resolveClaudeAllowedTools,
16
- } from "./claude-subagents.js";
17
- import {
18
- claudeSandboxCapabilityMismatchResult,
19
- claudeSandboxPolicyProblem,
20
- } from "./claude-sandbox.js";
21
- import { createClaudeSubagentActivityNormalizer } from "./claude-subagent-activity.js";
22
- import { toolLifecycleMetadata } from "../tool-lifecycle.js";
23
-
24
- const CLAUDE_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh", "max"]);
25
- const CLAUDE_SETTING_SOURCES = new Set(["user", "project", "local"]);
26
- const MAX_CLAUDE_ERROR_CHARS = 2_000;
27
-
28
- /**
29
- * Filesystem settings the Agent SDK may load for this run. The SDK's own default
30
- * is "load nothing", and mono-agent keeps that default so a hosted run stays
31
- * reproducible: no host CLAUDE.md, hooks, or plugins leak in unless the caller
32
- * asks for them. A host that *wants* on-disk discovery — typically to reach
33
- * `.claude/agents` so the native `Task` tool has profiles to deploy — opts in per
34
- * run. Unrecognized entries are dropped rather than forwarded, so a typo cannot
35
- * silently widen what the SDK reads off disk.
36
- * @param {unknown} value
37
- * @returns {Array<"user" | "project" | "local">}
38
- */
39
- function normalizeClaudeSettingSources(value) {
40
- if (!Array.isArray(value)) return [];
41
- return Array.from(new Set(value.filter((entry) => CLAUDE_SETTING_SOURCES.has(entry))));
42
- }
43
-
44
- /**
45
- * Preserve the provider default when effort is omitted. The current Agent SDK
46
- * public effort contract accepts the five values below. Its shipped JavaScript
47
- * currently forwards out-of-contract values to Claude Code, so mono-agent keeps
48
- * this route inside the pinned public contract rather than relying on that
49
- * untyped pass-through. Mono-agent must not infer thinking enablement/disablement
50
- * from a requested effort level.
51
- * @param {unknown} effort
52
- * @returns {{effort?: "low" | "medium" | "high" | "xhigh" | "max"}}
53
- */
54
- export function claudeEffortOptions(effort) {
55
- if (effort == null || String(effort).trim() === "") return {};
56
- const normalized = String(effort).trim();
57
- if (normalized === "none") {
58
- throw new Error(
59
- 'Mono-agent\'s Claude SDK route does not support effort "none": the pinned Claude Agent SDK public effort contract starts at "low". Omit effort to use the provider default, or choose low, medium, high, xhigh, or max.',
60
- );
61
- }
62
- if (!CLAUDE_EFFORT_LEVELS.has(normalized)) {
63
- throw new Error(
64
- `Mono-agent's Claude SDK route does not support effort "${boundedText(normalized, 64)}": the pinned Claude Agent SDK public effort contract ends at "max". Choose low, medium, high, xhigh, or max, or omit effort.`,
65
- );
66
- }
67
- return { effort: /** @type {"low" | "medium" | "high" | "xhigh" | "max"} */ (normalized) };
68
- }
69
-
70
- function boundedText(value, limit = MAX_CLAUDE_ERROR_CHARS) {
71
- const text = String(value ?? "").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
72
- if (text.length <= limit) return text;
73
- return `${text.slice(0, Math.max(0, limit - 16))}… [truncated]`;
74
- }
75
-
76
- function createClaudeSdkEnvironment(overrides, providerEnvironment) {
77
- return {
78
- ...process.env,
79
- ...(overrides && typeof overrides === "object" ? overrides : {}),
80
- ...(providerEnvironment && typeof providerEnvironment === "object" ? providerEnvironment : {}),
81
- MCP_CONNECTION_NONBLOCKING: "0",
82
- };
83
- }
84
-
85
- /** @param {string} model @param {unknown} contextWindow */
86
- export function claudeSdkModelForQuery(model, contextWindow) {
87
- return modelWithContextWindow(model, contextWindow);
88
- }
89
-
90
- function extractText(event) {
91
- if (event.type !== "assistant" || !event.message?.content) return "";
92
- let out = "";
93
- for (const block of event.message.content) {
94
- if (block.type === "text") out += block.text;
95
- }
96
- return out;
97
- }
98
-
99
- function assistantToolNames(event) {
100
- if (event.type !== "assistant" || !Array.isArray(event.message?.content)) return [];
101
- return event.message.content
102
- .filter((block) => block?.type === "tool_use" && block.name)
103
- .map((block) => block.name);
104
- }
105
-
106
- function assistantThinkingObserved(event) {
107
- return event?.type === "assistant"
108
- && Array.isArray(event.message?.content)
109
- && event.message.content.some((block) => block?.type === "thinking" || block?.type === "redacted_thinking");
110
- }
111
-
112
- function extractResultText(event) {
113
- if (event.type !== "result") return "";
114
- if (typeof event.result === "string") return event.result;
115
- if (event.result != null) return JSON.stringify(event.result);
116
- if (typeof event.final_output === "string") return event.final_output;
117
- if (event.final_output != null) return JSON.stringify(event.final_output);
118
- return "";
119
- }
120
-
121
- function stringifyError(value) {
122
- if (!value) return "";
123
- if (typeof value === "string") return value;
124
- if (typeof value.message === "string") return value.message;
125
- try { return JSON.stringify(value); } catch { return String(value); }
126
- }
127
-
128
- function claudeAssistantFailure(code, requestId = null) {
129
- const normalizedCode = boundedText(code || "unknown", 80);
130
- const mapping = {
131
- authentication_failed: {
132
- message: "Claude authentication failed. Sign in again or provide a valid Claude credential.",
133
- failureKind: "provider_auth",
134
- category: "authentication",
135
- retryable: false,
136
- },
137
- oauth_org_not_allowed: {
138
- message: "Claude authentication succeeded, but this organization does not allow the OAuth session.",
139
- failureKind: "provider_auth",
140
- category: "authentication",
141
- retryable: false,
142
- },
143
- rate_limit: {
144
- message: "Claude usage or rate limit reached.",
145
- failureKind: "usage_limit",
146
- category: "usage_limit",
147
- retryable: false,
148
- },
149
- max_output_tokens: {
150
- message: "Claude reached the maximum output-token limit.",
151
- failureKind: "usage_limit",
152
- category: "usage_limit",
153
- retryable: false,
154
- },
155
- overloaded: {
156
- message: "Claude is temporarily overloaded.",
157
- failureKind: "provider_unavailable",
158
- category: "provider_unavailable",
159
- retryable: true,
160
- },
161
- server_error: {
162
- message: "Claude returned a temporary server error.",
163
- failureKind: "provider_unavailable",
164
- category: "provider_unavailable",
165
- retryable: true,
166
- },
167
- billing_error: {
168
- message: "Claude rejected the request because the account needs billing attention.",
169
- failureKind: "provider_unavailable",
170
- category: "nonretryable",
171
- retryable: false,
172
- },
173
- invalid_request: {
174
- message: "Claude rejected the request as invalid.",
175
- failureKind: "provider_unavailable",
176
- category: "nonretryable",
177
- retryable: false,
178
- },
179
- model_not_found: {
180
- message: "Claude could not find or access the requested model.",
181
- failureKind: "provider_unavailable",
182
- category: "nonretryable",
183
- retryable: false,
184
- },
185
- unknown: {
186
- message: "Claude reported an unknown provider error.",
187
- failureKind: "provider_unavailable",
188
- category: "unknown",
189
- retryable: false,
190
- },
191
- };
192
- const selected = mapping[normalizedCode] || mapping.unknown;
193
- const safeRequestId = typeof requestId === "string" && requestId.trim()
194
- ? boundedText(requestId, 160)
195
- : null;
196
- return {
197
- ...selected,
198
- code: normalizedCode,
199
- requestId: safeRequestId,
200
- message: `${selected.message}${safeRequestId ? ` Request ID: ${safeRequestId}.` : ""}`,
201
- };
202
- }
203
-
204
- function resultFailureCategory(event, resultError) {
205
- const text = `${resultError?.message || ""} ${Array.isArray(event?.errors) ? event.errors.join(" ") : ""}`;
206
- if (/auth|oauth|api key|401|403|sign[ -]?in|log[ -]?in/i.test(text)) {
207
- return claudeAssistantFailure("authentication_failed");
208
- }
209
- if (event?.subtype === "error_max_turns" || event?.subtype === "error_max_budget_usd") {
210
- return {
211
- message: boundedText(resultError?.message || "Claude usage limit reached."),
212
- failureKind: "usage_limit",
213
- category: "usage_limit",
214
- retryable: false,
215
- code: event.subtype,
216
- requestId: null,
217
- };
218
- }
219
- if (/overload|temporar|server error|\b50[0234]\b/i.test(text)) {
220
- return {
221
- message: boundedText(resultError?.message || "Claude is temporarily unavailable."),
222
- failureKind: "provider_unavailable",
223
- category: "provider_unavailable",
224
- retryable: true,
225
- code: event?.subtype || "result_error",
226
- requestId: null,
227
- };
228
- }
229
- return {
230
- message: boundedText(resultError?.message || "Claude request failed."),
231
- failureKind: resultError?.failureKind || "provider_unavailable",
232
- category: resultError?.failureKind === "invalid_result" ? "nonretryable" : "unknown",
233
- retryable: false,
234
- code: event?.subtype || "result_error",
235
- requestId: null,
236
- };
237
- }
238
-
239
- function humanizeSubtype(subtype) {
240
- return String(subtype || "").replace(/^error_/, "").replace(/_/g, " ").trim();
241
- }
242
-
243
- function resultEventError(event) {
244
- if (event.type !== "result") return null;
245
- const subtype = typeof event.subtype === "string" ? event.subtype : "";
246
- const errors = Array.isArray(event.errors) ? event.errors.filter(Boolean) : [];
247
- const explicit = stringifyError(event.error) || stringifyError(event.message);
248
- if (!event.is_error && !subtype.startsWith("error_") && errors.length === 0 && !explicit) return null;
249
-
250
- const detail = explicit || errors.map(stringifyError).filter(Boolean).join("; ");
251
- const label = humanizeSubtype(subtype);
252
- const message = subtype === "error_max_turns"
253
- ? "Claude stopped before final output: max turns reached"
254
- : `Claude result error${label ? ` (${label})` : ""}${detail ? `: ${detail}` : ""}`;
255
- return {
256
- message: boundedText(message),
257
- failureKind: subtype === "error_max_turns"
258
- ? "usage_limit"
259
- : subtype === "error_max_structured_output_retries"
260
- ? "invalid_result"
261
- : "provider_unavailable",
262
- };
263
- }
264
-
265
- function makeRuntimeWarning(message, warningKind = "claude_post_success_error") {
266
- return {
267
- warning_kind: warningKind,
268
- message,
269
- };
270
- }
271
-
272
- function extractStructuredOutput(event) {
273
- if (event?.type === "result" && Object.prototype.hasOwnProperty.call(event, "structured_output")) {
274
- return event.structured_output;
275
- }
276
- return undefined;
277
- }
278
-
279
- function structuredOutputEvent(value) {
280
- return {
281
- type: "structured_output",
282
- source: "claude_sdk_output_format",
283
- value,
284
- };
285
- }
286
-
287
- function structuredOutputToolUses(event) {
288
- if (event?.type !== "assistant" || !Array.isArray(event.message?.content)) return [];
289
- return event.message.content
290
- .filter((block) => (
291
- block?.type === "tool_use"
292
- && block?.name === "StructuredOutput"
293
- && block.input !== undefined
294
- && (block.id || block.tool_use_id)
295
- ))
296
- .map((block) => ({
297
- id: block.id || block.tool_use_id,
298
- input: block.input,
299
- }));
300
- }
301
-
302
- function acceptedStructuredOutputValues(event, pendingStructuredOutputById) {
303
- if (event?.type !== "user" || !Array.isArray(event.message?.content)) return [];
304
- const values = [];
305
- for (const block of event.message.content) {
306
- if (block?.type !== "tool_result") continue;
307
- const id = block.tool_use_id || block.toolUseId;
308
- if (!id || !pendingStructuredOutputById.has(id)) continue;
309
- const value = pendingStructuredOutputById.get(id);
310
- pendingStructuredOutputById.delete(id);
311
- if (block.is_error === true) continue;
312
- values.push(value);
313
- }
314
- return values;
315
- }
316
-
317
- function pickSessionId(...values) {
318
- for (const value of values) {
319
- if (typeof value === "string" && value.trim()) return value.trim();
320
- }
321
- return null;
322
- }
323
-
324
- function sessionIdFromEvent(event) {
325
- return pickSessionId(event?.session_id, event?.sessionId);
326
- }
327
-
328
- function lastTextSnippet(texts, limit = 200) {
329
- for (let i = texts.length - 1; i >= 0; i -= 1) {
330
- const text = texts[i];
331
- if (typeof text === "string" && text.trim()) {
332
- const trimmed = text.trim();
333
- return trimmed.length > limit ? trimmed.slice(-limit) : trimmed;
334
- }
335
- }
336
- return null;
337
- }
338
-
339
- function buildClaudeErrorDetails({
340
- event = null,
341
- subtype = null,
342
- providerSessionId = null,
343
- assistantTexts = [],
344
- lastToolName = null,
345
- toolResultsSeen = 0,
346
- numTurns = 0,
347
- lastStructuredOutputRejection = null,
348
- failureCode = null,
349
- failureCategory = null,
350
- retryable = null,
351
- requestId = null,
352
- }) {
353
- const rawSubtype = subtype || event?.subtype || event?.type || null;
354
- const resolvedSubtype = rawSubtype == null ? null : boundedText(rawSubtype, 160);
355
- const turnCount = Number(event?.num_turns ?? numTurns) || 0;
356
- const excerpt = lastTextSnippet(assistantTexts);
357
- return {
358
- claude_error_subtype: resolvedSubtype,
359
- last_text_excerpt: excerpt,
360
- last_tool_name: lastToolName ? boundedText(lastToolName, 160) : null,
361
- had_partial_progress: !!(excerpt || lastToolName || toolResultsSeen > 0),
362
- tool_results_seen: toolResultsSeen,
363
- turn_count: turnCount,
364
- max_turns_hit: resolvedSubtype === "error_max_turns",
365
- structured_output_retry_exhausted: resolvedSubtype === "error_max_structured_output_retries",
366
- last_structured_output_rejection: lastStructuredOutputRejection
367
- ? boundedText(lastStructuredOutputRejection, 500)
368
- : null,
369
- provider_session_id: providerSessionId ? boundedText(providerSessionId, 160) : null,
370
- claude_error_code: failureCode ? boundedText(failureCode, 80) : null,
371
- claude_error_category: failureCategory || null,
372
- retryable: typeof retryable === "boolean" ? retryable : null,
373
- request_id: requestId || null,
374
- };
375
- }
376
-
377
- function toolResultText(block) {
378
- const content = block?.content;
379
- if (typeof content === "string") return content;
380
- if (Array.isArray(content)) {
381
- return content.map((item) => item?.text || item?.content || "").filter(Boolean).join("\n");
382
- }
383
- if (content == null) return "";
384
- try { return JSON.stringify(content); } catch { return String(content); }
385
- }
386
-
387
- function annotateClaudeToolLifecycles(event) {
388
- if (event?.type !== "user" || !Array.isArray(event.message?.content)) return;
389
- for (const block of event.message.content) {
390
- if (!block || block.type !== "tool_result") continue;
391
- block.tool_lifecycle = toolLifecycleMetadata(block.is_error === true
392
- ? { state: "error", failure_kind: "runtime_error", detail_code: "claude_sdk_tool_error" }
393
- : { state: "success" });
394
- }
395
- }
396
-
397
- function structuredOutputRejectionFromEvent(event) {
398
- if (event?.type !== "user" || !Array.isArray(event.message?.content)) return null;
399
- for (const block of event.message.content) {
400
- if (block?.type === "tool_result" && block.is_error) {
401
- const text = toolResultText(block);
402
- if (/structured output|required schema|did not match schema|schema violation/i.test(text)) return text;
403
- }
404
- }
405
- const result = event.is_error === true ? stringifyError(event.tool_use_result) : null;
406
- return /structured output|required schema|did not match schema|schema violation/i.test(result) ? result : null;
407
- }
408
-
409
- function mergeHookMatchers(existing = {}, additions = {}) {
410
- const merged = {};
411
- for (const [name, groups] of Object.entries(existing || {})) {
412
- if (Array.isArray(groups)) merged[name] = [...groups];
413
- }
414
- for (const [name, groups] of Object.entries(additions || {})) {
415
- if (!Array.isArray(groups) || !groups.length) continue;
416
- merged[name] = [...(merged[name] || []), ...groups];
417
- }
418
- return merged;
419
- }
420
-
421
- function objectInput(value) {
422
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
423
- }
424
-
425
- function parseMcpToolName(toolName) {
426
- const name = String(toolName || "");
427
- if (!name.startsWith("mcp__")) return null;
428
- const rest = name.slice(5);
429
- const sep = rest.indexOf("__");
430
- if (sep <= 0) return null;
431
- return {
432
- serverName: rest.slice(0, sep),
433
- toolName: rest.slice(sep + 2),
434
- };
435
- }
436
-
437
- function normalizeClaudeMcpInput(input, qaOutputDir) {
438
- const parsed = parseMcpToolName(input?.tool_name);
439
- if (!parsed) return null;
440
- const current = objectInput(input?.tool_input);
441
- const normalized = normalizeMcpToolParams(parsed.serverName, parsed.toolName, current, { qaOutputDir });
442
- if (normalized === current) return null;
443
- try {
444
- if (JSON.stringify(normalized) === JSON.stringify(current)) return null;
445
- } catch {
446
- // Non-serializable input is unexpected, but returning an SDK override is
447
- // still safe when the normalizer produced a different object.
448
- }
449
- return normalized;
450
- }
451
-
452
- function claudeToolResponseBlocks(toolResponse) {
453
- if (toolResponse && typeof toolResponse === "object" && Array.isArray(toolResponse.content)) {
454
- return toolResponse.content;
455
- }
456
- if (typeof toolResponse === "string") return [{ type: "text", text: toolResponse }];
457
- if (toolResponse == null) return [];
458
- try {
459
- return [{ type: "text", text: JSON.stringify(toolResponse) }];
460
- } catch {
461
- return [{ type: "text", text: String(toolResponse) }];
462
- }
463
- }
464
-
465
- // Resolve the tool_result byte cap. Precedence is PER-GROUP (MIGRATION.md §8):
466
- // explicit options.toolPayloadMaxBytes
467
- // -> typed options.toolLimits — when this group is PRESENT it wins wholesale:
468
- // its toolPayloadMaxBytes is used if valid, otherwise the default; the
469
- // DEPRECATED settings fallback below is never consulted for this group,
470
- // even if toolLimits.toolPayloadMaxBytes itself is absent/invalid.
471
- // -> DEPRECATED options.settings.agent_tool_payload_max_bytes (usedSettings),
472
- // only consulted when options.toolLimits is entirely absent.
473
- // -> MAX_TOOL_RESULT_BYTES default.
474
- // `usedSettings` lets the caller emit one deprecation warning per run when the
475
- // legacy settings fallback was actually consumed.
476
- export function toolPayloadLimit(options) {
477
- const explicit = Number(options.toolPayloadMaxBytes);
478
- if (Number.isFinite(explicit) && explicit > 0) return { bytes: Math.floor(explicit), usedSettings: false };
479
- if (options.toolLimits) {
480
- const typed = Number(options.toolLimits.toolPayloadMaxBytes);
481
- if (Number.isFinite(typed) && typed > 0) return { bytes: Math.floor(typed), usedSettings: false };
482
- return { bytes: MAX_TOOL_RESULT_BYTES, usedSettings: false };
483
- }
484
- const configured = Number(options.settings?.agent_tool_payload_max_bytes);
485
- if (Number.isFinite(configured) && configured > 0) return { bytes: Math.floor(configured), usedSettings: true };
486
- return { bytes: MAX_TOOL_RESULT_BYTES, usedSettings: false };
487
- }
488
-
489
- function createClaudeRuntimeHooks({
490
- emitEvent,
491
- persistArtifact,
492
- qaOutputDir,
493
- toolPayloadMaxBytes,
494
- onToolUse,
495
- onToolResult,
496
- }) {
497
- return {
498
- PreToolUse: [{
499
- matcher: "*",
500
- hooks: [async (input) => {
501
- onToolUse?.(input?.tool_name);
502
- const updatedInput = normalizeClaudeMcpInput(input, qaOutputDir);
503
- if (!updatedInput) return {};
504
- return {
505
- continue: true,
506
- hookSpecificOutput: {
507
- hookEventName: "PreToolUse",
508
- updatedInput,
509
- },
510
- };
511
- }],
512
- }],
513
- PostToolUse: [{
514
- matcher: "*",
515
- hooks: [async (input, toolUseID) => {
516
- const toolName = input?.tool_name || "tool";
517
- onToolUse?.(toolName);
518
- onToolResult?.(toolName);
519
- const blocks = claudeToolResponseBlocks(input?.tool_response);
520
- if (!blocks.length) return {};
521
- const summary = summarisePayload(toolName, blocks, persistArtifact, {
522
- maxBytes: toolPayloadMaxBytes,
523
- toolUseId: toolUseID || input?.tool_use_id || input?.toolUseID || null,
524
- });
525
- if (!summary.truncated) return {};
526
- emitEvent({
527
- type: "runtime_warning",
528
- warning_kind: "tool_payload_truncated",
529
- source: "tool_bloat_guard",
530
- tool: toolName,
531
- tool_use_id: toolUseID || input?.tool_use_id || input?.toolUseID || null,
532
- original_bytes: summary.originalBytes,
533
- max_bytes: toolPayloadMaxBytes,
534
- saved_paths: summary.savedPaths,
535
- });
536
- return {
537
- continue: true,
538
- hookSpecificOutput: {
539
- hookEventName: "PostToolUse",
540
- updatedMCPToolOutput: summary.rewrittenBlocks,
541
- },
542
- };
543
- }],
544
- }],
545
- PostToolUseFailure: [{
546
- matcher: "*",
547
- hooks: [async (input) => {
548
- onToolUse?.(input?.tool_name);
549
- return {};
550
- }],
551
- }],
552
- };
553
- }
554
-
555
- function promptStringFromMessages(messages) {
556
- return Array.isArray(messages)
557
- ? messages.filter(m => m.role === "user").map(m => typeof m.content === "string" ? m.content : JSON.stringify(m.content)).join("\n")
558
- : String(messages || "");
559
- }
560
-
561
- function makeSdkUserMessage(body, sessionId, uuid = randomUUID()) {
562
- return {
563
- type: "user",
564
- session_id: sessionId,
565
- parent_tool_use_id: null,
566
- uuid,
567
- message: {
568
- role: "user",
569
- content: body,
570
- },
571
- };
572
- }
573
-
574
- function createClaudeCanUseTool(approvalManager, modelName) {
575
- return async function canUseTool(toolName, input, context = {}) {
576
- const decision = await approvalManager.request({
577
- toolName,
578
- input,
579
- model: modelName,
580
- toolUseId: context?.toolUseID || context?.toolUseId || context?.tool_use_id || null,
581
- });
582
- if (decision.decision === "deny") {
583
- return {
584
- behavior: "deny",
585
- message: `Tool ${toolName} denied by host approval gate (${decision.reason || "no reason"})`,
586
- };
587
- }
588
- return { behavior: "allow", updatedInput: input };
589
- };
590
- }
591
-
592
- async function* livePromptMessages({ initialPrompt, liveInput, sessionId, prompts }) {
593
- yield makeSdkUserMessage(initialPrompt, sessionId);
594
- for await (const message of liveInput) {
595
- try {
596
- const sdkMessage = makeSdkUserMessage(
597
- formatLiveInputGuidance(message.body, prompts),
598
- sessionId,
599
- message.id || randomUUID(),
600
- );
601
- message.acknowledge?.();
602
- yield sdkMessage;
603
- } catch (err) {
604
- message.reject?.(err);
605
- throw err;
606
- }
607
- }
608
- }
609
-
610
- export async function generateClaudeResponse(systemPrompt, options) {
611
- const {
612
- messages,
613
- model,
614
- effort,
615
- cwd,
616
- mcpServers,
617
- allowedTools,
618
- disallowedTools,
619
- hooks,
620
- permissionMode = "bypassPermissions",
621
- maxTurns,
622
- abortSignal,
623
- onEvent = () => {},
624
- } = options;
625
-
626
- let effortOptions;
627
- try {
628
- effortOptions = claudeEffortOptions(effort);
629
- } catch (error) {
630
- const message = boundedText(error?.message || error);
631
- return {
632
- text: "",
633
- structuredResult: undefined,
634
- structuredResultSource: null,
635
- events: [],
636
- usage: {},
637
- durationMs: 0,
638
- numTurns: 0,
639
- model: model.model,
640
- effort: effort ?? null,
641
- sdk: "claude",
642
- cancelled: false,
643
- error: message,
644
- errorDetails: {
645
- claude_error_code: "claude_effort_unsupported",
646
- claude_error_category: "nonretryable",
647
- retryable: false,
648
- },
649
- failureKind: "skipped_capability_mismatch",
650
- providerSessionId: pickSessionId(options.sessionId, options.providerSessionId),
651
- runtimeWarnings: [],
652
- capabilitiesUsed: buildCapabilitiesUsed({ thinkingEnabled: null }),
653
- };
654
- }
655
-
656
- if (claudeSandboxPolicyProblem(options)) {
657
- return claudeSandboxCapabilityMismatchResult({
658
- model: model.reference || `claude:${model.model}`,
659
- effort,
660
- sdk: "claude",
661
- providerSessionId: pickSessionId(options.sessionId, options.providerSessionId),
662
- outputSchema: options.outputSchema,
663
- });
664
- }
665
-
666
- const promptString = promptStringFromMessages(messages);
667
- const runtimeWarnings = [];
668
- const capturedEvents = [];
669
- const assistantTextFragments = [];
670
- const reusableProviderSessionId = pickSessionId(options.sessionId, options.providerSessionId);
671
- const persistArtifact = options.persistArtifact || null;
672
- const qaOutputDir = options.qaOutputDir || options.runArtifactDir || null;
673
- const { bytes: toolPayloadMaxBytes, usedSettings: toolPayloadFromSettings } = toolPayloadLimit(options);
674
- let providerSessionId = reusableProviderSessionId;
675
- let lastToolName = null;
676
- let toolResultsSeen = 0;
677
-
678
- function emitEvent(event) {
679
- if (!event) return;
680
- annotateClaudeToolLifecycles(event);
681
- capturedEvents.push(event);
682
- onEvent(event);
683
- }
684
-
685
- const subagentNormalizer = createClaudeSubagentActivityNormalizer();
686
- function emitSubagentEvents(activityEvents) {
687
- for (const activity of activityEvents) emitEvent(activity);
688
- }
689
- function drainSubagents(reason) {
690
- emitSubagentEvents(subagentNormalizer.drain(reason));
691
- }
692
-
693
- // Deprecated `settings` fallback for the tool_result byte cap was consumed;
694
- // surface the one-per-run deprecation warning (the typed `toolLimits` object
695
- // is the supported path). mono-agent never passes `settings`, so this never
696
- // fires there.
697
- if (toolPayloadFromSettings) {
698
- const warning = deprecatedSettingsWarning(["agent_tool_payload_max_bytes"]);
699
- runtimeWarnings.push(warning);
700
- emitEvent({ type: "runtime_warning", ...warning });
701
- }
702
-
703
- function noteToolUse(toolName) {
704
- if (toolName) lastToolName = toolName;
705
- }
706
-
707
- function noteToolResult(toolName) {
708
- if (toolName) lastToolName = toolName;
709
- toolResultsSeen += 1;
710
- }
711
-
712
- const approvalManager = options.onToolApprovalRequest
713
- ? createApprovalManager({
714
- onToolApprovalRequest: options.onToolApprovalRequest,
715
- defaultRiskTier: options.approvalDefaultRiskTier,
716
- timeoutMs: options.approvalTimeoutMs,
717
- onEvent: emitEvent,
718
- riskTiersByTool: options.toolRiskTiers,
719
- alwaysAllowTools: options.approvalAlwaysAllowTools,
720
- })
721
- : null;
722
- // The Claude SDK invokes `canUseTool` only when permissionMode opts out of
723
- // bypass. When the host enabled approval gates, force the SDK out of
724
- // bypass; otherwise the callback would be skipped silently.
725
- const effectivePermissionMode = approvalManager && permissionMode === "bypassPermissions"
726
- ? "default"
727
- : permissionMode;
728
- const nativeAgents = claudeNativeAgentDefinitions(options.nativeSubagents);
729
- // `"*"` allow-all → pass `allowedTools: undefined` so the SDK uses its default
730
- // toolset (every tool, incl. Task — not double-added). disallowedTools still
731
- // flows through, so deny-wins holds under allow-all.
732
- const { allowAll: allowAllTools, tools: resolvedAllowedTools } = resolveClaudeAllowedTools(allowedTools, options.nativeSubagents);
733
- const hasExplicitToolProjection = Array.isArray(allowedTools) && !allowAllTools;
734
- const internalAbortController = new AbortController();
735
- const disposableSession = options.persistSession === false
736
- || options.disposable === true
737
- || options.readinessProbe === true
738
- || options.sessionKeepAlive === false;
739
- // Assembled incrementally, then handed across the SDK `query` boundary
740
- // (outputFormat/resume/maxTurns are attached conditionally below).
741
- /** @type {any} */
742
- const queryOptions = {
743
- systemPrompt,
744
- model: claudeSdkModelForQuery(model.model, options.contextWindow),
745
- cwd,
746
- permissionMode: effectivePermissionMode,
747
- ...(effectivePermissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {}),
748
- // `tools` is the SDK's availability projection. In particular, [] must
749
- // remain [] so a readiness/discovery call cannot silently regain defaults.
750
- ...(hasExplicitToolProjection ? { tools: resolvedAllowedTools } : {}),
751
- // `allowedTools` only controls auto-approval. Never provide it alongside
752
- // canUseTool, where it would bypass the host approval callback.
753
- ...(!approvalManager && hasExplicitToolProjection ? { allowedTools: resolvedAllowedTools } : {}),
754
- disallowedTools,
755
- mcpServers: mcpServers || {},
756
- strictMcpConfig: true,
757
- settingSources: normalizeClaudeSettingSources(options.settingSources),
758
- // The SDK otherwise forwards child tool frames but suppresses child prose.
759
- // Request it explicitly so the live normalizer can preserve the complete
760
- // nested transcript without ever merging it into the parent answer.
761
- forwardSubagentText: true,
762
- env: createClaudeSdkEnvironment(options.env, options.providerEnv),
763
- abortController: internalAbortController,
764
- ...(disposableSession ? { persistSession: false } : options.persistSession === true ? { persistSession: true } : {}),
765
- ...(approvalManager ? { canUseTool: createClaudeCanUseTool(approvalManager, model.model) } : {}),
766
- ...(nativeAgents ? { agents: nativeAgents } : {}),
767
- hooks: mergeHookMatchers(hooks, createClaudeRuntimeHooks({
768
- emitEvent,
769
- persistArtifact,
770
- qaOutputDir,
771
- toolPayloadMaxBytes,
772
- onToolUse: noteToolUse,
773
- onToolResult: noteToolResult,
774
- })),
775
- ...effortOptions,
776
- };
777
- if (options.outputSchema) {
778
- queryOptions.outputFormat = {
779
- type: "json_schema",
780
- schema: options.outputSchema,
781
- };
782
- }
783
- if (reusableProviderSessionId) {
784
- queryOptions.resume = reusableProviderSessionId;
785
- }
786
- if (Number.isFinite(Number(maxTurns)) && Number(maxTurns) > 0) {
787
- queryOptions.maxTurns = Number(maxTurns);
788
- }
789
-
790
- const prompt = options.liveInput
791
- ? livePromptMessages({ initialPrompt: promptString, liveInput: options.liveInput, sessionId: reusableProviderSessionId || randomUUID(), prompts: options.prompts })
792
- : promptString;
793
- const claudeAgentQuery = options.claudeAgentQuery ?? query;
794
- const providerRequestStartedAt = Date.now();
795
- emitEvent({
796
- type: "provider_request_started",
797
- sdk: "claude",
798
- model: model.model,
799
- runtime: "sdk",
800
- timestamp: providerRequestStartedAt,
801
- });
802
- /** @type {ReturnType<typeof query> | null} */
803
- let stream = null;
804
-
805
- let text = "";
806
- let usage = {};
807
- let durationMs = 0;
808
- let numTurns = 0;
809
- let resultText = "";
810
- let cancelled = false;
811
- let errorMessage = null;
812
- let failureKind = null;
813
- let successfulResultSeen = false;
814
- let postSuccessErrorSeen = false;
815
- let structuredResultSource = null;
816
- let structuredResult = undefined;
817
- let errorDetails = null;
818
- let lastStructuredOutputRejection = null;
819
- let totalCostUsd = null;
820
- let thinkingObserved = false;
821
- let structuredTerminalFailure = null;
822
- const pendingStructuredOutputById = new Map();
823
-
824
- const rawFinalText = () => resultText || text;
825
-
826
- function hasUsableFinalOutput() {
827
- return structuredResult !== undefined || String(rawFinalText() || "").trim().length > 0;
828
- }
829
-
830
- function hasPreservableFinalOutput() {
831
- return successfulResultSeen ? hasUsableFinalOutput() : structuredResult !== undefined;
832
- }
833
-
834
- function preservePostSuccessError(message) {
835
- if (postSuccessErrorSeen) return;
836
- postSuccessErrorSeen = true;
837
- runtimeWarnings.push(makeRuntimeWarning(message));
838
- }
839
-
840
- const abortHandler = () => {
841
- cancelled = true;
842
- drainSubagents("subagent cancelled with the parent run");
843
- internalAbortController.abort();
844
- try { stream?.close?.(); } catch { /* best effort; finally closes again */ }
845
- };
846
- if (abortSignal) {
847
- if (abortSignal.aborted) abortHandler();
848
- else abortSignal.addEventListener("abort", abortHandler, { once: true });
849
- }
850
-
851
- try {
852
- stream = claudeAgentQuery({ prompt: /** @type {any} */ (prompt), options: queryOptions });
853
- for await (const rawEvent of stream) {
854
- const nextSessionId = sessionIdFromEvent(rawEvent);
855
- if (nextSessionId) providerSessionId = nextSessionId;
856
- const observation = subagentNormalizer.observe(rawEvent);
857
- emitSubagentEvents(observation.events);
858
- // Child records are represented exclusively as subagent_activity. In
859
- // particular, their text, errors, tools, usage, and structured output
860
- // must never mutate the parent's result state below.
861
- if (observation.consumed) {
862
- if (cancelled) break;
863
- continue;
864
- }
865
- // Preserve unrelated blocks when a root user message batches them with a
866
- // background Agent launch acknowledgement.
867
- const event = /** @type {any} */ (observation.forwarded ?? rawEvent);
868
- emitEvent(event);
869
- if (event?.type === "tool_progress" && event.tool_name) noteToolUse(event.tool_name);
870
- for (const toolUse of structuredOutputToolUses(event)) {
871
- pendingStructuredOutputById.set(toolUse.id, toolUse.input);
872
- }
873
- const structuredOutputRejection = structuredOutputRejectionFromEvent(event);
874
- if (structuredOutputRejection) lastStructuredOutputRejection = structuredOutputRejection;
875
- for (const acceptedStructuredOutput of acceptedStructuredOutputValues(event, pendingStructuredOutputById)) {
876
- structuredResult = acceptedStructuredOutput;
877
- structuredResultSource = "StructuredOutput";
878
- emitEvent({
879
- ...structuredOutputEvent(acceptedStructuredOutput),
880
- source: "StructuredOutput",
881
- });
882
- }
883
- const eventStructuredOutput = extractStructuredOutput(event);
884
- if (eventStructuredOutput !== undefined) {
885
- structuredResult = eventStructuredOutput;
886
- structuredResultSource = "structured_output";
887
- emitEvent(structuredOutputEvent(eventStructuredOutput));
888
- }
889
- if (event.type === "assistant") {
890
- thinkingObserved = thinkingObserved || assistantThinkingObserved(event);
891
- if (event.error && !structuredTerminalFailure) {
892
- const assistantFailure = claudeAssistantFailure(event.error, event.request_id);
893
- structuredTerminalFailure = assistantFailure;
894
- errorDetails = buildClaudeErrorDetails({
895
- event,
896
- subtype: event.error,
897
- providerSessionId,
898
- assistantTexts: assistantTextFragments,
899
- lastToolName,
900
- toolResultsSeen,
901
- numTurns,
902
- lastStructuredOutputRejection,
903
- failureCode: assistantFailure.code,
904
- failureCategory: assistantFailure.category,
905
- retryable: assistantFailure.retryable,
906
- requestId: assistantFailure.requestId,
907
- });
908
- }
909
- const delta = extractText(event);
910
- if (delta) assistantTextFragments.push(delta);
911
- text += delta;
912
- for (const toolName of assistantToolNames(event)) noteToolUse(toolName);
913
- }
914
- // The SDK's message union does not declare a runtime `error` event, but
915
- // the runtime can emit one; keep this defensive branch and cast past the
916
- // narrowed union.
917
- else if (/** @type {any} */ (event).type === "error") {
918
- const errorEvent = /** @type {any} */ (event);
919
- const message = boundedText(errorEvent.error?.message || errorEvent.error || "sdk stream error");
920
- if (structuredTerminalFailure) {
921
- // A typed assistant error is authoritative. A later transport error
922
- // cannot turn authentication/billing diagnostics into a generic
923
- // provider failure.
924
- } else if (hasPreservableFinalOutput()) {
925
- preservePostSuccessError(`Claude SDK emitted an error after final output; preserved final result. ${message}`);
926
- } else {
927
- errorMessage = message;
928
- failureKind = "provider_unavailable";
929
- errorDetails = buildClaudeErrorDetails({
930
- event,
931
- subtype: "error",
932
- providerSessionId,
933
- assistantTexts: assistantTextFragments,
934
- lastToolName,
935
- toolResultsSeen,
936
- numTurns,
937
- lastStructuredOutputRejection,
938
- });
939
- }
940
- break;
941
- } else if (event.type === "result") {
942
- if (Number.isFinite(Number(event.total_cost_usd))) totalCostUsd = Number(event.total_cost_usd);
943
- const resultError = resultEventError(event);
944
- if (resultError) {
945
- if (!successfulResultSeen) {
946
- usage = event.usage || usage;
947
- durationMs = event.duration_ms || durationMs;
948
- numTurns = event.num_turns || numTurns;
949
- }
950
- if (structuredTerminalFailure) {
951
- // Retain the typed assistant error and request id. The result still
952
- // contributes usage/duration/cost above.
953
- } else if (hasPreservableFinalOutput()) {
954
- preservePostSuccessError(`Claude SDK emitted an error after final output; preserved final result. ${resultError.message}`);
955
- successfulResultSeen = true;
956
- } else {
957
- const categorized = resultFailureCategory(event, resultError);
958
- usage = event.usage || usage;
959
- durationMs = event.duration_ms || durationMs;
960
- numTurns = event.num_turns || numTurns;
961
- errorMessage = categorized.message;
962
- failureKind = categorized.failureKind;
963
- if (failureKind === "invalid_result") {
964
- runtimeWarnings.push(makeRuntimeWarning(
965
- resultError.message,
966
- `${(options.toolContext?.runtimeBrand ?? readRuntimeBrand()).schemaPrefix}_result_validation`,
967
- ));
968
- }
969
- errorDetails = buildClaudeErrorDetails({
970
- event,
971
- subtype: event.subtype || "result_error",
972
- providerSessionId,
973
- assistantTexts: assistantTextFragments,
974
- lastToolName,
975
- toolResultsSeen,
976
- numTurns,
977
- lastStructuredOutputRejection,
978
- failureCode: categorized.code,
979
- failureCategory: categorized.category,
980
- retryable: categorized.retryable,
981
- requestId: categorized.requestId,
982
- });
983
- }
984
- } else {
985
- usage = event.usage || usage;
986
- durationMs = event.duration_ms || durationMs;
987
- numTurns = event.num_turns || numTurns;
988
- resultText = extractResultText(event) || resultText;
989
- successfulResultSeen = true;
990
- }
991
- if (options.liveInput) break;
992
- }
993
- if (cancelled) break;
994
- }
995
- } catch (err) {
996
- if (!cancelled) {
997
- const message = boundedText(err?.message || String(err));
998
- if (structuredTerminalFailure) {
999
- // Keep the earlier typed provider failure and its request id.
1000
- } else if (successfulResultSeen && hasUsableFinalOutput()) {
1001
- preservePostSuccessError(`Claude SDK stream failed after final output; preserved final result. ${message}`);
1002
- } else {
1003
- errorMessage = message;
1004
- failureKind = "provider_unavailable";
1005
- errorDetails = buildClaudeErrorDetails({
1006
- subtype: "exception",
1007
- providerSessionId,
1008
- assistantTexts: assistantTextFragments,
1009
- lastToolName,
1010
- toolResultsSeen,
1011
- numTurns,
1012
- lastStructuredOutputRejection,
1013
- failureCode: "exception",
1014
- failureCategory: "unknown",
1015
- retryable: false,
1016
- });
1017
- }
1018
- }
1019
- } finally {
1020
- drainSubagents(cancelled
1021
- ? "subagent cancelled with the parent run"
1022
- : errorMessage || structuredTerminalFailure
1023
- ? "subagent stopped because the Claude SDK stream failed"
1024
- : "subagent stream closed before completion");
1025
- try { stream?.close?.(); } catch { /* best effort after every terminal path */ }
1026
- if (abortSignal) abortSignal.removeEventListener?.("abort", abortHandler);
1027
- }
1028
-
1029
- if (structuredTerminalFailure) {
1030
- errorMessage = structuredTerminalFailure.message;
1031
- failureKind = structuredTerminalFailure.failureKind;
1032
- }
1033
-
1034
- const reference = model.reference || `claude:${model.model}`;
1035
- const inputTokens = usage?.input_tokens ?? usage?.inputTokens ?? 0;
1036
- const outputTokens = usage?.output_tokens ?? usage?.outputTokens ?? 0;
1037
- const cachedTokens = usage?.cache_read_input_tokens ?? usage?.cache_read_tokens ?? 0;
1038
- const cacheCreationTokens = usage?.cache_creation_input_tokens ?? usage?.cache_creation_tokens ?? 0;
1039
- const costUsd = Number.isFinite(totalCostUsd)
1040
- ? totalCostUsd
1041
- : estimateCost({
1042
- resolveCustomPricing: options.resolveCustomPricing,
1043
- model: reference,
1044
- inputTokens,
1045
- outputTokens,
1046
- cachedTokens,
1047
- cacheWriteTokens: cacheCreationTokens,
1048
- });
1049
- const enrichedUsage = {
1050
- ...usage,
1051
- input_tokens: inputTokens || null,
1052
- output_tokens: outputTokens || null,
1053
- cache_read_tokens: cachedTokens || null,
1054
- cache_creation_tokens: cacheCreationTokens || null,
1055
- cost_usd: costUsd,
1056
- };
1057
-
1058
- emitEvent({
1059
- type: "provider_request_completed",
1060
- sdk: "claude",
1061
- model: model.model,
1062
- runtime: "sdk",
1063
- timestamp: Date.now(),
1064
- durationMs: Date.now() - providerRequestStartedAt,
1065
- failureKind,
1066
- cancelled,
1067
- });
1068
- if (cachedTokens > 0) {
1069
- emitEvent({ type: "cache_hit", sdk: "claude", model: model.model, tokens: cachedTokens, source: "anthropic_prompt_cache" });
1070
- }
1071
- if (cacheCreationTokens > 0) {
1072
- emitEvent({ type: "cache_miss", sdk: "claude", model: model.model, tokens: cacheCreationTokens, source: "anthropic_prompt_cache" });
1073
- }
1074
- emitEvent({
1075
- type: "cost_accumulated",
1076
- sdk: "claude",
1077
- model: model.model,
1078
- cumulativeUsd: costUsd ?? 0,
1079
- tokens: {
1080
- input: inputTokens,
1081
- output: outputTokens,
1082
- cacheReadTokens: cachedTokens,
1083
- cacheCreationTokens,
1084
- },
1085
- });
1086
-
1087
- const observedSubagents = subagentNormalizer.nativeSubagentsUsed();
1088
- const capabilitiesUsed = buildCapabilitiesUsed({
1089
- promptCacheActive: cachedTokens > 0 || cacheCreationTokens > 0,
1090
- thinkingEnabled: thinkingObserved ? true : null,
1091
- structuredOutputEnforced: !!options.outputSchema,
1092
- subagentInvoked: subagentNormalizer.subagentInvoked(),
1093
- mcpServersUsed: Object.keys(mcpServers || {}),
1094
- nativeSubagentsUsed: observedSubagents,
1095
- toolCompactionApplied: toolCompactionAppliedFromWarnings(runtimeWarnings),
1096
- contextCompactionApplied: null, // Claude SDK doesn't use the runtime compaction layer
1097
- });
1098
- emitEvent({ type: "capabilities_resolved", sdk: "claude", model: model.model, capabilitiesUsed });
1099
-
1100
- return {
1101
- text: rawFinalText(),
1102
- structuredResult,
1103
- structuredResultSource,
1104
- events: capturedEvents,
1105
- usage: enrichedUsage,
1106
- durationMs,
1107
- numTurns,
1108
- model: model.model,
1109
- effort,
1110
- sdk: "claude",
1111
- cancelled,
1112
- error: errorMessage,
1113
- errorDetails,
1114
- failureKind,
1115
- providerSessionId,
1116
- runtimeWarnings,
1117
- capabilitiesUsed,
1118
- };
1119
- }
1120
-
1121
- export const claudeRuntimeBridge = {
1122
- id: "claude",
1123
- kind: "claude",
1124
- capabilities: runtimeCapabilities("claude"),
1125
- supports: (ref) => ref?.sdk === "claude",
1126
- execute: generateClaudeResponse,
1127
- };