claude-code-rust 0.7.1 → 0.8.1
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/LICENSE +202 -661
- package/README.md +3 -9
- package/agent-sdk/README.md +5 -0
- package/agent-sdk/dist/bridge/commands.js +183 -19
- package/agent-sdk/dist/bridge/events.js +14 -1
- package/agent-sdk/dist/bridge/history.js +4 -1
- package/agent-sdk/dist/bridge/mcp.js +289 -0
- package/agent-sdk/dist/bridge/message_handlers.js +14 -3
- package/agent-sdk/dist/bridge/session_lifecycle.js +139 -41
- package/agent-sdk/dist/bridge/tool_calls.js +12 -3
- package/agent-sdk/dist/bridge/tooling.js +272 -7
- package/agent-sdk/dist/bridge/user_interaction.js +76 -28
- package/agent-sdk/dist/bridge.js +158 -7
- package/agent-sdk/dist/bridge.test.js +846 -23
- package/package.json +3 -3
|
@@ -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,
|
|
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
|
|
187
|
-
if (rawMode
|
|
200
|
+
function permissionModeFromSettingsValue(rawMode) {
|
|
201
|
+
if (typeof rawMode !== "string") {
|
|
188
202
|
return undefined;
|
|
189
203
|
}
|
|
190
204
|
switch (rawMode) {
|
|
@@ -195,32 +209,41 @@ function permissionModeFromLaunchSettings(rawMode) {
|
|
|
195
209
|
case "dontAsk":
|
|
196
210
|
return rawMode;
|
|
197
211
|
default:
|
|
198
|
-
throw new Error(`unsupported launch_settings.
|
|
212
|
+
throw new Error(`unsupported launch_settings.settings.permissions.defaultMode: ${rawMode}`);
|
|
199
213
|
}
|
|
200
214
|
}
|
|
201
215
|
function initialSessionModel(launchSettings) {
|
|
202
|
-
|
|
216
|
+
const settings = settingsObjectFromLaunchSettings(launchSettings);
|
|
217
|
+
const model = typeof settings?.model === "string" ? settings.model.trim() : "";
|
|
218
|
+
return model || DEFAULT_MODEL_NAME;
|
|
203
219
|
}
|
|
204
|
-
function
|
|
205
|
-
|
|
220
|
+
function startupModelOption(launchSettings) {
|
|
221
|
+
const settings = settingsObjectFromLaunchSettings(launchSettings);
|
|
222
|
+
const model = typeof settings?.model === "string" ? settings.model.trim() : "";
|
|
223
|
+
return model ? { model } : {};
|
|
206
224
|
}
|
|
207
|
-
function
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
case "disabled":
|
|
214
|
-
return { type: "disabled" };
|
|
215
|
-
default:
|
|
216
|
-
throw new Error(`unsupported launch_settings.thinking_mode: ${String(launchSettings.thinking_mode)}`);
|
|
217
|
-
}
|
|
225
|
+
function initialSessionMode(launchSettings) {
|
|
226
|
+
const settings = settingsObjectFromLaunchSettings(launchSettings);
|
|
227
|
+
const permissions = settings?.permissions && typeof settings.permissions === "object" && !Array.isArray(settings.permissions)
|
|
228
|
+
? settings.permissions
|
|
229
|
+
: undefined;
|
|
230
|
+
return permissionModeFromSettingsValue(permissions?.defaultMode) ?? DEFAULT_PERMISSION_MODE;
|
|
218
231
|
}
|
|
219
|
-
function
|
|
220
|
-
|
|
221
|
-
|
|
232
|
+
function startupPermissionModeOptions(launchSettings) {
|
|
233
|
+
const settings = settingsObjectFromLaunchSettings(launchSettings);
|
|
234
|
+
const permissions = settings?.permissions && typeof settings.permissions === "object" && !Array.isArray(settings.permissions)
|
|
235
|
+
? settings.permissions
|
|
236
|
+
: undefined;
|
|
237
|
+
const permissionMode = permissionModeFromSettingsValue(permissions?.defaultMode);
|
|
238
|
+
if (!permissionMode) {
|
|
239
|
+
return {};
|
|
222
240
|
}
|
|
223
|
-
return
|
|
241
|
+
return permissionMode === "bypassPermissions"
|
|
242
|
+
? {
|
|
243
|
+
permissionMode,
|
|
244
|
+
allowDangerouslySkipPermissions: true,
|
|
245
|
+
}
|
|
246
|
+
: { permissionMode };
|
|
224
247
|
}
|
|
225
248
|
function systemPromptFromLaunchSettings(launchSettings) {
|
|
226
249
|
const language = launchSettings.language?.trim();
|
|
@@ -235,20 +258,22 @@ function systemPromptFromLaunchSettings(launchSettings) {
|
|
|
235
258
|
};
|
|
236
259
|
}
|
|
237
260
|
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
261
|
const systemPrompt = systemPromptFromLaunchSettings(params.launchSettings);
|
|
262
|
+
const modelOption = startupModelOption(params.launchSettings);
|
|
263
|
+
const permissionModeOptions = startupPermissionModeOptions(params.launchSettings);
|
|
242
264
|
return {
|
|
243
265
|
cwd: params.cwd,
|
|
244
266
|
includePartialMessages: true,
|
|
245
267
|
executable: "node",
|
|
246
268
|
...(params.resume ? {} : { sessionId: params.provisionalSessionId }),
|
|
247
|
-
...(params.launchSettings.
|
|
269
|
+
...(params.launchSettings.settings ? { settings: params.launchSettings.settings } : {}),
|
|
270
|
+
...modelOption,
|
|
271
|
+
...permissionModeOptions,
|
|
272
|
+
toolConfig: { askUserQuestion: { previewFormat: "markdown" } },
|
|
248
273
|
...(systemPrompt ? { systemPrompt } : {}),
|
|
249
|
-
...(
|
|
250
|
-
|
|
251
|
-
|
|
274
|
+
...(params.launchSettings.agent_progress_summaries !== undefined
|
|
275
|
+
? { agentProgressSummaries: params.launchSettings.agent_progress_summaries }
|
|
276
|
+
: {}),
|
|
252
277
|
...(params.claudeCodeExecutable
|
|
253
278
|
? { pathToClaudeCodeExecutable: params.claudeCodeExecutable }
|
|
254
279
|
: {}),
|
|
@@ -283,21 +308,52 @@ export function buildQueryOptions(params) {
|
|
|
283
308
|
resume: params.resume,
|
|
284
309
|
canUseTool: params.canUseTool,
|
|
285
310
|
onElicitation: async (request) => {
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
? request.
|
|
289
|
-
: "
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
311
|
+
const requestId = randomUUID();
|
|
312
|
+
const mode = request.mode === "form" || request.mode === "url"
|
|
313
|
+
? request.mode
|
|
314
|
+
: typeof request.url === "string" && request.url.trim().length > 0
|
|
315
|
+
? "url"
|
|
316
|
+
: "form";
|
|
317
|
+
const normalized = {
|
|
318
|
+
request_id: requestId,
|
|
319
|
+
server_name: typeof request.serverName === "string" && request.serverName.trim().length > 0
|
|
320
|
+
? request.serverName
|
|
321
|
+
: "unknown",
|
|
322
|
+
message: typeof request.message === "string" && request.message.trim().length > 0
|
|
323
|
+
? request.message
|
|
324
|
+
: "<no message>",
|
|
325
|
+
mode,
|
|
326
|
+
...(typeof request.url === "string" && request.url.trim().length > 0
|
|
327
|
+
? { url: request.url }
|
|
328
|
+
: {}),
|
|
329
|
+
...(typeof request.elicitationId === "string" && request.elicitationId.trim().length > 0
|
|
330
|
+
? { elicitation_id: request.elicitationId }
|
|
331
|
+
: {}),
|
|
332
|
+
...(request.requestedSchema
|
|
333
|
+
? { requested_schema: request.requestedSchema }
|
|
334
|
+
: {}),
|
|
335
|
+
};
|
|
336
|
+
writeEvent({
|
|
337
|
+
event: "elicitation_request",
|
|
338
|
+
session_id: params.sessionIdForLogs(),
|
|
339
|
+
request: normalized,
|
|
340
|
+
});
|
|
341
|
+
return await new Promise((resolve) => {
|
|
342
|
+
const currentSession = sessions.get(params.sessionIdForLogs());
|
|
343
|
+
if (!currentSession) {
|
|
344
|
+
resolve({ action: "cancel" });
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
currentSession.pendingElicitations.set(requestId, {
|
|
348
|
+
resolve,
|
|
349
|
+
serverName: normalized.server_name,
|
|
350
|
+
elicitationId: normalized.elicitation_id,
|
|
351
|
+
});
|
|
352
|
+
});
|
|
297
353
|
},
|
|
298
354
|
};
|
|
299
355
|
}
|
|
300
|
-
function mapAvailableModels(models) {
|
|
356
|
+
export function mapAvailableModels(models) {
|
|
301
357
|
if (!Array.isArray(models)) {
|
|
302
358
|
return [];
|
|
303
359
|
}
|
|
@@ -315,6 +371,15 @@ function mapAvailableModels(models) {
|
|
|
315
371
|
supported_effort_levels: Array.isArray(entry.supportedEffortLevels)
|
|
316
372
|
? entry.supportedEffortLevels.filter((level) => level === "low" || level === "medium" || level === "high")
|
|
317
373
|
: [],
|
|
374
|
+
...(typeof entry.supportsAdaptiveThinking === "boolean"
|
|
375
|
+
? { supports_adaptive_thinking: entry.supportsAdaptiveThinking }
|
|
376
|
+
: {}),
|
|
377
|
+
...(typeof entry.supportsFastMode === "boolean"
|
|
378
|
+
? { supports_fast_mode: entry.supportsFastMode }
|
|
379
|
+
: {}),
|
|
380
|
+
...(typeof entry.supportsAutoMode === "boolean"
|
|
381
|
+
? { supports_auto_mode: entry.supportsAutoMode }
|
|
382
|
+
: {}),
|
|
318
383
|
...(typeof entry.description === "string" && entry.description.trim().length > 0
|
|
319
384
|
? { description: entry.description }
|
|
320
385
|
: {}),
|
|
@@ -366,3 +431,36 @@ export function handlePermissionResponse(command) {
|
|
|
366
431
|
}
|
|
367
432
|
resolver.resolve(permissionResult);
|
|
368
433
|
}
|
|
434
|
+
export function handleQuestionResponse(command) {
|
|
435
|
+
const session = sessionById(command.session_id);
|
|
436
|
+
if (!session) {
|
|
437
|
+
logPermissionDebug(`question response dropped: unknown session session_id=${command.session_id} tool_call_id=${command.tool_call_id}`);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const resolver = session.pendingQuestions.get(command.tool_call_id);
|
|
441
|
+
if (!resolver) {
|
|
442
|
+
logPermissionDebug(`question response dropped: no pending resolver session_id=${command.session_id} tool_call_id=${command.tool_call_id}`);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
session.pendingQuestions.delete(command.tool_call_id);
|
|
446
|
+
resolver.onOutcome(command.outcome);
|
|
447
|
+
}
|
|
448
|
+
export function handleElicitationResponse(command) {
|
|
449
|
+
const session = sessionById(command.session_id);
|
|
450
|
+
if (!session) {
|
|
451
|
+
console.error(`[sdk warn] elicitation response dropped: unknown session ` +
|
|
452
|
+
`session_id=${command.session_id} request_id=${command.elicitation_request_id}`);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const pending = session.pendingElicitations.get(command.elicitation_request_id);
|
|
456
|
+
if (!pending) {
|
|
457
|
+
console.error(`[sdk warn] elicitation response dropped: no pending request ` +
|
|
458
|
+
`session_id=${command.session_id} request_id=${command.elicitation_request_id}`);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
session.pendingElicitations.delete(command.elicitation_request_id);
|
|
462
|
+
pending.resolve({
|
|
463
|
+
action: command.action,
|
|
464
|
+
...(command.content ? { content: command.content } : {}),
|
|
465
|
+
});
|
|
466
|
+
}
|
|
@@ -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 [
|
|
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
|
-
|
|
267
|
-
const
|
|
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
|
|
287
|
-
if (
|
|
288
|
-
fields.content =
|
|
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
|
}
|