claude-code-rust 0.5.1 → 0.7.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 +8 -8
- package/agent-sdk/dist/bridge/agents.js +75 -0
- package/agent-sdk/dist/bridge/commands.js +45 -14
- package/agent-sdk/dist/bridge/error_classification.js +55 -0
- package/agent-sdk/dist/bridge/events.js +83 -0
- package/agent-sdk/dist/bridge/history.js +49 -258
- package/agent-sdk/dist/bridge/message_handlers.js +428 -0
- package/agent-sdk/dist/bridge/permissions.js +15 -3
- package/agent-sdk/dist/bridge/session_lifecycle.js +368 -0
- package/agent-sdk/dist/bridge/shared.js +49 -0
- package/agent-sdk/dist/bridge/state_parsing.js +66 -0
- package/agent-sdk/dist/bridge/tool_calls.js +168 -0
- package/agent-sdk/dist/bridge/tooling.js +27 -5
- package/agent-sdk/dist/bridge/user_interaction.js +175 -0
- package/agent-sdk/dist/bridge.js +37 -1106
- package/agent-sdk/dist/bridge.test.js +304 -150
- package/bin/claude-rs.js +1 -1
- package/package.json +6 -7
- package/scripts/postinstall.js +2 -2
- package/agent-sdk/README.md +0 -13
- package/agent-sdk/dist/bridge/usage.js +0 -95
|
@@ -34,6 +34,7 @@ export function normalizeToolKind(name) {
|
|
|
34
34
|
case "TodoWrite":
|
|
35
35
|
return "other";
|
|
36
36
|
case "Task":
|
|
37
|
+
case "Agent":
|
|
37
38
|
return "think";
|
|
38
39
|
case "ExitPlanMode":
|
|
39
40
|
return "switch_mode";
|
|
@@ -169,16 +170,37 @@ function persistedOutputFirstLine(text) {
|
|
|
169
170
|
}
|
|
170
171
|
return null;
|
|
171
172
|
}
|
|
172
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Replace verbose SDK-internal tool rejection messages with short user-facing text.
|
|
175
|
+
* The SDK sends these as tool result content meant for Claude, not for the user.
|
|
176
|
+
*/
|
|
177
|
+
const USER_REJECTED_TOOL_USE_EXACT = "The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.";
|
|
178
|
+
const USER_REJECTED_TOOL_USE_PREFIX = "The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). To tell you how to proceed, the user said:";
|
|
179
|
+
const PERMISSION_DENIED_TOOL_USE_EXACT = "Permission for this tool use was denied. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). Try a different approach or report the limitation to complete your task.";
|
|
180
|
+
const PERMISSION_DENIED_TOOL_USE_PREFIX = "Permission for this tool use was denied. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). The user said:";
|
|
181
|
+
function sanitizeSdkRejectionText(text) {
|
|
182
|
+
const normalized = text.trim();
|
|
183
|
+
if (normalized === USER_REJECTED_TOOL_USE_EXACT ||
|
|
184
|
+
normalized.startsWith(USER_REJECTED_TOOL_USE_PREFIX)) {
|
|
185
|
+
return "Cancelled by user.";
|
|
186
|
+
}
|
|
187
|
+
if (normalized === PERMISSION_DENIED_TOOL_USE_EXACT ||
|
|
188
|
+
normalized.startsWith(PERMISSION_DENIED_TOOL_USE_PREFIX)) {
|
|
189
|
+
return "Permission denied.";
|
|
190
|
+
}
|
|
191
|
+
return text;
|
|
192
|
+
}
|
|
193
|
+
export function normalizeToolResultText(value, isError = false) {
|
|
173
194
|
const text = extractText(value);
|
|
174
195
|
if (!text) {
|
|
175
196
|
return "";
|
|
176
197
|
}
|
|
177
198
|
const persistedLine = persistedOutputFirstLine(text);
|
|
178
|
-
|
|
179
|
-
|
|
199
|
+
const normalized = persistedLine || text;
|
|
200
|
+
if (!isError) {
|
|
201
|
+
return normalized;
|
|
180
202
|
}
|
|
181
|
-
return
|
|
203
|
+
return sanitizeSdkRejectionText(normalized);
|
|
182
204
|
}
|
|
183
205
|
function resolveToolName(toolCall) {
|
|
184
206
|
const meta = asRecordOrNull(toolCall?.meta);
|
|
@@ -242,7 +264,7 @@ function writeDiffFromResult(rawContent) {
|
|
|
242
264
|
return [];
|
|
243
265
|
}
|
|
244
266
|
export function buildToolResultFields(isError, rawContent, base) {
|
|
245
|
-
const rawOutput = normalizeToolResultText(rawContent);
|
|
267
|
+
const rawOutput = normalizeToolResultText(rawContent, isError);
|
|
246
268
|
const toolName = resolveToolName(base);
|
|
247
269
|
const fields = {
|
|
248
270
|
status: isError ? "failed" : "completed",
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { asRecordOrNull } from "./shared.js";
|
|
2
|
+
import { writeEvent, emitSessionUpdate } from "./events.js";
|
|
3
|
+
import { setToolCallStatus } from "./tool_calls.js";
|
|
4
|
+
export const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
|
|
5
|
+
export const QUESTION_CHOICE_KIND = "question_choice";
|
|
6
|
+
export const EXIT_PLAN_MODE_TOOL_NAME = "ExitPlanMode";
|
|
7
|
+
export const PLAN_APPROVE_KIND = "plan_approve";
|
|
8
|
+
export const PLAN_REJECT_KIND = "plan_reject";
|
|
9
|
+
export async function requestExitPlanModeApproval(session, toolUseId, inputData, baseToolCall) {
|
|
10
|
+
const options = [
|
|
11
|
+
{
|
|
12
|
+
option_id: "approve",
|
|
13
|
+
name: "Approve",
|
|
14
|
+
description: "Approve the plan and continue",
|
|
15
|
+
kind: PLAN_APPROVE_KIND,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
option_id: "reject",
|
|
19
|
+
name: "Reject",
|
|
20
|
+
description: "Reject the plan",
|
|
21
|
+
kind: PLAN_REJECT_KIND,
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
const request = {
|
|
25
|
+
tool_call: baseToolCall,
|
|
26
|
+
options,
|
|
27
|
+
};
|
|
28
|
+
const outcome = await new Promise((resolve) => {
|
|
29
|
+
session.pendingPermissions.set(toolUseId, {
|
|
30
|
+
onOutcome: resolve,
|
|
31
|
+
toolName: EXIT_PLAN_MODE_TOOL_NAME,
|
|
32
|
+
inputData,
|
|
33
|
+
});
|
|
34
|
+
writeEvent({ event: "permission_request", session_id: session.sessionId, request });
|
|
35
|
+
});
|
|
36
|
+
if (outcome.outcome !== "selected" || outcome.option_id === "reject") {
|
|
37
|
+
setToolCallStatus(session, toolUseId, "failed", "Plan rejected");
|
|
38
|
+
return { behavior: "deny", message: "Plan rejected", toolUseID: toolUseId };
|
|
39
|
+
}
|
|
40
|
+
return { behavior: "allow", updatedInput: inputData, toolUseID: toolUseId };
|
|
41
|
+
}
|
|
42
|
+
export function parseAskUserQuestionPrompts(inputData) {
|
|
43
|
+
const rawQuestions = Array.isArray(inputData.questions) ? inputData.questions : [];
|
|
44
|
+
const prompts = [];
|
|
45
|
+
for (const rawQuestion of rawQuestions) {
|
|
46
|
+
const questionRecord = asRecordOrNull(rawQuestion);
|
|
47
|
+
if (!questionRecord) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const question = typeof questionRecord.question === "string" ? questionRecord.question.trim() : "";
|
|
51
|
+
if (!question) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const headerRaw = typeof questionRecord.header === "string" ? questionRecord.header.trim() : "";
|
|
55
|
+
const header = headerRaw || `Q${prompts.length + 1}`;
|
|
56
|
+
const multiSelect = Boolean(questionRecord.multiSelect);
|
|
57
|
+
const rawOptions = Array.isArray(questionRecord.options) ? questionRecord.options : [];
|
|
58
|
+
const options = [];
|
|
59
|
+
for (const rawOption of rawOptions) {
|
|
60
|
+
const optionRecord = asRecordOrNull(rawOption);
|
|
61
|
+
if (!optionRecord) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const label = typeof optionRecord.label === "string" ? optionRecord.label.trim() : "";
|
|
65
|
+
const description = typeof optionRecord.description === "string" ? optionRecord.description.trim() : "";
|
|
66
|
+
if (!label) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
options.push({ label, description });
|
|
70
|
+
}
|
|
71
|
+
if (options.length < 2) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
prompts.push({ question, header, multiSelect, options });
|
|
75
|
+
}
|
|
76
|
+
return prompts;
|
|
77
|
+
}
|
|
78
|
+
function askUserQuestionOptions(prompt) {
|
|
79
|
+
return prompt.options.map((option, index) => ({
|
|
80
|
+
option_id: `question_${index}`,
|
|
81
|
+
name: option.label,
|
|
82
|
+
description: option.description,
|
|
83
|
+
kind: QUESTION_CHOICE_KIND,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
function askUserQuestionPromptToolCall(base, prompt, index, total) {
|
|
87
|
+
return {
|
|
88
|
+
...base,
|
|
89
|
+
title: prompt.question,
|
|
90
|
+
raw_input: {
|
|
91
|
+
questions: [
|
|
92
|
+
{
|
|
93
|
+
question: prompt.question,
|
|
94
|
+
header: prompt.header,
|
|
95
|
+
multiSelect: prompt.multiSelect,
|
|
96
|
+
options: prompt.options,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
question_index: index,
|
|
100
|
+
total_questions: total,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function askUserQuestionTranscript(answers) {
|
|
105
|
+
return answers.map((entry) => `${entry.header}: ${entry.answer}\n ${entry.question}`).join("\n");
|
|
106
|
+
}
|
|
107
|
+
export async function requestAskUserQuestionAnswers(session, toolUseId, toolName, inputData, baseToolCall) {
|
|
108
|
+
const prompts = parseAskUserQuestionPrompts(inputData);
|
|
109
|
+
if (prompts.length === 0) {
|
|
110
|
+
return { behavior: "allow", updatedInput: inputData, toolUseID: toolUseId };
|
|
111
|
+
}
|
|
112
|
+
const answers = {};
|
|
113
|
+
const transcript = [];
|
|
114
|
+
for (const [index, prompt] of prompts.entries()) {
|
|
115
|
+
const promptToolCall = askUserQuestionPromptToolCall(baseToolCall, prompt, index, prompts.length);
|
|
116
|
+
const fields = {
|
|
117
|
+
title: promptToolCall.title,
|
|
118
|
+
status: "in_progress",
|
|
119
|
+
raw_input: promptToolCall.raw_input,
|
|
120
|
+
};
|
|
121
|
+
emitSessionUpdate(session.sessionId, {
|
|
122
|
+
type: "tool_call_update",
|
|
123
|
+
tool_call_update: { tool_call_id: toolUseId, fields },
|
|
124
|
+
});
|
|
125
|
+
const tracked = session.toolCalls.get(toolUseId);
|
|
126
|
+
if (tracked) {
|
|
127
|
+
tracked.title = promptToolCall.title;
|
|
128
|
+
tracked.status = "in_progress";
|
|
129
|
+
tracked.raw_input = promptToolCall.raw_input;
|
|
130
|
+
}
|
|
131
|
+
const request = {
|
|
132
|
+
tool_call: promptToolCall,
|
|
133
|
+
options: askUserQuestionOptions(prompt),
|
|
134
|
+
};
|
|
135
|
+
const outcome = await new Promise((resolve) => {
|
|
136
|
+
session.pendingPermissions.set(toolUseId, {
|
|
137
|
+
onOutcome: resolve,
|
|
138
|
+
toolName,
|
|
139
|
+
inputData,
|
|
140
|
+
});
|
|
141
|
+
writeEvent({ event: "permission_request", session_id: session.sessionId, request });
|
|
142
|
+
});
|
|
143
|
+
if (outcome.outcome !== "selected") {
|
|
144
|
+
setToolCallStatus(session, toolUseId, "failed", "Question cancelled");
|
|
145
|
+
return { behavior: "deny", message: "Question cancelled", toolUseID: toolUseId };
|
|
146
|
+
}
|
|
147
|
+
const selected = request.options.find((option) => option.option_id === outcome.option_id);
|
|
148
|
+
if (!selected) {
|
|
149
|
+
setToolCallStatus(session, toolUseId, "failed", "Question answer was invalid");
|
|
150
|
+
return { behavior: "deny", message: "Question answer was invalid", toolUseID: toolUseId };
|
|
151
|
+
}
|
|
152
|
+
answers[prompt.question] = selected.name;
|
|
153
|
+
transcript.push({ header: prompt.header, question: prompt.question, answer: selected.name });
|
|
154
|
+
const summary = askUserQuestionTranscript(transcript);
|
|
155
|
+
const progressFields = {
|
|
156
|
+
status: index + 1 >= prompts.length ? "completed" : "in_progress",
|
|
157
|
+
raw_output: summary,
|
|
158
|
+
content: [{ type: "content", content: { type: "text", text: summary } }],
|
|
159
|
+
};
|
|
160
|
+
emitSessionUpdate(session.sessionId, {
|
|
161
|
+
type: "tool_call_update",
|
|
162
|
+
tool_call_update: { tool_call_id: toolUseId, fields: progressFields },
|
|
163
|
+
});
|
|
164
|
+
if (tracked) {
|
|
165
|
+
tracked.status = progressFields.status ?? tracked.status;
|
|
166
|
+
tracked.raw_output = summary;
|
|
167
|
+
tracked.content = progressFields.content ?? tracked.content;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
behavior: "allow",
|
|
172
|
+
updatedInput: { ...inputData, answers },
|
|
173
|
+
toolUseID: toolUseId,
|
|
174
|
+
};
|
|
175
|
+
}
|