claude-code-rust 0.7.1 → 0.8.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.
@@ -13,6 +13,9 @@ export const sessions = new Map();
13
13
  const DEFAULT_SETTING_SOURCES = ["user", "project", "local"];
14
14
  const DEFAULT_MODEL_NAME = "default";
15
15
  const DEFAULT_PERMISSION_MODE = "default";
16
+ function settingsObjectFromLaunchSettings(launchSettings) {
17
+ return launchSettings.settings;
18
+ }
16
19
  export function sessionById(sessionId) {
17
20
  return sessions.get(sessionId) ?? null;
18
21
  }
@@ -32,6 +35,14 @@ export async function closeSession(session) {
32
35
  pending.onOutcome?.({ outcome: "cancelled" });
33
36
  }
34
37
  session.pendingPermissions.clear();
38
+ for (const pending of session.pendingQuestions.values()) {
39
+ pending.onOutcome({ outcome: "cancelled" });
40
+ }
41
+ session.pendingQuestions.clear();
42
+ for (const pending of session.pendingElicitations.values()) {
43
+ pending.resolve({ action: "cancel" });
44
+ }
45
+ session.pendingElicitations.clear();
35
46
  }
36
47
  export async function closeAllSessions() {
37
48
  const active = Array.from(sessions.values());
@@ -54,7 +65,7 @@ export async function createSession(params) {
54
65
  `decision_reason=${options.decisionReason ?? "<none>"} suggestions=${formatPermissionUpdates(options.suggestions)}`);
55
66
  const existing = ensureToolCallVisible(session, toolUseId, toolName, inputData);
56
67
  if (toolName === ASK_USER_QUESTION_TOOL_NAME) {
57
- return await requestAskUserQuestionAnswers(session, toolUseId, toolName, inputData, existing);
68
+ return await requestAskUserQuestionAnswers(session, toolUseId, inputData, existing);
58
69
  }
59
70
  const request = {
60
71
  tool_call: existing,
@@ -117,6 +128,9 @@ export async function createSession(params) {
117
128
  toolCalls: new Map(),
118
129
  taskToolUseIds: new Map(),
119
130
  pendingPermissions: new Map(),
131
+ pendingQuestions: new Map(),
132
+ pendingElicitations: new Map(),
133
+ mcpStatusRevalidatedAt: new Map(),
120
134
  authHintSent: false,
121
135
  ...(params.resumeUpdates && params.resumeUpdates.length > 0
122
136
  ? { resumeUpdates: params.resumeUpdates }
@@ -183,8 +197,8 @@ export async function createSession(params) {
183
197
  }
184
198
  })();
185
199
  }
186
- function permissionModeFromLaunchSettings(rawMode) {
187
- if (rawMode === undefined) {
200
+ function permissionModeFromSettingsValue(rawMode) {
201
+ if (typeof rawMode !== "string") {
188
202
  return undefined;
189
203
  }
190
204
  switch (rawMode) {
@@ -195,32 +209,20 @@ function permissionModeFromLaunchSettings(rawMode) {
195
209
  case "dontAsk":
196
210
  return rawMode;
197
211
  default:
198
- throw new Error(`unsupported launch_settings.permission_mode: ${rawMode}`);
212
+ throw new Error(`unsupported launch_settings.settings.permissions.defaultMode: ${rawMode}`);
199
213
  }
200
214
  }
201
215
  function initialSessionModel(launchSettings) {
202
- return launchSettings.model?.trim() || DEFAULT_MODEL_NAME;
216
+ const settings = settingsObjectFromLaunchSettings(launchSettings);
217
+ const model = typeof settings?.model === "string" ? settings.model.trim() : "";
218
+ return model || DEFAULT_MODEL_NAME;
203
219
  }
204
220
  function initialSessionMode(launchSettings) {
205
- return permissionModeFromLaunchSettings(launchSettings.permission_mode) ?? DEFAULT_PERMISSION_MODE;
206
- }
207
- function thinkingConfigFromLaunchSettings(launchSettings) {
208
- switch (launchSettings.thinking_mode) {
209
- case undefined:
210
- return undefined;
211
- case "adaptive":
212
- return { type: "adaptive" };
213
- case "disabled":
214
- return { type: "disabled" };
215
- default:
216
- throw new Error(`unsupported launch_settings.thinking_mode: ${String(launchSettings.thinking_mode)}`);
217
- }
218
- }
219
- function effortFromLaunchSettings(launchSettings) {
220
- if (launchSettings.thinking_mode !== "adaptive") {
221
- return undefined;
222
- }
223
- return launchSettings.effort_level;
221
+ const settings = settingsObjectFromLaunchSettings(launchSettings);
222
+ const permissions = settings?.permissions && typeof settings.permissions === "object" && !Array.isArray(settings.permissions)
223
+ ? settings.permissions
224
+ : undefined;
225
+ return permissionModeFromSettingsValue(permissions?.defaultMode) ?? DEFAULT_PERMISSION_MODE;
224
226
  }
225
227
  function systemPromptFromLaunchSettings(launchSettings) {
226
228
  const language = launchSettings.language?.trim();
@@ -235,20 +237,18 @@ function systemPromptFromLaunchSettings(launchSettings) {
235
237
  };
236
238
  }
237
239
  export function buildQueryOptions(params) {
238
- const permissionMode = permissionModeFromLaunchSettings(params.launchSettings.permission_mode);
239
- const thinking = thinkingConfigFromLaunchSettings(params.launchSettings);
240
- const effort = effortFromLaunchSettings(params.launchSettings);
241
240
  const systemPrompt = systemPromptFromLaunchSettings(params.launchSettings);
242
241
  return {
243
242
  cwd: params.cwd,
244
243
  includePartialMessages: true,
245
244
  executable: "node",
246
245
  ...(params.resume ? {} : { sessionId: params.provisionalSessionId }),
247
- ...(params.launchSettings.model ? { model: params.launchSettings.model } : {}),
246
+ ...(params.launchSettings.settings ? { settings: params.launchSettings.settings } : {}),
247
+ toolConfig: { askUserQuestion: { previewFormat: "markdown" } },
248
248
  ...(systemPrompt ? { systemPrompt } : {}),
249
- ...(permissionMode ? { permissionMode } : {}),
250
- ...(thinking ? { thinking } : {}),
251
- ...(effort ? { effort } : {}),
249
+ ...(params.launchSettings.agent_progress_summaries !== undefined
250
+ ? { agentProgressSummaries: params.launchSettings.agent_progress_summaries }
251
+ : {}),
252
252
  ...(params.claudeCodeExecutable
253
253
  ? { pathToClaudeCodeExecutable: params.claudeCodeExecutable }
254
254
  : {}),
@@ -283,21 +283,52 @@ export function buildQueryOptions(params) {
283
283
  resume: params.resume,
284
284
  canUseTool: params.canUseTool,
285
285
  onElicitation: async (request) => {
286
- const requestMode = typeof request.mode === "string" ? request.mode : "unknown";
287
- const requestServer = typeof request.serverName === "string" && request.serverName.trim().length > 0
288
- ? request.serverName
289
- : "unknown";
290
- const requestMessage = typeof request.message === "string" && request.message.trim().length > 0
291
- ? request.message
292
- : "<no message>";
293
- console.error(`[sdk warn] elicitation unsupported without MCP settings UI; ` +
294
- `auto-canceling session_id=${params.sessionIdForLogs()} server=${requestServer} ` +
295
- `mode=${requestMode} message=${JSON.stringify(requestMessage)}`);
296
- return { action: "cancel" };
286
+ const requestId = randomUUID();
287
+ const mode = request.mode === "form" || request.mode === "url"
288
+ ? request.mode
289
+ : typeof request.url === "string" && request.url.trim().length > 0
290
+ ? "url"
291
+ : "form";
292
+ const normalized = {
293
+ request_id: requestId,
294
+ server_name: typeof request.serverName === "string" && request.serverName.trim().length > 0
295
+ ? request.serverName
296
+ : "unknown",
297
+ message: typeof request.message === "string" && request.message.trim().length > 0
298
+ ? request.message
299
+ : "<no message>",
300
+ mode,
301
+ ...(typeof request.url === "string" && request.url.trim().length > 0
302
+ ? { url: request.url }
303
+ : {}),
304
+ ...(typeof request.elicitationId === "string" && request.elicitationId.trim().length > 0
305
+ ? { elicitation_id: request.elicitationId }
306
+ : {}),
307
+ ...(request.requestedSchema
308
+ ? { requested_schema: request.requestedSchema }
309
+ : {}),
310
+ };
311
+ writeEvent({
312
+ event: "elicitation_request",
313
+ session_id: params.sessionIdForLogs(),
314
+ request: normalized,
315
+ });
316
+ return await new Promise((resolve) => {
317
+ const currentSession = sessions.get(params.sessionIdForLogs());
318
+ if (!currentSession) {
319
+ resolve({ action: "cancel" });
320
+ return;
321
+ }
322
+ currentSession.pendingElicitations.set(requestId, {
323
+ resolve,
324
+ serverName: normalized.server_name,
325
+ elicitationId: normalized.elicitation_id,
326
+ });
327
+ });
297
328
  },
298
329
  };
299
330
  }
300
- function mapAvailableModels(models) {
331
+ export function mapAvailableModels(models) {
301
332
  if (!Array.isArray(models)) {
302
333
  return [];
303
334
  }
@@ -315,6 +346,15 @@ function mapAvailableModels(models) {
315
346
  supported_effort_levels: Array.isArray(entry.supportedEffortLevels)
316
347
  ? entry.supportedEffortLevels.filter((level) => level === "low" || level === "medium" || level === "high")
317
348
  : [],
349
+ ...(typeof entry.supportsAdaptiveThinking === "boolean"
350
+ ? { supports_adaptive_thinking: entry.supportsAdaptiveThinking }
351
+ : {}),
352
+ ...(typeof entry.supportsFastMode === "boolean"
353
+ ? { supports_fast_mode: entry.supportsFastMode }
354
+ : {}),
355
+ ...(typeof entry.supportsAutoMode === "boolean"
356
+ ? { supports_auto_mode: entry.supportsAutoMode }
357
+ : {}),
318
358
  ...(typeof entry.description === "string" && entry.description.trim().length > 0
319
359
  ? { description: entry.description }
320
360
  : {}),
@@ -366,3 +406,36 @@ export function handlePermissionResponse(command) {
366
406
  }
367
407
  resolver.resolve(permissionResult);
368
408
  }
409
+ export function handleQuestionResponse(command) {
410
+ const session = sessionById(command.session_id);
411
+ if (!session) {
412
+ logPermissionDebug(`question response dropped: unknown session session_id=${command.session_id} tool_call_id=${command.tool_call_id}`);
413
+ return;
414
+ }
415
+ const resolver = session.pendingQuestions.get(command.tool_call_id);
416
+ if (!resolver) {
417
+ logPermissionDebug(`question response dropped: no pending resolver session_id=${command.session_id} tool_call_id=${command.tool_call_id}`);
418
+ return;
419
+ }
420
+ session.pendingQuestions.delete(command.tool_call_id);
421
+ resolver.onOutcome(command.outcome);
422
+ }
423
+ export function handleElicitationResponse(command) {
424
+ const session = sessionById(command.session_id);
425
+ if (!session) {
426
+ console.error(`[sdk warn] elicitation response dropped: unknown session ` +
427
+ `session_id=${command.session_id} request_id=${command.elicitation_request_id}`);
428
+ return;
429
+ }
430
+ const pending = session.pendingElicitations.get(command.elicitation_request_id);
431
+ if (!pending) {
432
+ console.error(`[sdk warn] elicitation response dropped: no pending request ` +
433
+ `session_id=${command.session_id} request_id=${command.elicitation_request_id}`);
434
+ return;
435
+ }
436
+ session.pendingElicitations.delete(command.elicitation_request_id);
437
+ pending.resolve({
438
+ action: command.action,
439
+ ...(command.content ? { content: command.content } : {}),
440
+ });
441
+ }
@@ -67,9 +67,9 @@ export function emitPlanIfTodoWrite(session, name, input) {
67
67
  emitSessionUpdate(session.sessionId, { type: "plan", entries });
68
68
  }
69
69
  }
70
- export function emitToolResultUpdate(session, toolUseId, isError, rawContent) {
70
+ export function emitToolResultUpdate(session, toolUseId, isError, rawContent, rawResult = rawContent) {
71
71
  const base = session.toolCalls.get(toolUseId);
72
- const fields = buildToolResultFields(isError, rawContent, base);
72
+ const fields = buildToolResultFields(isError, rawContent, base, rawResult);
73
73
  const update = { tool_call_id: toolUseId, fields };
74
74
  emitSessionUpdate(session.sessionId, { type: "tool_call_update", tool_call_update: update });
75
75
  if (base) {
@@ -80,6 +80,9 @@ export function emitToolResultUpdate(session, toolUseId, isError, rawContent) {
80
80
  if (fields.content) {
81
81
  base.content = fields.content;
82
82
  }
83
+ if (fields.output_metadata) {
84
+ base.output_metadata = fields.output_metadata;
85
+ }
83
86
  }
84
87
  }
85
88
  export function finalizeOpenToolCalls(session, status) {
@@ -101,7 +104,9 @@ export function emitToolProgressUpdate(session, toolUseId, toolName) {
101
104
  emitToolCall(session, toolUseId, toolName, {});
102
105
  return;
103
106
  }
104
- if (existing.status === "in_progress") {
107
+ if (existing.status === "in_progress" ||
108
+ existing.status === "completed" ||
109
+ existing.status === "failed") {
105
110
  return;
106
111
  }
107
112
  const fields = { status: "in_progress" };
@@ -159,6 +164,10 @@ export function resolveTaskToolUseId(session, msg) {
159
164
  return session.taskToolUseIds.get(taskId) ?? "";
160
165
  }
161
166
  export function taskProgressText(msg) {
167
+ const summary = typeof msg.summary === "string" ? msg.summary.trim() : "";
168
+ if (summary) {
169
+ return summary;
170
+ }
162
171
  const description = typeof msg.description === "string" ? msg.description : "";
163
172
  const lastTool = typeof msg.last_tool_name === "string" ? msg.last_tool_name : "";
164
173
  if (description && lastTool) {
@@ -18,6 +18,7 @@ export function normalizeToolKind(name) {
18
18
  case "Bash":
19
19
  return "execute";
20
20
  case "Read":
21
+ case "ReadMcpResource":
21
22
  return "read";
22
23
  case "Write":
23
24
  case "Edit":
@@ -75,6 +76,16 @@ export function toolTitle(name, input) {
75
76
  if ((name === "Read" || name === "Write" || name === "Edit") && typeof input.file_path === "string") {
76
77
  return `${name} ${input.file_path}`;
77
78
  }
79
+ if (name === "ReadMcpResource") {
80
+ const uri = typeof input.uri === "string" ? input.uri : "";
81
+ const server = typeof input.server === "string" ? input.server : "";
82
+ if (server && uri) {
83
+ return `ReadMcpResource ${server} ${uri}`;
84
+ }
85
+ if (uri) {
86
+ return `ReadMcpResource ${uri}`;
87
+ }
88
+ }
78
89
  return name;
79
90
  }
80
91
  function editDiffContent(name, input) {
@@ -115,6 +126,145 @@ export function createToolCall(toolUseId, name, input) {
115
126
  },
116
127
  };
117
128
  }
129
+ function resultRecordCandidates(rawResult, rawContent) {
130
+ const candidates = [];
131
+ const pushRecord = (value) => {
132
+ const record = asRecordOrNull(value);
133
+ if (record) {
134
+ candidates.push(record);
135
+ }
136
+ };
137
+ const pushNestedRecords = (value) => {
138
+ const record = asRecordOrNull(value);
139
+ if (!record) {
140
+ return;
141
+ }
142
+ pushRecord(record.result);
143
+ pushRecord(record.data);
144
+ pushRecord(record.content);
145
+ };
146
+ pushRecord(rawResult);
147
+ pushNestedRecords(rawResult);
148
+ pushRecord(rawContent);
149
+ pushNestedRecords(rawContent);
150
+ return candidates;
151
+ }
152
+ function parseJsonCandidate(value) {
153
+ const text = typeof value === "string" ? value : extractText(value);
154
+ const trimmed = text.trim();
155
+ if (!(trimmed.startsWith("{") || trimmed.startsWith("["))) {
156
+ return undefined;
157
+ }
158
+ try {
159
+ return JSON.parse(trimmed);
160
+ }
161
+ catch {
162
+ return undefined;
163
+ }
164
+ }
165
+ function pushStructuredRecordCandidates(candidates, value) {
166
+ const record = asRecordOrNull(value);
167
+ if (!record) {
168
+ return;
169
+ }
170
+ candidates.push(record);
171
+ const nestedResult = asRecordOrNull(record.result);
172
+ if (nestedResult) {
173
+ candidates.push(nestedResult);
174
+ }
175
+ const nestedData = asRecordOrNull(record.data);
176
+ if (nestedData) {
177
+ candidates.push(nestedData);
178
+ }
179
+ const nestedContent = asRecordOrNull(record.content);
180
+ if (nestedContent) {
181
+ candidates.push(nestedContent);
182
+ }
183
+ }
184
+ function mcpResourceContentFromResult(rawResult, rawContent) {
185
+ const candidates = [];
186
+ for (const candidate of [rawResult, rawContent, parseJsonCandidate(rawResult), parseJsonCandidate(rawContent)]) {
187
+ pushStructuredRecordCandidates(candidates, candidate);
188
+ }
189
+ for (const candidate of candidates) {
190
+ const contents = Array.isArray(candidate.contents) ? candidate.contents : null;
191
+ if (!contents || contents.length === 0) {
192
+ continue;
193
+ }
194
+ const mapped = [];
195
+ for (const entry of contents) {
196
+ const record = asRecordOrNull(entry);
197
+ if (!record) {
198
+ continue;
199
+ }
200
+ const uri = typeof record.uri === "string" ? record.uri : "";
201
+ if (!uri) {
202
+ continue;
203
+ }
204
+ const text = typeof record.text === "string" && record.text.length > 0 ? record.text : undefined;
205
+ const mimeType = typeof record.mimeType === "string" && record.mimeType.trim().length > 0
206
+ ? record.mimeType.trim()
207
+ : undefined;
208
+ const blobSavedTo = typeof record.blobSavedTo === "string" && record.blobSavedTo.trim().length > 0
209
+ ? record.blobSavedTo.trim()
210
+ : undefined;
211
+ if (!text && !blobSavedTo) {
212
+ continue;
213
+ }
214
+ mapped.push({
215
+ type: "mcp_resource",
216
+ uri,
217
+ ...(mimeType ? { mime_type: mimeType } : {}),
218
+ ...(text ? { text } : {}),
219
+ ...(blobSavedTo ? { blob_saved_to: blobSavedTo } : {}),
220
+ });
221
+ }
222
+ if (mapped.length > 0) {
223
+ return mapped;
224
+ }
225
+ }
226
+ return [];
227
+ }
228
+ function extractToolOutputMetadata(toolName, rawResult, rawContent) {
229
+ const candidates = resultRecordCandidates(rawResult, rawContent);
230
+ if (toolName === "Bash") {
231
+ for (const candidate of candidates) {
232
+ const hasAssistantAutoBackgrounded = typeof candidate.assistantAutoBackgrounded === "boolean";
233
+ const hasTokenSaverOutput = typeof candidate.tokenSaverOutput === "string" && candidate.tokenSaverOutput.length > 0;
234
+ if (hasAssistantAutoBackgrounded || hasTokenSaverOutput) {
235
+ const bashMetadata = {};
236
+ if (hasAssistantAutoBackgrounded) {
237
+ bashMetadata.assistant_auto_backgrounded = candidate.assistantAutoBackgrounded;
238
+ }
239
+ if (hasTokenSaverOutput) {
240
+ bashMetadata.token_saver_active = true;
241
+ }
242
+ return {
243
+ bash: bashMetadata,
244
+ };
245
+ }
246
+ }
247
+ return undefined;
248
+ }
249
+ if (toolName === "ExitPlanMode") {
250
+ for (const candidate of candidates) {
251
+ if (typeof candidate.isUltraplan === "boolean") {
252
+ return { exit_plan_mode: { is_ultraplan: candidate.isUltraplan } };
253
+ }
254
+ }
255
+ return undefined;
256
+ }
257
+ if (toolName === "TodoWrite") {
258
+ for (const candidate of candidates) {
259
+ if (typeof candidate.verificationNudgeNeeded === "boolean") {
260
+ return {
261
+ todo_write: { verification_nudge_needed: candidate.verificationNudgeNeeded },
262
+ };
263
+ }
264
+ }
265
+ }
266
+ return undefined;
267
+ }
118
268
  export function extractText(value) {
119
269
  if (typeof value === "string") {
120
270
  return value;
@@ -255,21 +405,129 @@ function writeDiffFromResult(rawContent) {
255
405
  : "";
256
406
  const content = typeof record.content === "string" ? record.content : "";
257
407
  const originalRaw = "originalFile" in record ? record.originalFile : "original_file" in record ? record.original_file : undefined;
408
+ const gitDiff = asRecordOrNull(record.gitDiff);
409
+ const repository = typeof gitDiff?.repository === "string" && gitDiff.repository.trim().length > 0
410
+ ? gitDiff.repository.trim()
411
+ : undefined;
258
412
  if (!filePath || !content || originalRaw === undefined) {
259
413
  continue;
260
414
  }
261
415
  const original = typeof originalRaw === "string" ? originalRaw : originalRaw === null ? "" : "";
262
- return [{ type: "diff", old_path: filePath, new_path: filePath, old: original, new: content }];
416
+ return [
417
+ {
418
+ type: "diff",
419
+ old_path: filePath,
420
+ new_path: filePath,
421
+ old: original,
422
+ new: content,
423
+ ...(repository ? { repository } : {}),
424
+ },
425
+ ];
263
426
  }
264
427
  return [];
265
428
  }
266
- export function buildToolResultFields(isError, rawContent, base) {
267
- const rawOutput = normalizeToolResultText(rawContent, isError);
429
+ function editDiffFromResult(rawResult, rawInput) {
430
+ const input = asRecordOrNull(rawInput);
431
+ const filePath = typeof input?.file_path === "string" ? input.file_path : "";
432
+ const oldText = typeof input?.old_string === "string"
433
+ ? input.old_string
434
+ : typeof input?.oldString === "string"
435
+ ? input.oldString
436
+ : "";
437
+ const newText = typeof input?.new_string === "string"
438
+ ? input.new_string
439
+ : typeof input?.newString === "string"
440
+ ? input.newString
441
+ : "";
442
+ if (!filePath || (!oldText && !newText)) {
443
+ return [];
444
+ }
445
+ for (const candidate of resultRecordCandidates(rawResult, undefined)) {
446
+ const candidatePath = typeof candidate.filePath === "string"
447
+ ? candidate.filePath
448
+ : typeof candidate.file_path === "string"
449
+ ? candidate.file_path
450
+ : "";
451
+ const gitDiff = asRecordOrNull(candidate.gitDiff);
452
+ if (!candidatePath && !gitDiff) {
453
+ continue;
454
+ }
455
+ if (candidatePath && candidatePath !== filePath) {
456
+ continue;
457
+ }
458
+ const repository = typeof gitDiff?.repository === "string" && gitDiff.repository.trim().length > 0
459
+ ? gitDiff.repository.trim()
460
+ : undefined;
461
+ return [
462
+ {
463
+ type: "diff",
464
+ old_path: filePath,
465
+ new_path: filePath,
466
+ old: oldText,
467
+ new: newText,
468
+ ...(repository ? { repository } : {}),
469
+ },
470
+ ];
471
+ }
472
+ return editDiffFromInput(rawInput);
473
+ }
474
+ function findBashResultRecord(rawResult, rawContent) {
475
+ return resultRecordCandidates(rawResult, rawContent).find((candidate) => "stdout" in candidate ||
476
+ "stderr" in candidate ||
477
+ "backgroundTaskId" in candidate ||
478
+ "backgroundedByUser" in candidate ||
479
+ "assistantAutoBackgrounded" in candidate ||
480
+ "tokenSaverOutput" in candidate);
481
+ }
482
+ function bashBackgroundMessage(record) {
483
+ const backgroundTaskId = typeof record.backgroundTaskId === "string" ? record.backgroundTaskId : "";
484
+ if (!backgroundTaskId) {
485
+ return "";
486
+ }
487
+ if (record.assistantAutoBackgrounded === true) {
488
+ return `Command was auto-backgrounded by assistant mode with ID: ${backgroundTaskId}.`;
489
+ }
490
+ if (record.backgroundedByUser === true) {
491
+ return `Command was backgrounded by user with ID: ${backgroundTaskId}.`;
492
+ }
493
+ return `Command is running in background with ID: ${backgroundTaskId}.`;
494
+ }
495
+ function buildBashDisplayOutput(record) {
496
+ const segments = [];
497
+ const stdout = typeof record.stdout === "string" ? record.stdout : "";
498
+ const stderr = typeof record.stderr === "string" ? record.stderr : "";
499
+ if (stdout) {
500
+ segments.push(stdout);
501
+ }
502
+ if (stderr) {
503
+ segments.push(stderr);
504
+ }
505
+ if (record.interrupted === true) {
506
+ segments.push("Command was aborted before completion.");
507
+ }
508
+ const backgroundMessage = bashBackgroundMessage(record);
509
+ if (backgroundMessage) {
510
+ segments.push(backgroundMessage);
511
+ }
512
+ return segments.join("\n");
513
+ }
514
+ export function buildToolResultFields(isError, rawContent, base, rawResult) {
268
515
  const toolName = resolveToolName(base);
516
+ const bashResultRecord = toolName === "Bash" ? findBashResultRecord(rawResult, rawContent) : undefined;
517
+ const normalizedRawOutput = normalizeToolResultText(rawContent, isError);
518
+ const rawOutput = bashResultRecord
519
+ ? buildBashDisplayOutput(bashResultRecord)
520
+ : normalizedRawOutput || JSON.stringify(rawContent);
269
521
  const fields = {
270
522
  status: isError ? "failed" : "completed",
271
- raw_output: rawOutput || JSON.stringify(rawContent),
272
523
  };
524
+ if (rawOutput) {
525
+ fields.raw_output = rawOutput;
526
+ }
527
+ const outputMetadata = extractToolOutputMetadata(toolName, rawResult, rawContent);
528
+ if (outputMetadata) {
529
+ fields.output_metadata = outputMetadata;
530
+ }
273
531
  if (!isError && toolName === "Write") {
274
532
  const structuredDiff = writeDiffFromResult(rawContent);
275
533
  if (structuredDiff.length > 0) {
@@ -283,15 +541,22 @@ export function buildToolResultFields(isError, rawContent, base) {
283
541
  }
284
542
  }
285
543
  if (!isError && toolName === "Edit") {
286
- const inputDiff = editDiffFromInput(base?.raw_input);
287
- if (inputDiff.length > 0) {
288
- fields.content = inputDiff;
544
+ const structuredDiff = editDiffFromResult(rawResult, base?.raw_input);
545
+ if (structuredDiff.length > 0) {
546
+ fields.content = structuredDiff;
289
547
  return fields;
290
548
  }
291
549
  if (base?.content.some((entry) => entry.type === "diff")) {
292
550
  return fields;
293
551
  }
294
552
  }
553
+ if (!isError && toolName === "ReadMcpResource") {
554
+ const structuredResourceContent = mcpResourceContentFromResult(rawResult, rawContent);
555
+ if (structuredResourceContent.length > 0) {
556
+ fields.content = structuredResourceContent;
557
+ return fields;
558
+ }
559
+ }
295
560
  if (rawOutput) {
296
561
  fields.content = [{ type: "content", content: { type: "text", text: rawOutput } }];
297
562
  }