@mono-agent/agent-runtime 0.6.2 → 0.9.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/README.md +36 -16
- package/package.json +14 -7
- package/src/agent/approval.js +52 -17
- package/src/agent/sandbox-seam.js +1 -0
- package/src/agent/tools/pi-bridge.js +7 -0
- package/src/agent/tools/shared/ripgrep.js +12 -8
- package/src/ai/index.js +8 -0
- package/src/ai/providers/claude-cli.js +109 -5
- package/src/ai/providers/claude-sandbox.js +71 -0
- package/src/ai/providers/claude-sdk-discovery-worker.js +53 -0
- package/src/ai/providers/claude-sdk-discovery.js +352 -0
- package/src/ai/providers/claude-sdk.js +313 -35
- package/src/ai/providers/codex-app.js +823 -78
- package/src/ai/providers/opencode-app.js +682 -96
- package/src/ai/providers/opencode-server.js +508 -0
- package/src/ai/providers/pi-native/turn-runner.js +8 -0
- package/src/ai/runtime/capabilities.js +12 -0
- package/src/ai/runtime/context-windows.js +8 -0
- package/src/ai/runtime/registry.js +8 -2
- package/src/ai/runtime/router.js +627 -29
- package/src/ai/types.js +29 -2
- package/src/index.js +6 -0
- package/src/runtime.js +17 -1
- package/types/agent/approval.d.ts +4 -7
- package/types/agent/sandbox-seam.d.ts +5 -0
- package/types/ai/backend.d.ts +16 -0
- package/types/ai/index.d.ts +1 -0
- package/types/ai/providers/claude-cli.d.ts +116 -0
- package/types/ai/providers/claude-sandbox.d.ts +79 -0
- package/types/ai/providers/claude-sdk-discovery-worker.d.ts +1 -0
- package/types/ai/providers/claude-sdk-discovery.d.ts +97 -0
- package/types/ai/providers/claude-sdk.d.ts +81 -5
- package/types/ai/providers/codex-app.d.ts +11 -7
- package/types/ai/providers/opencode-app.d.ts +15 -16
- package/types/ai/providers/opencode-server.d.ts +20 -0
- package/types/ai/runtime/capabilities.d.ts +19 -0
- package/types/ai/runtime/context-windows.d.ts +1 -0
- package/types/ai/runtime/router.d.ts +24 -23
- package/types/ai/types.d.ts +75 -2
- package/types/index.d.ts +1 -0
|
@@ -14,10 +14,55 @@ import {
|
|
|
14
14
|
claudeNativeAgentDefinitions,
|
|
15
15
|
resolveClaudeAllowedTools,
|
|
16
16
|
} from "./claude-subagents.js";
|
|
17
|
+
import {
|
|
18
|
+
claudeSandboxCapabilityMismatchResult,
|
|
19
|
+
claudeSandboxPolicyProblem,
|
|
20
|
+
} from "./claude-sandbox.js";
|
|
21
|
+
|
|
22
|
+
const CLAUDE_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh", "max"]);
|
|
23
|
+
const MAX_CLAUDE_ERROR_CHARS = 2_000;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Preserve the provider default when effort is omitted. The current Agent SDK
|
|
27
|
+
* accepts the five values below verbatim; mono-agent must not infer thinking
|
|
28
|
+
* enablement/disablement from a requested effort level.
|
|
29
|
+
* @param {unknown} effort
|
|
30
|
+
* @returns {{effort?: "low" | "medium" | "high" | "xhigh" | "max"}}
|
|
31
|
+
*/
|
|
32
|
+
export function claudeEffortOptions(effort) {
|
|
33
|
+
if (effort == null || String(effort).trim() === "") return {};
|
|
34
|
+
const normalized = String(effort).trim();
|
|
35
|
+
if (normalized === "none") {
|
|
36
|
+
throw new Error(
|
|
37
|
+
'Claude Agent SDK does not support effort "none". Omit effort to use the provider default, or choose low, medium, high, xhigh, or max.',
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (!CLAUDE_EFFORT_LEVELS.has(normalized)) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Claude Agent SDK does not support effort "${boundedText(normalized, 64)}". Choose low, medium, high, xhigh, or max, or omit effort.`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return { effort: /** @type {"low" | "medium" | "high" | "xhigh" | "max"} */ (normalized) };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function boundedText(value, limit = MAX_CLAUDE_ERROR_CHARS) {
|
|
49
|
+
const text = String(value ?? "").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
|
|
50
|
+
if (text.length <= limit) return text;
|
|
51
|
+
return `${text.slice(0, Math.max(0, limit - 16))}… [truncated]`;
|
|
52
|
+
}
|
|
17
53
|
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
54
|
+
function createClaudeSdkEnvironment(overrides, providerEnvironment) {
|
|
55
|
+
return {
|
|
56
|
+
...process.env,
|
|
57
|
+
...(overrides && typeof overrides === "object" ? overrides : {}),
|
|
58
|
+
...(providerEnvironment && typeof providerEnvironment === "object" ? providerEnvironment : {}),
|
|
59
|
+
MCP_CONNECTION_NONBLOCKING: "0",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** @param {string} model @param {unknown} contextWindow */
|
|
64
|
+
export function claudeSdkModelForQuery(model, contextWindow) {
|
|
65
|
+
return modelWithContextWindow(model, contextWindow);
|
|
21
66
|
}
|
|
22
67
|
|
|
23
68
|
function extractText(event) {
|
|
@@ -36,6 +81,12 @@ function assistantToolNames(event) {
|
|
|
36
81
|
.map((block) => block.name);
|
|
37
82
|
}
|
|
38
83
|
|
|
84
|
+
function assistantThinkingObserved(event) {
|
|
85
|
+
return event?.type === "assistant"
|
|
86
|
+
&& Array.isArray(event.message?.content)
|
|
87
|
+
&& event.message.content.some((block) => block?.type === "thinking" || block?.type === "redacted_thinking");
|
|
88
|
+
}
|
|
89
|
+
|
|
39
90
|
function extractResultText(event) {
|
|
40
91
|
if (event.type !== "result") return "";
|
|
41
92
|
if (typeof event.result === "string") return event.result;
|
|
@@ -52,6 +103,117 @@ function stringifyError(value) {
|
|
|
52
103
|
try { return JSON.stringify(value); } catch { return String(value); }
|
|
53
104
|
}
|
|
54
105
|
|
|
106
|
+
function claudeAssistantFailure(code, requestId = null) {
|
|
107
|
+
const normalizedCode = boundedText(code || "unknown", 80);
|
|
108
|
+
const mapping = {
|
|
109
|
+
authentication_failed: {
|
|
110
|
+
message: "Claude authentication failed. Sign in again or provide a valid Claude credential.",
|
|
111
|
+
failureKind: "provider_auth",
|
|
112
|
+
category: "authentication",
|
|
113
|
+
retryable: false,
|
|
114
|
+
},
|
|
115
|
+
oauth_org_not_allowed: {
|
|
116
|
+
message: "Claude authentication succeeded, but this organization does not allow the OAuth session.",
|
|
117
|
+
failureKind: "provider_auth",
|
|
118
|
+
category: "authentication",
|
|
119
|
+
retryable: false,
|
|
120
|
+
},
|
|
121
|
+
rate_limit: {
|
|
122
|
+
message: "Claude usage or rate limit reached.",
|
|
123
|
+
failureKind: "usage_limit",
|
|
124
|
+
category: "usage_limit",
|
|
125
|
+
retryable: false,
|
|
126
|
+
},
|
|
127
|
+
max_output_tokens: {
|
|
128
|
+
message: "Claude reached the maximum output-token limit.",
|
|
129
|
+
failureKind: "usage_limit",
|
|
130
|
+
category: "usage_limit",
|
|
131
|
+
retryable: false,
|
|
132
|
+
},
|
|
133
|
+
overloaded: {
|
|
134
|
+
message: "Claude is temporarily overloaded.",
|
|
135
|
+
failureKind: "provider_unavailable",
|
|
136
|
+
category: "provider_unavailable",
|
|
137
|
+
retryable: true,
|
|
138
|
+
},
|
|
139
|
+
server_error: {
|
|
140
|
+
message: "Claude returned a temporary server error.",
|
|
141
|
+
failureKind: "provider_unavailable",
|
|
142
|
+
category: "provider_unavailable",
|
|
143
|
+
retryable: true,
|
|
144
|
+
},
|
|
145
|
+
billing_error: {
|
|
146
|
+
message: "Claude rejected the request because the account needs billing attention.",
|
|
147
|
+
failureKind: "provider_unavailable",
|
|
148
|
+
category: "nonretryable",
|
|
149
|
+
retryable: false,
|
|
150
|
+
},
|
|
151
|
+
invalid_request: {
|
|
152
|
+
message: "Claude rejected the request as invalid.",
|
|
153
|
+
failureKind: "provider_unavailable",
|
|
154
|
+
category: "nonretryable",
|
|
155
|
+
retryable: false,
|
|
156
|
+
},
|
|
157
|
+
model_not_found: {
|
|
158
|
+
message: "Claude could not find or access the requested model.",
|
|
159
|
+
failureKind: "provider_unavailable",
|
|
160
|
+
category: "nonretryable",
|
|
161
|
+
retryable: false,
|
|
162
|
+
},
|
|
163
|
+
unknown: {
|
|
164
|
+
message: "Claude reported an unknown provider error.",
|
|
165
|
+
failureKind: "provider_unavailable",
|
|
166
|
+
category: "unknown",
|
|
167
|
+
retryable: false,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
const selected = mapping[normalizedCode] || mapping.unknown;
|
|
171
|
+
const safeRequestId = typeof requestId === "string" && requestId.trim()
|
|
172
|
+
? boundedText(requestId, 160)
|
|
173
|
+
: null;
|
|
174
|
+
return {
|
|
175
|
+
...selected,
|
|
176
|
+
code: normalizedCode,
|
|
177
|
+
requestId: safeRequestId,
|
|
178
|
+
message: `${selected.message}${safeRequestId ? ` Request ID: ${safeRequestId}.` : ""}`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function resultFailureCategory(event, resultError) {
|
|
183
|
+
const text = `${resultError?.message || ""} ${Array.isArray(event?.errors) ? event.errors.join(" ") : ""}`;
|
|
184
|
+
if (/auth|oauth|api key|401|403|sign[ -]?in|log[ -]?in/i.test(text)) {
|
|
185
|
+
return claudeAssistantFailure("authentication_failed");
|
|
186
|
+
}
|
|
187
|
+
if (event?.subtype === "error_max_turns" || event?.subtype === "error_max_budget_usd") {
|
|
188
|
+
return {
|
|
189
|
+
message: boundedText(resultError?.message || "Claude usage limit reached."),
|
|
190
|
+
failureKind: "usage_limit",
|
|
191
|
+
category: "usage_limit",
|
|
192
|
+
retryable: false,
|
|
193
|
+
code: event.subtype,
|
|
194
|
+
requestId: null,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (/overload|temporar|server error|\b50[0234]\b/i.test(text)) {
|
|
198
|
+
return {
|
|
199
|
+
message: boundedText(resultError?.message || "Claude is temporarily unavailable."),
|
|
200
|
+
failureKind: "provider_unavailable",
|
|
201
|
+
category: "provider_unavailable",
|
|
202
|
+
retryable: true,
|
|
203
|
+
code: event?.subtype || "result_error",
|
|
204
|
+
requestId: null,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
message: boundedText(resultError?.message || "Claude request failed."),
|
|
209
|
+
failureKind: resultError?.failureKind || "provider_unavailable",
|
|
210
|
+
category: resultError?.failureKind === "invalid_result" ? "nonretryable" : "unknown",
|
|
211
|
+
retryable: false,
|
|
212
|
+
code: event?.subtype || "result_error",
|
|
213
|
+
requestId: null,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
55
217
|
function humanizeSubtype(subtype) {
|
|
56
218
|
return String(subtype || "").replace(/^error_/, "").replace(/_/g, " ").trim();
|
|
57
219
|
}
|
|
@@ -69,7 +231,7 @@ function resultEventError(event) {
|
|
|
69
231
|
? "Claude stopped before final output: max turns reached"
|
|
70
232
|
: `Claude result error${label ? ` (${label})` : ""}${detail ? `: ${detail}` : ""}`;
|
|
71
233
|
return {
|
|
72
|
-
message,
|
|
234
|
+
message: boundedText(message),
|
|
73
235
|
failureKind: subtype === "error_max_turns"
|
|
74
236
|
? "usage_limit"
|
|
75
237
|
: subtype === "error_max_structured_output_retries"
|
|
@@ -161,21 +323,32 @@ function buildClaudeErrorDetails({
|
|
|
161
323
|
toolResultsSeen = 0,
|
|
162
324
|
numTurns = 0,
|
|
163
325
|
lastStructuredOutputRejection = null,
|
|
326
|
+
failureCode = null,
|
|
327
|
+
failureCategory = null,
|
|
328
|
+
retryable = null,
|
|
329
|
+
requestId = null,
|
|
164
330
|
}) {
|
|
165
|
-
const
|
|
331
|
+
const rawSubtype = subtype || event?.subtype || event?.type || null;
|
|
332
|
+
const resolvedSubtype = rawSubtype == null ? null : boundedText(rawSubtype, 160);
|
|
166
333
|
const turnCount = Number(event?.num_turns ?? numTurns) || 0;
|
|
167
334
|
const excerpt = lastTextSnippet(assistantTexts);
|
|
168
335
|
return {
|
|
169
336
|
claude_error_subtype: resolvedSubtype,
|
|
170
337
|
last_text_excerpt: excerpt,
|
|
171
|
-
last_tool_name: lastToolName
|
|
338
|
+
last_tool_name: lastToolName ? boundedText(lastToolName, 160) : null,
|
|
172
339
|
had_partial_progress: !!(excerpt || lastToolName || toolResultsSeen > 0),
|
|
173
340
|
tool_results_seen: toolResultsSeen,
|
|
174
341
|
turn_count: turnCount,
|
|
175
342
|
max_turns_hit: resolvedSubtype === "error_max_turns",
|
|
176
343
|
structured_output_retry_exhausted: resolvedSubtype === "error_max_structured_output_retries",
|
|
177
|
-
last_structured_output_rejection: lastStructuredOutputRejection
|
|
178
|
-
|
|
344
|
+
last_structured_output_rejection: lastStructuredOutputRejection
|
|
345
|
+
? boundedText(lastStructuredOutputRejection, 500)
|
|
346
|
+
: null,
|
|
347
|
+
provider_session_id: providerSessionId ? boundedText(providerSessionId, 160) : null,
|
|
348
|
+
claude_error_code: failureCode ? boundedText(failureCode, 80) : null,
|
|
349
|
+
claude_error_category: failureCategory || null,
|
|
350
|
+
retryable: typeof retryable === "boolean" ? retryable : null,
|
|
351
|
+
request_id: requestId || null,
|
|
179
352
|
};
|
|
180
353
|
}
|
|
181
354
|
|
|
@@ -372,7 +545,7 @@ function createClaudeCanUseTool(approvalManager, modelName) {
|
|
|
372
545
|
toolName,
|
|
373
546
|
input,
|
|
374
547
|
model: modelName,
|
|
375
|
-
toolUseId: context?.toolUseId || context?.tool_use_id || null,
|
|
548
|
+
toolUseId: context?.toolUseID || context?.toolUseId || context?.tool_use_id || null,
|
|
376
549
|
});
|
|
377
550
|
if (decision.decision === "deny") {
|
|
378
551
|
return {
|
|
@@ -395,7 +568,7 @@ export async function generateClaudeResponse(systemPrompt, options) {
|
|
|
395
568
|
const {
|
|
396
569
|
messages,
|
|
397
570
|
model,
|
|
398
|
-
effort
|
|
571
|
+
effort,
|
|
399
572
|
cwd,
|
|
400
573
|
mcpServers,
|
|
401
574
|
allowedTools,
|
|
@@ -407,7 +580,45 @@ export async function generateClaudeResponse(systemPrompt, options) {
|
|
|
407
580
|
onEvent = () => {},
|
|
408
581
|
} = options;
|
|
409
582
|
|
|
410
|
-
|
|
583
|
+
let effortOptions;
|
|
584
|
+
try {
|
|
585
|
+
effortOptions = claudeEffortOptions(effort);
|
|
586
|
+
} catch (error) {
|
|
587
|
+
const message = boundedText(error?.message || error);
|
|
588
|
+
return {
|
|
589
|
+
text: "",
|
|
590
|
+
structuredResult: undefined,
|
|
591
|
+
structuredResultSource: null,
|
|
592
|
+
events: [],
|
|
593
|
+
usage: {},
|
|
594
|
+
durationMs: 0,
|
|
595
|
+
numTurns: 0,
|
|
596
|
+
model: model.model,
|
|
597
|
+
effort: effort ?? null,
|
|
598
|
+
sdk: "claude",
|
|
599
|
+
cancelled: false,
|
|
600
|
+
error: message,
|
|
601
|
+
errorDetails: {
|
|
602
|
+
claude_error_code: "claude_effort_unsupported",
|
|
603
|
+
claude_error_category: "nonretryable",
|
|
604
|
+
retryable: false,
|
|
605
|
+
},
|
|
606
|
+
failureKind: "skipped_capability_mismatch",
|
|
607
|
+
providerSessionId: pickSessionId(options.sessionId, options.providerSessionId),
|
|
608
|
+
runtimeWarnings: [],
|
|
609
|
+
capabilitiesUsed: buildCapabilitiesUsed({ thinkingEnabled: null }),
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (claudeSandboxPolicyProblem(options)) {
|
|
614
|
+
return claudeSandboxCapabilityMismatchResult({
|
|
615
|
+
model: model.reference || `claude:${model.model}`,
|
|
616
|
+
effort,
|
|
617
|
+
sdk: "claude",
|
|
618
|
+
providerSessionId: pickSessionId(options.sessionId, options.providerSessionId),
|
|
619
|
+
outputSchema: options.outputSchema,
|
|
620
|
+
});
|
|
621
|
+
}
|
|
411
622
|
|
|
412
623
|
const promptString = promptStringFromMessages(messages);
|
|
413
624
|
const runtimeWarnings = [];
|
|
@@ -467,18 +678,34 @@ export async function generateClaudeResponse(systemPrompt, options) {
|
|
|
467
678
|
// toolset (every tool, incl. Task — not double-added). disallowedTools still
|
|
468
679
|
// flows through, so deny-wins holds under allow-all.
|
|
469
680
|
const { allowAll: allowAllTools, tools: resolvedAllowedTools } = resolveClaudeAllowedTools(allowedTools, options.nativeSubagents);
|
|
681
|
+
const hasExplicitToolProjection = Array.isArray(allowedTools) && !allowAllTools;
|
|
682
|
+
const internalAbortController = new AbortController();
|
|
683
|
+
const disposableSession = options.persistSession === false
|
|
684
|
+
|| options.disposable === true
|
|
685
|
+
|| options.readinessProbe === true
|
|
686
|
+
|| options.sessionKeepAlive === false;
|
|
470
687
|
// Assembled incrementally, then handed across the SDK `query` boundary
|
|
471
688
|
// (outputFormat/resume/maxTurns are attached conditionally below).
|
|
472
689
|
/** @type {any} */
|
|
473
690
|
const queryOptions = {
|
|
474
691
|
systemPrompt,
|
|
475
|
-
model:
|
|
692
|
+
model: claudeSdkModelForQuery(model.model, options.contextWindow),
|
|
476
693
|
cwd,
|
|
477
694
|
permissionMode: effectivePermissionMode,
|
|
478
695
|
...(effectivePermissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {}),
|
|
479
|
-
|
|
696
|
+
// `tools` is the SDK's availability projection. In particular, [] must
|
|
697
|
+
// remain [] so a readiness/discovery call cannot silently regain defaults.
|
|
698
|
+
...(hasExplicitToolProjection ? { tools: resolvedAllowedTools } : {}),
|
|
699
|
+
// `allowedTools` only controls auto-approval. Never provide it alongside
|
|
700
|
+
// canUseTool, where it would bypass the host approval callback.
|
|
701
|
+
...(!approvalManager && hasExplicitToolProjection ? { allowedTools: resolvedAllowedTools } : {}),
|
|
480
702
|
disallowedTools,
|
|
481
|
-
mcpServers,
|
|
703
|
+
mcpServers: mcpServers || {},
|
|
704
|
+
strictMcpConfig: true,
|
|
705
|
+
settingSources: [],
|
|
706
|
+
env: createClaudeSdkEnvironment(options.env, options.providerEnv),
|
|
707
|
+
abortController: internalAbortController,
|
|
708
|
+
...(disposableSession ? { persistSession: false } : options.persistSession === true ? { persistSession: true } : {}),
|
|
482
709
|
...(approvalManager ? { canUseTool: createClaudeCanUseTool(approvalManager, model.model) } : {}),
|
|
483
710
|
...(nativeAgents ? { agents: nativeAgents } : {}),
|
|
484
711
|
hooks: mergeHookMatchers(hooks, createClaudeRuntimeHooks({
|
|
@@ -489,7 +716,7 @@ export async function generateClaudeResponse(systemPrompt, options) {
|
|
|
489
716
|
onToolUse: noteToolUse,
|
|
490
717
|
onToolResult: noteToolResult,
|
|
491
718
|
})),
|
|
492
|
-
...
|
|
719
|
+
...effortOptions,
|
|
493
720
|
};
|
|
494
721
|
if (options.outputSchema) {
|
|
495
722
|
queryOptions.outputFormat = {
|
|
@@ -515,7 +742,8 @@ export async function generateClaudeResponse(systemPrompt, options) {
|
|
|
515
742
|
runtime: "sdk",
|
|
516
743
|
timestamp: providerRequestStartedAt,
|
|
517
744
|
});
|
|
518
|
-
|
|
745
|
+
/** @type {ReturnType<typeof query> | null} */
|
|
746
|
+
let stream = null;
|
|
519
747
|
|
|
520
748
|
let text = "";
|
|
521
749
|
let usage = {};
|
|
@@ -531,6 +759,9 @@ export async function generateClaudeResponse(systemPrompt, options) {
|
|
|
531
759
|
let structuredResult = undefined;
|
|
532
760
|
let errorDetails = null;
|
|
533
761
|
let lastStructuredOutputRejection = null;
|
|
762
|
+
let totalCostUsd = null;
|
|
763
|
+
let thinkingObserved = false;
|
|
764
|
+
let structuredTerminalFailure = null;
|
|
534
765
|
const pendingStructuredOutputById = new Map();
|
|
535
766
|
|
|
536
767
|
const rawFinalText = () => resultText || text;
|
|
@@ -549,16 +780,18 @@ export async function generateClaudeResponse(systemPrompt, options) {
|
|
|
549
780
|
runtimeWarnings.push(makeRuntimeWarning(message));
|
|
550
781
|
}
|
|
551
782
|
|
|
552
|
-
const abortHandler =
|
|
783
|
+
const abortHandler = () => {
|
|
553
784
|
cancelled = true;
|
|
554
|
-
|
|
785
|
+
internalAbortController.abort();
|
|
786
|
+
try { stream?.close?.(); } catch { /* best effort; finally closes again */ }
|
|
555
787
|
};
|
|
556
788
|
if (abortSignal) {
|
|
557
|
-
if (abortSignal.aborted)
|
|
789
|
+
if (abortSignal.aborted) abortHandler();
|
|
558
790
|
else abortSignal.addEventListener("abort", abortHandler, { once: true });
|
|
559
791
|
}
|
|
560
792
|
|
|
561
793
|
try {
|
|
794
|
+
stream = query({ prompt: /** @type {any} */ (prompt), options: queryOptions });
|
|
562
795
|
for await (const event of stream) {
|
|
563
796
|
const nextSessionId = sessionIdFromEvent(event);
|
|
564
797
|
if (nextSessionId) providerSessionId = nextSessionId;
|
|
@@ -584,6 +817,25 @@ export async function generateClaudeResponse(systemPrompt, options) {
|
|
|
584
817
|
emitEvent(structuredOutputEvent(eventStructuredOutput));
|
|
585
818
|
}
|
|
586
819
|
if (event.type === "assistant") {
|
|
820
|
+
thinkingObserved = thinkingObserved || assistantThinkingObserved(event);
|
|
821
|
+
if (event.error && !structuredTerminalFailure) {
|
|
822
|
+
const assistantFailure = claudeAssistantFailure(event.error, event.request_id);
|
|
823
|
+
structuredTerminalFailure = assistantFailure;
|
|
824
|
+
errorDetails = buildClaudeErrorDetails({
|
|
825
|
+
event,
|
|
826
|
+
subtype: event.error,
|
|
827
|
+
providerSessionId,
|
|
828
|
+
assistantTexts: assistantTextFragments,
|
|
829
|
+
lastToolName,
|
|
830
|
+
toolResultsSeen,
|
|
831
|
+
numTurns,
|
|
832
|
+
lastStructuredOutputRejection,
|
|
833
|
+
failureCode: assistantFailure.code,
|
|
834
|
+
failureCategory: assistantFailure.category,
|
|
835
|
+
retryable: assistantFailure.retryable,
|
|
836
|
+
requestId: assistantFailure.requestId,
|
|
837
|
+
});
|
|
838
|
+
}
|
|
587
839
|
const delta = extractText(event);
|
|
588
840
|
if (delta) assistantTextFragments.push(delta);
|
|
589
841
|
text += delta;
|
|
@@ -594,8 +846,12 @@ export async function generateClaudeResponse(systemPrompt, options) {
|
|
|
594
846
|
// narrowed union.
|
|
595
847
|
else if (/** @type {any} */ (event).type === "error") {
|
|
596
848
|
const errorEvent = /** @type {any} */ (event);
|
|
597
|
-
const message = errorEvent.error?.message || errorEvent.error || "sdk stream error";
|
|
598
|
-
if (
|
|
849
|
+
const message = boundedText(errorEvent.error?.message || errorEvent.error || "sdk stream error");
|
|
850
|
+
if (structuredTerminalFailure) {
|
|
851
|
+
// A typed assistant error is authoritative. A later transport error
|
|
852
|
+
// cannot turn authentication/billing diagnostics into a generic
|
|
853
|
+
// provider failure.
|
|
854
|
+
} else if (hasPreservableFinalOutput()) {
|
|
599
855
|
preservePostSuccessError(`Claude SDK emitted an error after final output; preserved final result. ${message}`);
|
|
600
856
|
} else {
|
|
601
857
|
errorMessage = message;
|
|
@@ -613,6 +869,7 @@ export async function generateClaudeResponse(systemPrompt, options) {
|
|
|
613
869
|
}
|
|
614
870
|
break;
|
|
615
871
|
} else if (event.type === "result") {
|
|
872
|
+
if (Number.isFinite(Number(event.total_cost_usd))) totalCostUsd = Number(event.total_cost_usd);
|
|
616
873
|
const resultError = resultEventError(event);
|
|
617
874
|
if (resultError) {
|
|
618
875
|
if (!successfulResultSeen) {
|
|
@@ -620,15 +877,19 @@ export async function generateClaudeResponse(systemPrompt, options) {
|
|
|
620
877
|
durationMs = event.duration_ms || durationMs;
|
|
621
878
|
numTurns = event.num_turns || numTurns;
|
|
622
879
|
}
|
|
623
|
-
if (
|
|
880
|
+
if (structuredTerminalFailure) {
|
|
881
|
+
// Retain the typed assistant error and request id. The result still
|
|
882
|
+
// contributes usage/duration/cost above.
|
|
883
|
+
} else if (hasPreservableFinalOutput()) {
|
|
624
884
|
preservePostSuccessError(`Claude SDK emitted an error after final output; preserved final result. ${resultError.message}`);
|
|
625
885
|
successfulResultSeen = true;
|
|
626
886
|
} else {
|
|
887
|
+
const categorized = resultFailureCategory(event, resultError);
|
|
627
888
|
usage = event.usage || usage;
|
|
628
889
|
durationMs = event.duration_ms || durationMs;
|
|
629
890
|
numTurns = event.num_turns || numTurns;
|
|
630
|
-
errorMessage =
|
|
631
|
-
failureKind =
|
|
891
|
+
errorMessage = categorized.message;
|
|
892
|
+
failureKind = categorized.failureKind;
|
|
632
893
|
if (failureKind === "invalid_result") {
|
|
633
894
|
runtimeWarnings.push(makeRuntimeWarning(
|
|
634
895
|
resultError.message,
|
|
@@ -644,6 +905,10 @@ export async function generateClaudeResponse(systemPrompt, options) {
|
|
|
644
905
|
toolResultsSeen,
|
|
645
906
|
numTurns,
|
|
646
907
|
lastStructuredOutputRejection,
|
|
908
|
+
failureCode: categorized.code,
|
|
909
|
+
failureCategory: categorized.category,
|
|
910
|
+
retryable: categorized.retryable,
|
|
911
|
+
requestId: categorized.requestId,
|
|
647
912
|
});
|
|
648
913
|
}
|
|
649
914
|
} else {
|
|
@@ -659,8 +924,10 @@ export async function generateClaudeResponse(systemPrompt, options) {
|
|
|
659
924
|
}
|
|
660
925
|
} catch (err) {
|
|
661
926
|
if (!cancelled) {
|
|
662
|
-
const message = err?.message || String(err);
|
|
663
|
-
if (
|
|
927
|
+
const message = boundedText(err?.message || String(err));
|
|
928
|
+
if (structuredTerminalFailure) {
|
|
929
|
+
// Keep the earlier typed provider failure and its request id.
|
|
930
|
+
} else if (successfulResultSeen && hasUsableFinalOutput()) {
|
|
664
931
|
preservePostSuccessError(`Claude SDK stream failed after final output; preserved final result. ${message}`);
|
|
665
932
|
} else {
|
|
666
933
|
errorMessage = message;
|
|
@@ -673,26 +940,37 @@ export async function generateClaudeResponse(systemPrompt, options) {
|
|
|
673
940
|
toolResultsSeen,
|
|
674
941
|
numTurns,
|
|
675
942
|
lastStructuredOutputRejection,
|
|
943
|
+
failureCode: "exception",
|
|
944
|
+
failureCategory: "unknown",
|
|
945
|
+
retryable: false,
|
|
676
946
|
});
|
|
677
947
|
}
|
|
678
948
|
}
|
|
679
949
|
} finally {
|
|
950
|
+
try { stream?.close?.(); } catch { /* best effort after every terminal path */ }
|
|
680
951
|
if (abortSignal) abortSignal.removeEventListener?.("abort", abortHandler);
|
|
681
952
|
}
|
|
682
953
|
|
|
954
|
+
if (structuredTerminalFailure) {
|
|
955
|
+
errorMessage = structuredTerminalFailure.message;
|
|
956
|
+
failureKind = structuredTerminalFailure.failureKind;
|
|
957
|
+
}
|
|
958
|
+
|
|
683
959
|
const reference = model.reference || `claude:${model.model}`;
|
|
684
960
|
const inputTokens = usage?.input_tokens ?? usage?.inputTokens ?? 0;
|
|
685
961
|
const outputTokens = usage?.output_tokens ?? usage?.outputTokens ?? 0;
|
|
686
962
|
const cachedTokens = usage?.cache_read_input_tokens ?? usage?.cache_read_tokens ?? 0;
|
|
687
963
|
const cacheCreationTokens = usage?.cache_creation_input_tokens ?? usage?.cache_creation_tokens ?? 0;
|
|
688
|
-
const costUsd =
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
964
|
+
const costUsd = Number.isFinite(totalCostUsd)
|
|
965
|
+
? totalCostUsd
|
|
966
|
+
: estimateCost({
|
|
967
|
+
resolveCustomPricing: options.resolveCustomPricing,
|
|
968
|
+
model: reference,
|
|
969
|
+
inputTokens,
|
|
970
|
+
outputTokens,
|
|
971
|
+
cachedTokens,
|
|
972
|
+
cacheWriteTokens: cacheCreationTokens,
|
|
973
|
+
});
|
|
696
974
|
const enrichedUsage = {
|
|
697
975
|
...usage,
|
|
698
976
|
input_tokens: inputTokens || null,
|
|
@@ -736,7 +1014,7 @@ export async function generateClaudeResponse(systemPrompt, options) {
|
|
|
736
1014
|
: [];
|
|
737
1015
|
const capabilitiesUsed = buildCapabilitiesUsed({
|
|
738
1016
|
promptCacheActive: cachedTokens > 0 || cacheCreationTokens > 0,
|
|
739
|
-
thinkingEnabled:
|
|
1017
|
+
thinkingEnabled: thinkingObserved ? true : null,
|
|
740
1018
|
structuredOutputEnforced: !!options.outputSchema,
|
|
741
1019
|
// Claude SDK doesn't surface a per-call "subagent was invoked" signal,
|
|
742
1020
|
// so we report null when subagents were configured (unknown) and false
|