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.
@@ -0,0 +1,428 @@
1
+ import { asRecordOrNull } from "./shared.js";
2
+ import { toPermissionMode, buildModeState } from "./commands.js";
3
+ import { writeEvent, emitSessionUpdate, emitConnectEvent, refreshSessionsList } from "./events.js";
4
+ import { TOOL_RESULT_TYPES, unwrapToolUseResult } from "./tooling.js";
5
+ import { emitToolCall, emitPlanIfTodoWrite, emitToolResultUpdate, finalizeOpenToolCalls, emitToolProgressUpdate, emitToolSummaryUpdate, ensureToolCallVisible, resolveTaskToolUseId, taskProgressText, } from "./tool_calls.js";
6
+ import { emitAuthRequired, classifyTurnErrorKind, emitFastModeUpdateIfChanged } from "./error_classification.js";
7
+ import { mapAvailableAgentsFromNames, emitAvailableAgentsIfChanged, refreshAvailableAgents } from "./agents.js";
8
+ import { buildRateLimitUpdate, numberField } from "./state_parsing.js";
9
+ import { looksLikeAuthRequired } from "./auth.js";
10
+ import { updateSessionId } from "./session_lifecycle.js";
11
+ export function textFromPrompt(command) {
12
+ const chunks = command.chunks ?? [];
13
+ return chunks
14
+ .map((chunk) => {
15
+ if (chunk.kind !== "text") {
16
+ return "";
17
+ }
18
+ return typeof chunk.value === "string" ? chunk.value : "";
19
+ })
20
+ .filter((part) => part.length > 0)
21
+ .join("");
22
+ }
23
+ export function handleTaskSystemMessage(session, subtype, msg) {
24
+ if (subtype !== "task_started" && subtype !== "task_progress" && subtype !== "task_notification") {
25
+ return;
26
+ }
27
+ const taskId = typeof msg.task_id === "string" ? msg.task_id : "";
28
+ const explicitToolUseId = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
29
+ if (taskId && explicitToolUseId) {
30
+ session.taskToolUseIds.set(taskId, explicitToolUseId);
31
+ }
32
+ const toolUseId = resolveTaskToolUseId(session, msg);
33
+ if (!toolUseId) {
34
+ return;
35
+ }
36
+ const toolCall = ensureToolCallVisible(session, toolUseId, "Agent", {});
37
+ if (toolCall.status === "pending") {
38
+ toolCall.status = "in_progress";
39
+ emitSessionUpdate(session.sessionId, {
40
+ type: "tool_call_update",
41
+ tool_call_update: { tool_call_id: toolUseId, fields: { status: "in_progress" } },
42
+ });
43
+ }
44
+ if (subtype === "task_started") {
45
+ const description = typeof msg.description === "string" ? msg.description : "";
46
+ if (!description) {
47
+ return;
48
+ }
49
+ emitSessionUpdate(session.sessionId, {
50
+ type: "tool_call_update",
51
+ tool_call_update: {
52
+ tool_call_id: toolUseId,
53
+ fields: {
54
+ status: "in_progress",
55
+ raw_output: description,
56
+ content: [{ type: "content", content: { type: "text", text: description } }],
57
+ },
58
+ },
59
+ });
60
+ return;
61
+ }
62
+ if (subtype === "task_progress") {
63
+ const progress = taskProgressText(msg);
64
+ if (!progress) {
65
+ return;
66
+ }
67
+ emitSessionUpdate(session.sessionId, {
68
+ type: "tool_call_update",
69
+ tool_call_update: {
70
+ tool_call_id: toolUseId,
71
+ fields: {
72
+ status: "in_progress",
73
+ raw_output: progress,
74
+ content: [{ type: "content", content: { type: "text", text: progress } }],
75
+ },
76
+ },
77
+ });
78
+ return;
79
+ }
80
+ const status = typeof msg.status === "string" ? msg.status : "";
81
+ const summary = typeof msg.summary === "string" ? msg.summary : "";
82
+ const finalStatus = status === "completed" ? "completed" : "failed";
83
+ const fields = { status: finalStatus };
84
+ if (summary) {
85
+ fields.raw_output = summary;
86
+ fields.content = [{ type: "content", content: { type: "text", text: summary } }];
87
+ }
88
+ emitSessionUpdate(session.sessionId, {
89
+ type: "tool_call_update",
90
+ tool_call_update: { tool_call_id: toolUseId, fields },
91
+ });
92
+ toolCall.status = finalStatus;
93
+ if (taskId) {
94
+ session.taskToolUseIds.delete(taskId);
95
+ }
96
+ }
97
+ export function handleContentBlock(session, block) {
98
+ const blockType = typeof block.type === "string" ? block.type : "";
99
+ if (blockType === "text") {
100
+ const text = typeof block.text === "string" ? block.text : "";
101
+ if (text) {
102
+ emitSessionUpdate(session.sessionId, { type: "agent_message_chunk", content: { type: "text", text } });
103
+ }
104
+ return;
105
+ }
106
+ if (blockType === "thinking") {
107
+ const text = typeof block.thinking === "string" ? block.thinking : "";
108
+ if (text) {
109
+ emitSessionUpdate(session.sessionId, { type: "agent_thought_chunk", content: { type: "text", text } });
110
+ }
111
+ return;
112
+ }
113
+ if (blockType === "tool_use" || blockType === "server_tool_use" || blockType === "mcp_tool_use") {
114
+ const toolUseId = typeof block.id === "string" ? block.id : "";
115
+ const name = typeof block.name === "string" ? block.name : "Tool";
116
+ const input = block.input && typeof block.input === "object" ? block.input : {};
117
+ if (!toolUseId) {
118
+ return;
119
+ }
120
+ emitPlanIfTodoWrite(session, name, input);
121
+ emitToolCall(session, toolUseId, name, input);
122
+ return;
123
+ }
124
+ if (TOOL_RESULT_TYPES.has(blockType)) {
125
+ const toolUseId = typeof block.tool_use_id === "string" ? block.tool_use_id : "";
126
+ if (!toolUseId) {
127
+ return;
128
+ }
129
+ const isError = Boolean(block.is_error);
130
+ emitToolResultUpdate(session, toolUseId, isError, block.content);
131
+ }
132
+ }
133
+ export function handleStreamEvent(session, event) {
134
+ const eventType = typeof event.type === "string" ? event.type : "";
135
+ if (eventType === "content_block_start") {
136
+ if (event.content_block && typeof event.content_block === "object") {
137
+ handleContentBlock(session, event.content_block);
138
+ }
139
+ return;
140
+ }
141
+ if (eventType === "content_block_delta") {
142
+ if (!event.delta || typeof event.delta !== "object") {
143
+ return;
144
+ }
145
+ const delta = event.delta;
146
+ const deltaType = typeof delta.type === "string" ? delta.type : "";
147
+ if (deltaType === "text_delta") {
148
+ const text = typeof delta.text === "string" ? delta.text : "";
149
+ if (text) {
150
+ emitSessionUpdate(session.sessionId, { type: "agent_message_chunk", content: { type: "text", text } });
151
+ }
152
+ }
153
+ else if (deltaType === "thinking_delta") {
154
+ const text = typeof delta.thinking === "string" ? delta.thinking : "";
155
+ if (text) {
156
+ emitSessionUpdate(session.sessionId, { type: "agent_thought_chunk", content: { type: "text", text } });
157
+ }
158
+ }
159
+ }
160
+ }
161
+ export function handleAssistantMessage(session, message) {
162
+ const assistantError = typeof message.error === "string" ? message.error : "";
163
+ if (assistantError.length > 0) {
164
+ session.lastAssistantError = assistantError;
165
+ }
166
+ const messageObject = message.message && typeof message.message === "object"
167
+ ? message.message
168
+ : null;
169
+ if (!messageObject) {
170
+ return;
171
+ }
172
+ const content = Array.isArray(messageObject.content) ? messageObject.content : [];
173
+ for (const block of content) {
174
+ if (!block || typeof block !== "object") {
175
+ continue;
176
+ }
177
+ const blockRecord = block;
178
+ const blockType = typeof blockRecord.type === "string" ? blockRecord.type : "";
179
+ if (blockType === "tool_use" ||
180
+ blockType === "server_tool_use" ||
181
+ blockType === "mcp_tool_use" ||
182
+ TOOL_RESULT_TYPES.has(blockType)) {
183
+ handleContentBlock(session, blockRecord);
184
+ }
185
+ }
186
+ }
187
+ export function handleUserToolResultBlocks(session, message) {
188
+ const messageObject = message.message && typeof message.message === "object"
189
+ ? message.message
190
+ : null;
191
+ if (!messageObject) {
192
+ return;
193
+ }
194
+ const content = Array.isArray(messageObject.content) ? messageObject.content : [];
195
+ for (const block of content) {
196
+ if (!block || typeof block !== "object") {
197
+ continue;
198
+ }
199
+ const blockRecord = block;
200
+ const blockType = typeof blockRecord.type === "string" ? blockRecord.type : "";
201
+ if (TOOL_RESULT_TYPES.has(blockType)) {
202
+ handleContentBlock(session, blockRecord);
203
+ }
204
+ }
205
+ }
206
+ export function handleResultMessage(session, message) {
207
+ emitFastModeUpdateIfChanged(session, message.fast_mode_state);
208
+ const subtype = typeof message.subtype === "string" ? message.subtype : "";
209
+ if (subtype === "success") {
210
+ session.lastAssistantError = undefined;
211
+ finalizeOpenToolCalls(session, "completed");
212
+ writeEvent({ event: "turn_complete", session_id: session.sessionId });
213
+ return;
214
+ }
215
+ const errors = Array.isArray(message.errors) && message.errors.every((entry) => typeof entry === "string")
216
+ ? message.errors
217
+ : [];
218
+ const assistantError = session.lastAssistantError;
219
+ const authHint = errors.find((entry) => looksLikeAuthRequired(entry));
220
+ if (authHint) {
221
+ emitAuthRequired(session, authHint);
222
+ }
223
+ if (assistantError === "authentication_failed") {
224
+ emitAuthRequired(session);
225
+ }
226
+ finalizeOpenToolCalls(session, "failed");
227
+ const errorKind = classifyTurnErrorKind(subtype, errors, assistantError);
228
+ const fallback = subtype ? `turn failed: ${subtype}` : "turn failed";
229
+ writeEvent({
230
+ event: "turn_error",
231
+ session_id: session.sessionId,
232
+ message: errors.length > 0 ? errors.join("\n") : fallback,
233
+ error_kind: errorKind,
234
+ ...(subtype ? { sdk_result_subtype: subtype } : {}),
235
+ ...(assistantError ? { assistant_error: assistantError } : {}),
236
+ });
237
+ session.lastAssistantError = undefined;
238
+ }
239
+ export function handleSdkMessage(session, message) {
240
+ const msg = message;
241
+ const type = typeof msg.type === "string" ? msg.type : "";
242
+ if (type === "system") {
243
+ const subtype = typeof msg.subtype === "string" ? msg.subtype : "";
244
+ if (subtype === "init") {
245
+ const previousSessionId = session.sessionId;
246
+ const incomingSessionId = typeof msg.session_id === "string" ? msg.session_id : session.sessionId;
247
+ updateSessionId(session, incomingSessionId);
248
+ const previousModelName = session.model;
249
+ const modelName = typeof msg.model === "string" ? msg.model : session.model;
250
+ session.model = modelName;
251
+ const incomingMode = typeof msg.permissionMode === "string" ? toPermissionMode(msg.permissionMode) : null;
252
+ if (incomingMode) {
253
+ session.mode = incomingMode;
254
+ }
255
+ emitFastModeUpdateIfChanged(session, msg.fast_mode_state);
256
+ if (!session.connected) {
257
+ emitConnectEvent(session);
258
+ }
259
+ else if (previousSessionId !== session.sessionId) {
260
+ const historyUpdates = session.resumeUpdates;
261
+ writeEvent({
262
+ event: "session_replaced",
263
+ session_id: session.sessionId,
264
+ cwd: session.cwd,
265
+ model_name: session.model,
266
+ available_models: session.availableModels,
267
+ mode: session.mode ? buildModeState(session.mode) : null,
268
+ ...(historyUpdates && historyUpdates.length > 0
269
+ ? { history_updates: historyUpdates }
270
+ : {}),
271
+ });
272
+ session.resumeUpdates = undefined;
273
+ refreshSessionsList();
274
+ }
275
+ else {
276
+ if (session.model !== previousModelName) {
277
+ emitSessionUpdate(session.sessionId, {
278
+ type: "config_option_update",
279
+ option_id: "model",
280
+ value: session.model,
281
+ });
282
+ }
283
+ if (incomingMode) {
284
+ emitSessionUpdate(session.sessionId, {
285
+ type: "mode_state_update",
286
+ mode: buildModeState(incomingMode),
287
+ });
288
+ }
289
+ }
290
+ if (Array.isArray(msg.slash_commands)) {
291
+ const commands = msg.slash_commands
292
+ .filter((entry) => typeof entry === "string")
293
+ .map((name) => ({ name, description: "", input_hint: undefined }));
294
+ if (commands.length > 0) {
295
+ emitSessionUpdate(session.sessionId, { type: "available_commands_update", commands });
296
+ }
297
+ }
298
+ if (session.lastAvailableAgentsSignature === undefined && Array.isArray(msg.agents)) {
299
+ emitAvailableAgentsIfChanged(session, mapAvailableAgentsFromNames(msg.agents));
300
+ }
301
+ void session.query
302
+ .supportedCommands()
303
+ .then((commands) => {
304
+ const mapped = commands.map((command) => ({
305
+ name: command.name,
306
+ description: command.description ?? "",
307
+ input_hint: command.argumentHint ?? undefined,
308
+ }));
309
+ emitSessionUpdate(session.sessionId, { type: "available_commands_update", commands: mapped });
310
+ })
311
+ .catch(() => {
312
+ // Best-effort only; slash commands from init were already emitted.
313
+ });
314
+ refreshAvailableAgents(session);
315
+ return;
316
+ }
317
+ if (subtype === "status") {
318
+ const mode = typeof msg.permissionMode === "string" ? toPermissionMode(msg.permissionMode) : null;
319
+ if (mode) {
320
+ session.mode = mode;
321
+ emitSessionUpdate(session.sessionId, { type: "current_mode_update", current_mode_id: mode });
322
+ }
323
+ if (msg.status === "compacting") {
324
+ emitSessionUpdate(session.sessionId, { type: "session_status_update", status: "compacting" });
325
+ }
326
+ else if (msg.status === null) {
327
+ emitSessionUpdate(session.sessionId, { type: "session_status_update", status: "idle" });
328
+ }
329
+ emitFastModeUpdateIfChanged(session, msg.fast_mode_state);
330
+ return;
331
+ }
332
+ if (subtype === "compact_boundary") {
333
+ const compactMetadata = asRecordOrNull(msg.compact_metadata);
334
+ if (!compactMetadata) {
335
+ return;
336
+ }
337
+ const trigger = compactMetadata.trigger;
338
+ const preTokens = numberField(compactMetadata, "pre_tokens", "preTokens");
339
+ if ((trigger === "manual" || trigger === "auto") && preTokens !== undefined) {
340
+ emitSessionUpdate(session.sessionId, {
341
+ type: "compaction_boundary",
342
+ trigger,
343
+ pre_tokens: preTokens,
344
+ });
345
+ }
346
+ return;
347
+ }
348
+ if (subtype === "local_command_output") {
349
+ const content = typeof msg.content === "string" ? msg.content : "";
350
+ if (content.trim().length > 0) {
351
+ emitSessionUpdate(session.sessionId, {
352
+ type: "agent_message_chunk",
353
+ content: { type: "text", text: content },
354
+ });
355
+ }
356
+ return;
357
+ }
358
+ if (subtype === "elicitation_complete") {
359
+ // No-op: elicitation flow is auto-canceled in the onElicitation callback.
360
+ return;
361
+ }
362
+ handleTaskSystemMessage(session, subtype, msg);
363
+ return;
364
+ }
365
+ if (type === "auth_status") {
366
+ const output = Array.isArray(msg.output)
367
+ ? msg.output.filter((entry) => typeof entry === "string").join("\n")
368
+ : "";
369
+ const errorText = typeof msg.error === "string" ? msg.error : "";
370
+ const combined = [errorText, output].filter((entry) => entry.length > 0).join("\n");
371
+ if (combined && looksLikeAuthRequired(combined)) {
372
+ emitAuthRequired(session, combined);
373
+ }
374
+ return;
375
+ }
376
+ if (type === "stream_event") {
377
+ if (msg.event && typeof msg.event === "object") {
378
+ handleStreamEvent(session, msg.event);
379
+ }
380
+ return;
381
+ }
382
+ if (type === "tool_progress") {
383
+ const toolUseId = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
384
+ const toolName = typeof msg.tool_name === "string" ? msg.tool_name : "Tool";
385
+ if (toolUseId) {
386
+ emitToolProgressUpdate(session, toolUseId, toolName);
387
+ }
388
+ return;
389
+ }
390
+ if (type === "tool_use_summary") {
391
+ const summary = typeof msg.summary === "string" ? msg.summary : "";
392
+ const toolIds = Array.isArray(msg.preceding_tool_use_ids)
393
+ ? msg.preceding_tool_use_ids.filter((id) => typeof id === "string")
394
+ : [];
395
+ if (summary && toolIds.length > 0) {
396
+ for (const toolUseId of toolIds) {
397
+ emitToolSummaryUpdate(session, toolUseId, summary);
398
+ }
399
+ }
400
+ return;
401
+ }
402
+ if (type === "rate_limit_event") {
403
+ const update = buildRateLimitUpdate(msg.rate_limit_info);
404
+ if (update) {
405
+ emitSessionUpdate(session.sessionId, update);
406
+ }
407
+ return;
408
+ }
409
+ if (type === "user") {
410
+ handleUserToolResultBlocks(session, msg);
411
+ const toolUseId = typeof msg.parent_tool_use_id === "string" ? msg.parent_tool_use_id : "";
412
+ if (toolUseId && "tool_use_result" in msg) {
413
+ const parsed = unwrapToolUseResult(msg.tool_use_result);
414
+ emitToolResultUpdate(session, toolUseId, parsed.isError, parsed.content);
415
+ }
416
+ return;
417
+ }
418
+ if (type === "assistant") {
419
+ if (msg.error === "authentication_failed") {
420
+ emitAuthRequired(session);
421
+ }
422
+ handleAssistantMessage(session, msg);
423
+ return;
424
+ }
425
+ if (type === "result") {
426
+ handleResultMessage(session, msg);
427
+ }
428
+ }
@@ -83,12 +83,24 @@ export function permissionResultFromOutcome(outcome, toolCallId, inputData, sugg
83
83
  };
84
84
  }
85
85
  if (outcome.option_id === "allow_always") {
86
- const suggestionsForAlways = scopedSuggestions.persistent;
86
+ const persistentSuggestions = scopedSuggestions.persistent;
87
+ const fallbackSuggestions = persistentSuggestions.length > 0
88
+ ? persistentSuggestions
89
+ : toolName
90
+ ? [
91
+ {
92
+ type: "addRules",
93
+ rules: [{ toolName }],
94
+ behavior: "allow",
95
+ destination: "localSettings",
96
+ },
97
+ ]
98
+ : undefined;
87
99
  return {
88
100
  behavior: "allow",
89
101
  updatedInput: inputData,
90
- ...(suggestionsForAlways && suggestionsForAlways.length > 0
91
- ? { updatedPermissions: suggestionsForAlways }
102
+ ...(fallbackSuggestions && fallbackSuggestions.length > 0
103
+ ? { updatedPermissions: fallbackSuggestions }
92
104
  : {}),
93
105
  toolUseID: toolCallId,
94
106
  };