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.
- 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 +116 -43
- 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 +805 -24
- 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,20 @@ 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
220
|
function initialSessionMode(launchSettings) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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.
|
|
246
|
+
...(params.launchSettings.settings ? { settings: params.launchSettings.settings } : {}),
|
|
247
|
+
toolConfig: { askUserQuestion: { previewFormat: "markdown" } },
|
|
248
248
|
...(systemPrompt ? { systemPrompt } : {}),
|
|
249
|
-
...(
|
|
250
|
-
|
|
251
|
-
|
|
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
|
|
287
|
-
const
|
|
288
|
-
? request.
|
|
289
|
-
: "
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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 [
|
|
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
|
}
|