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
|
@@ -63,10 +63,15 @@ export function parseAskUserQuestionPrompts(inputData) {
|
|
|
63
63
|
}
|
|
64
64
|
const label = typeof optionRecord.label === "string" ? optionRecord.label.trim() : "";
|
|
65
65
|
const description = typeof optionRecord.description === "string" ? optionRecord.description.trim() : "";
|
|
66
|
+
const preview = typeof optionRecord.preview === "string" ? optionRecord.preview.trim() : "";
|
|
66
67
|
if (!label) {
|
|
67
68
|
continue;
|
|
68
69
|
}
|
|
69
|
-
options.push({
|
|
70
|
+
options.push({
|
|
71
|
+
label,
|
|
72
|
+
description,
|
|
73
|
+
...(preview.length > 0 ? { preview } : {}),
|
|
74
|
+
});
|
|
70
75
|
}
|
|
71
76
|
if (options.length < 2) {
|
|
72
77
|
continue;
|
|
@@ -78,38 +83,74 @@ export function parseAskUserQuestionPrompts(inputData) {
|
|
|
78
83
|
function askUserQuestionOptions(prompt) {
|
|
79
84
|
return prompt.options.map((option, index) => ({
|
|
80
85
|
option_id: `question_${index}`,
|
|
81
|
-
|
|
86
|
+
label: option.label,
|
|
82
87
|
description: option.description,
|
|
83
|
-
|
|
88
|
+
...(option.preview ? { preview: option.preview } : {}),
|
|
84
89
|
}));
|
|
85
90
|
}
|
|
91
|
+
function askUserQuestionPromptRawInput(prompt, index, total) {
|
|
92
|
+
return {
|
|
93
|
+
prompt: {
|
|
94
|
+
question: prompt.question,
|
|
95
|
+
header: prompt.header,
|
|
96
|
+
multi_select: prompt.multiSelect,
|
|
97
|
+
options: prompt.options.map((option, optionIndex) => ({
|
|
98
|
+
option_id: `question_${optionIndex}`,
|
|
99
|
+
label: option.label,
|
|
100
|
+
description: option.description,
|
|
101
|
+
...(option.preview ? { preview: option.preview } : {}),
|
|
102
|
+
})),
|
|
103
|
+
},
|
|
104
|
+
question_index: index,
|
|
105
|
+
total_questions: total,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
86
108
|
function askUserQuestionPromptToolCall(base, prompt, index, total) {
|
|
87
109
|
return {
|
|
88
110
|
...base,
|
|
89
111
|
title: prompt.question,
|
|
90
|
-
raw_input:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
112
|
+
raw_input: askUserQuestionPromptRawInput(prompt, index, total),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function buildQuestionRequest(promptToolCall, prompt, index, total) {
|
|
116
|
+
return {
|
|
117
|
+
tool_call: promptToolCall,
|
|
118
|
+
prompt: {
|
|
119
|
+
question: prompt.question,
|
|
120
|
+
header: prompt.header,
|
|
121
|
+
multi_select: prompt.multiSelect,
|
|
122
|
+
options: askUserQuestionOptions(prompt),
|
|
101
123
|
},
|
|
124
|
+
question_index: index,
|
|
125
|
+
total_questions: total,
|
|
102
126
|
};
|
|
103
127
|
}
|
|
104
128
|
function askUserQuestionTranscript(answers) {
|
|
105
129
|
return answers.map((entry) => `${entry.header}: ${entry.answer}\n ${entry.question}`).join("\n");
|
|
106
130
|
}
|
|
107
|
-
|
|
131
|
+
function deriveAnnotation(selectedOptions, annotation) {
|
|
132
|
+
const preview = annotation?.preview?.trim().length
|
|
133
|
+
? annotation.preview
|
|
134
|
+
: selectedOptions
|
|
135
|
+
.map((option) => option.preview?.trim() ?? "")
|
|
136
|
+
.filter((previewText) => previewText.length > 0)
|
|
137
|
+
.join("\n\n");
|
|
138
|
+
const notes = annotation?.notes?.trim().length ? annotation.notes.trim() : undefined;
|
|
139
|
+
if (!preview && !notes) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
...(preview ? { preview } : {}),
|
|
144
|
+
...(notes ? { notes } : {}),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export async function requestAskUserQuestionAnswers(session, toolUseId, inputData, baseToolCall) {
|
|
108
148
|
const prompts = parseAskUserQuestionPrompts(inputData);
|
|
109
149
|
if (prompts.length === 0) {
|
|
110
150
|
return { behavior: "allow", updatedInput: inputData, toolUseID: toolUseId };
|
|
111
151
|
}
|
|
112
152
|
const answers = {};
|
|
153
|
+
const annotations = {};
|
|
113
154
|
const transcript = [];
|
|
114
155
|
for (const [index, prompt] of prompts.entries()) {
|
|
115
156
|
const promptToolCall = askUserQuestionPromptToolCall(baseToolCall, prompt, index, prompts.length);
|
|
@@ -128,29 +169,32 @@ export async function requestAskUserQuestionAnswers(session, toolUseId, toolName
|
|
|
128
169
|
tracked.status = "in_progress";
|
|
129
170
|
tracked.raw_input = promptToolCall.raw_input;
|
|
130
171
|
}
|
|
131
|
-
const request =
|
|
132
|
-
tool_call: promptToolCall,
|
|
133
|
-
options: askUserQuestionOptions(prompt),
|
|
134
|
-
};
|
|
172
|
+
const request = buildQuestionRequest(promptToolCall, prompt, index, prompts.length);
|
|
135
173
|
const outcome = await new Promise((resolve) => {
|
|
136
|
-
session.
|
|
174
|
+
session.pendingQuestions.set(toolUseId, {
|
|
137
175
|
onOutcome: resolve,
|
|
138
|
-
toolName,
|
|
176
|
+
toolName: ASK_USER_QUESTION_TOOL_NAME,
|
|
139
177
|
inputData,
|
|
140
178
|
});
|
|
141
|
-
writeEvent({ event: "
|
|
179
|
+
writeEvent({ event: "question_request", session_id: session.sessionId, request });
|
|
142
180
|
});
|
|
143
|
-
if (outcome.outcome !== "
|
|
181
|
+
if (outcome.outcome !== "answered") {
|
|
144
182
|
setToolCallStatus(session, toolUseId, "failed", "Question cancelled");
|
|
145
183
|
return { behavior: "deny", message: "Question cancelled", toolUseID: toolUseId };
|
|
146
184
|
}
|
|
147
|
-
const
|
|
148
|
-
if (
|
|
185
|
+
const selectedOptions = request.prompt.options.filter((option) => outcome.selected_option_ids.includes(option.option_id));
|
|
186
|
+
if (selectedOptions.length === 0 ||
|
|
187
|
+
(!prompt.multiSelect && selectedOptions.length !== 1)) {
|
|
149
188
|
setToolCallStatus(session, toolUseId, "failed", "Question answer was invalid");
|
|
150
189
|
return { behavior: "deny", message: "Question answer was invalid", toolUseID: toolUseId };
|
|
151
190
|
}
|
|
152
|
-
|
|
153
|
-
|
|
191
|
+
const answer = selectedOptions.map((option) => option.label).join(", ");
|
|
192
|
+
answers[prompt.question] = answer;
|
|
193
|
+
const annotation = deriveAnnotation(selectedOptions, outcome.annotation);
|
|
194
|
+
if (annotation) {
|
|
195
|
+
annotations[prompt.question] = annotation;
|
|
196
|
+
}
|
|
197
|
+
transcript.push({ header: prompt.header, question: prompt.question, answer });
|
|
154
198
|
const summary = askUserQuestionTranscript(transcript);
|
|
155
199
|
const progressFields = {
|
|
156
200
|
status: index + 1 >= prompts.length ? "completed" : "in_progress",
|
|
@@ -169,7 +213,11 @@ export async function requestAskUserQuestionAnswers(session, toolUseId, toolName
|
|
|
169
213
|
}
|
|
170
214
|
return {
|
|
171
215
|
behavior: "allow",
|
|
172
|
-
updatedInput: {
|
|
216
|
+
updatedInput: {
|
|
217
|
+
...inputData,
|
|
218
|
+
answers,
|
|
219
|
+
...(Object.keys(annotations).length > 0 ? { annotations } : {}),
|
|
220
|
+
},
|
|
173
221
|
toolUseID: toolUseId,
|
|
174
222
|
};
|
|
175
223
|
}
|
package/agent-sdk/dist/bridge.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
2
4
|
import readline from "node:readline";
|
|
3
5
|
import { pathToFileURL } from "node:url";
|
|
4
|
-
import { getSessionMessages, listSessions, } from "@anthropic-ai/claude-agent-sdk";
|
|
6
|
+
import { getSessionMessages, listSessions, renameSession, } from "@anthropic-ai/claude-agent-sdk";
|
|
5
7
|
import { parseCommandEnvelope, toPermissionMode } from "./bridge/commands.js";
|
|
6
|
-
import { writeEvent, failConnection, slashError, emitSessionUpdate, emitSessionsList, } from "./bridge/events.js";
|
|
8
|
+
import { writeEvent, failConnection, slashError, emitSessionUpdate, emitSessionsList, currentSessionListOptions, setSessionListingDir, } from "./bridge/events.js";
|
|
7
9
|
import { textFromPrompt } from "./bridge/message_handlers.js";
|
|
8
|
-
import { sessions, sessionById, createSession, closeAllSessions, handlePermissionResponse, } from "./bridge/session_lifecycle.js";
|
|
10
|
+
import { sessions, sessionById, createSession, closeAllSessions, handleElicitationResponse, handlePermissionResponse, handleQuestionResponse, } from "./bridge/session_lifecycle.js";
|
|
9
11
|
import { mapSessionMessagesToUpdates } from "./bridge/history.js";
|
|
12
|
+
import { MCP_STALE_STATUS_REVALIDATION_COOLDOWN_MS, handleMcpAuthenticateCommand, handleMcpClearAuthCommand, handleMcpOauthCallbackUrlCommand, handleMcpReconnectCommand, handleMcpSetServersCommand, handleMcpStatusCommand, handleMcpToggleCommand, staleMcpAuthCandidates, } from "./bridge/mcp.js";
|
|
10
13
|
// Re-exports: all symbols that tests and external consumers import from bridge.js.
|
|
11
14
|
export { AsyncQueue, logPermissionDebug } from "./bridge/shared.js";
|
|
12
15
|
export { asRecordOrNull } from "./bridge/shared.js";
|
|
@@ -14,16 +17,37 @@ export { CACHE_SPLIT_POLICY, previewKilobyteLabel } from "./bridge/cache_policy.
|
|
|
14
17
|
export { buildToolResultFields, createToolCall, normalizeToolKind, normalizeToolResultText, unwrapToolUseResult, } from "./bridge/tooling.js";
|
|
15
18
|
export { looksLikeAuthRequired } from "./bridge/auth.js";
|
|
16
19
|
export { parseCommandEnvelope } from "./bridge/commands.js";
|
|
20
|
+
export { buildSessionListOptions } from "./bridge/events.js";
|
|
17
21
|
export { permissionOptionsFromSuggestions, permissionResultFromOutcome, } from "./bridge/permissions.js";
|
|
18
22
|
export { mapSessionMessagesToUpdates, mapSdkSessions, } from "./bridge/history.js";
|
|
23
|
+
export { handleTaskSystemMessage } from "./bridge/message_handlers.js";
|
|
19
24
|
export { mapAvailableAgents } from "./bridge/agents.js";
|
|
20
|
-
export { buildQueryOptions } from "./bridge/session_lifecycle.js";
|
|
25
|
+
export { buildQueryOptions, mapAvailableModels } from "./bridge/session_lifecycle.js";
|
|
21
26
|
export { parseFastModeState, parseRateLimitStatus, buildRateLimitUpdate, } from "./bridge/state_parsing.js";
|
|
22
|
-
|
|
27
|
+
export { MCP_STALE_STATUS_REVALIDATION_COOLDOWN_MS, staleMcpAuthCandidates };
|
|
28
|
+
export function buildSessionMutationOptions(cwd) {
|
|
29
|
+
return cwd ? { dir: cwd } : undefined;
|
|
30
|
+
}
|
|
31
|
+
export function canGenerateSessionTitle(query) {
|
|
32
|
+
return typeof query.generateSessionTitle === "function";
|
|
33
|
+
}
|
|
34
|
+
export async function generatePersistedSessionTitle(query, description) {
|
|
35
|
+
if (!canGenerateSessionTitle(query)) {
|
|
36
|
+
throw new Error("SDK query does not support generateSessionTitle");
|
|
37
|
+
}
|
|
38
|
+
const title = await query.generateSessionTitle(description, { persist: true });
|
|
39
|
+
if (typeof title !== "string" || title.trim().length === 0) {
|
|
40
|
+
throw new Error("SDK did not return a generated session title");
|
|
41
|
+
}
|
|
42
|
+
return title;
|
|
43
|
+
}
|
|
44
|
+
const EXPECTED_AGENT_SDK_VERSION = "0.2.74";
|
|
23
45
|
const require = createRequire(import.meta.url);
|
|
24
46
|
export function resolveInstalledAgentSdkVersion() {
|
|
25
47
|
try {
|
|
26
|
-
const
|
|
48
|
+
const entryPath = require.resolve("@anthropic-ai/claude-agent-sdk");
|
|
49
|
+
const packageJsonPath = join(dirname(entryPath), "package.json");
|
|
50
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
27
51
|
return typeof pkg.version === "string" ? pkg.version : undefined;
|
|
28
52
|
}
|
|
29
53
|
catch {
|
|
@@ -54,6 +78,7 @@ async function handleCommand(command, requestId) {
|
|
|
54
78
|
failConnection(sdkVersionError, requestId);
|
|
55
79
|
return;
|
|
56
80
|
}
|
|
81
|
+
setSessionListingDir(command.cwd);
|
|
57
82
|
writeEvent({
|
|
58
83
|
event: "initialized",
|
|
59
84
|
result: {
|
|
@@ -77,6 +102,7 @@ async function handleCommand(command, requestId) {
|
|
|
77
102
|
await emitSessionsList(requestId);
|
|
78
103
|
return;
|
|
79
104
|
case "create_session":
|
|
105
|
+
setSessionListingDir(command.cwd);
|
|
80
106
|
await createSession({
|
|
81
107
|
cwd: command.cwd,
|
|
82
108
|
resume: command.resume,
|
|
@@ -87,12 +113,13 @@ async function handleCommand(command, requestId) {
|
|
|
87
113
|
return;
|
|
88
114
|
case "resume_session": {
|
|
89
115
|
try {
|
|
90
|
-
const sdkSessions = await listSessions();
|
|
116
|
+
const sdkSessions = await listSessions(currentSessionListOptions());
|
|
91
117
|
const matched = sdkSessions.find((entry) => entry.sessionId === command.session_id);
|
|
92
118
|
if (!matched) {
|
|
93
119
|
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
94
120
|
return;
|
|
95
121
|
}
|
|
122
|
+
setSessionListingDir(matched.cwd ?? process.cwd());
|
|
96
123
|
const historyMessages = await getSessionMessages(command.session_id, matched.cwd ? { dir: matched.cwd } : undefined);
|
|
97
124
|
const resumeUpdates = mapSessionMessagesToUpdates(historyMessages);
|
|
98
125
|
const staleSessions = Array.from(sessions.values());
|
|
@@ -115,6 +142,7 @@ async function handleCommand(command, requestId) {
|
|
|
115
142
|
}
|
|
116
143
|
case "new_session":
|
|
117
144
|
await closeAllSessions();
|
|
145
|
+
setSessionListingDir(command.cwd);
|
|
118
146
|
await createSession({
|
|
119
147
|
cwd: command.cwd,
|
|
120
148
|
launchSettings: command.launch_settings,
|
|
@@ -186,9 +214,132 @@ async function handleCommand(command, requestId) {
|
|
|
186
214
|
});
|
|
187
215
|
return;
|
|
188
216
|
}
|
|
217
|
+
case "generate_session_title": {
|
|
218
|
+
const session = sessionById(command.session_id);
|
|
219
|
+
if (!session) {
|
|
220
|
+
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
await generatePersistedSessionTitle(session.query, command.description);
|
|
225
|
+
setSessionListingDir(session.cwd);
|
|
226
|
+
await emitSessionsList(requestId);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
230
|
+
slashError(command.session_id, `failed to generate session title: ${message}`, requestId);
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
case "rename_session": {
|
|
235
|
+
const session = sessionById(command.session_id);
|
|
236
|
+
if (!session) {
|
|
237
|
+
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
await renameSession(command.session_id, command.title, buildSessionMutationOptions(session.cwd));
|
|
242
|
+
setSessionListingDir(session.cwd);
|
|
243
|
+
await emitSessionsList(requestId);
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
247
|
+
slashError(command.session_id, `failed to rename session: ${message}`, requestId);
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
case "get_status_snapshot": {
|
|
252
|
+
const session = sessionById(command.session_id);
|
|
253
|
+
if (!session) {
|
|
254
|
+
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const account = await session.query.accountInfo();
|
|
258
|
+
writeEvent({
|
|
259
|
+
event: "status_snapshot",
|
|
260
|
+
session_id: session.sessionId,
|
|
261
|
+
account: {
|
|
262
|
+
email: account.email,
|
|
263
|
+
organization: account.organization,
|
|
264
|
+
subscription_type: account.subscriptionType,
|
|
265
|
+
token_source: account.tokenSource,
|
|
266
|
+
api_key_source: account.apiKeySource,
|
|
267
|
+
},
|
|
268
|
+
}, requestId);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
case "mcp_status": {
|
|
272
|
+
const session = sessionById(command.session_id);
|
|
273
|
+
if (!session) {
|
|
274
|
+
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
await handleMcpStatusCommand(session, requestId);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
case "mcp_reconnect": {
|
|
281
|
+
const session = sessionById(command.session_id);
|
|
282
|
+
if (!session) {
|
|
283
|
+
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
await handleMcpReconnectCommand(session, command, requestId);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
case "mcp_toggle": {
|
|
290
|
+
const session = sessionById(command.session_id);
|
|
291
|
+
if (!session) {
|
|
292
|
+
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
await handleMcpToggleCommand(session, command, requestId);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
case "mcp_set_servers": {
|
|
299
|
+
const session = sessionById(command.session_id);
|
|
300
|
+
if (!session) {
|
|
301
|
+
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
await handleMcpSetServersCommand(session, command, requestId);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
case "mcp_authenticate": {
|
|
308
|
+
const session = sessionById(command.session_id);
|
|
309
|
+
if (!session) {
|
|
310
|
+
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
await handleMcpAuthenticateCommand(session, command, requestId);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
case "mcp_clear_auth": {
|
|
317
|
+
const session = sessionById(command.session_id);
|
|
318
|
+
if (!session) {
|
|
319
|
+
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
await handleMcpClearAuthCommand(session, command, requestId);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
case "mcp_oauth_callback_url": {
|
|
326
|
+
const session = sessionById(command.session_id);
|
|
327
|
+
if (!session) {
|
|
328
|
+
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
await handleMcpOauthCallbackUrlCommand(session, command, requestId);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
189
334
|
case "permission_response":
|
|
190
335
|
handlePermissionResponse(command);
|
|
191
336
|
return;
|
|
337
|
+
case "question_response":
|
|
338
|
+
handleQuestionResponse(command);
|
|
339
|
+
return;
|
|
340
|
+
case "elicitation_response":
|
|
341
|
+
handleElicitationResponse(command);
|
|
342
|
+
return;
|
|
192
343
|
case "shutdown":
|
|
193
344
|
await closeAllSessions();
|
|
194
345
|
process.exit(0);
|