claude-code-rust 0.10.0 → 0.11.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.
@@ -1,13 +1,13 @@
1
1
  import { asRecordOrNull } from "./shared.js";
2
- import { toPermissionMode, buildModeState } from "./commands.js";
2
+ import { toPermissionMode, buildModeState, refreshSupportedModesForSession } from "./commands.js";
3
3
  import { writeEvent, emitSessionUpdate, emitConnectEvent, emitSessionReplacedEvent, } from "./events.js";
4
4
  import { TOOL_RESULT_TYPES, unwrapToolUseResult } from "./tooling.js";
5
- import { emitToolCall, emitToolCallUpdate, emitPlanIfTodoWrite, emitToolResultUpdate, finalizeOpenToolCalls, emitToolProgressUpdate, emitToolSummaryUpdate, ensureToolCallVisible, resolveTaskToolUseId, taskProgressText, } from "./tool_calls.js";
5
+ import { emitToolCall, emitToolCallUpdate, emitPlanIfTodoWrite, emitToolResultUpdate, finalizeOpenToolCalls, emitToolProgressUpdate, emitToolSummaryUpdate, ensureToolCallVisible, resolveTaskToolUseId, taskProgressText, taskUpdatedFields, } from "./tool_calls.js";
6
6
  import { emitAuthRequired, classifyTurnErrorKind, emitFastModeUpdateIfChanged } from "./error_classification.js";
7
7
  import { mapAvailableAgentsFromNames, emitAvailableAgentsIfChanged, refreshAvailableAgents } from "./agents.js";
8
- import { buildRateLimitUpdate, numberField } from "./state_parsing.js";
8
+ import { buildApiRetryUpdate, buildRateLimitUpdate, normalizeSettingsParseErrors, numberField, parseRuntimeSessionState, } from "./state_parsing.js";
9
9
  import { looksLikeAuthRequired } from "./auth.js";
10
- import { updateSessionId } from "./session_lifecycle.js";
10
+ import { emitCurrentModelUpdate, refreshCurrentModel, updateSessionId } from "./session_lifecycle.js";
11
11
  import { bridgeLogger, LOG_TARGETS } from "./logger.js";
12
12
  export function textFromPrompt(command) {
13
13
  const chunks = command.chunks ?? [];
@@ -80,11 +80,12 @@ export function contentFromPrompt(command) {
80
80
  });
81
81
  continue;
82
82
  }
83
+ const supportedMimeType = mimeType;
83
84
  content.push({
84
85
  type: "image",
85
86
  source: {
86
87
  type: "base64",
87
- media_type: mimeType,
88
+ media_type: supportedMimeType,
88
89
  data,
89
90
  },
90
91
  });
@@ -93,7 +94,10 @@ export function contentFromPrompt(command) {
93
94
  return content;
94
95
  }
95
96
  export function handleTaskSystemMessage(session, subtype, msg) {
96
- if (subtype !== "task_started" && subtype !== "task_progress" && subtype !== "task_notification") {
97
+ if (subtype !== "task_started" &&
98
+ subtype !== "task_progress" &&
99
+ subtype !== "task_updated" &&
100
+ subtype !== "task_notification") {
97
101
  return;
98
102
  }
99
103
  const taskId = typeof msg.task_id === "string" ? msg.task_id : "";
@@ -102,7 +106,52 @@ export function handleTaskSystemMessage(session, subtype, msg) {
102
106
  session.taskToolUseIds.set(taskId, explicitToolUseId);
103
107
  }
104
108
  const toolUseId = resolveTaskToolUseId(session, msg);
109
+ bridgeLogger.debug({
110
+ target: LOG_TARGETS.APP_TOOL,
111
+ eventName: "sdk_task_linkage_observed",
112
+ message: "SDK task lifecycle linkage observed",
113
+ outcome: toolUseId ? "resolved" : "unresolved",
114
+ sessionId: session.sessionId,
115
+ toolCallId: toolUseId || explicitToolUseId || undefined,
116
+ fields: {
117
+ sdk_subtype: subtype,
118
+ task_id: taskId || undefined,
119
+ explicit_tool_use_id: explicitToolUseId || undefined,
120
+ resolved_tool_use_id: toolUseId || undefined,
121
+ task_status: typeof msg.status === "string" ? msg.status : undefined,
122
+ has_description: typeof msg.description === "string" && msg.description.length > 0,
123
+ has_summary: typeof msg.summary === "string" && msg.summary.length > 0,
124
+ last_tool_name: typeof msg.last_tool_name === "string" ? msg.last_tool_name : undefined,
125
+ },
126
+ });
127
+ if (subtype === "task_updated") {
128
+ bridgeLogger.debug({
129
+ target: LOG_TARGETS.APP_TOOL,
130
+ eventName: "task_updated_received",
131
+ message: "task update received",
132
+ outcome: toolUseId ? "resolved" : "unresolved",
133
+ sessionId: session.sessionId,
134
+ toolCallId: toolUseId || undefined,
135
+ fields: {
136
+ task_id: taskId,
137
+ explicit_tool_use_id: explicitToolUseId || undefined,
138
+ patch_keys: msg.patch && typeof msg.patch === "object"
139
+ ? Object.keys(msg.patch).sort()
140
+ : undefined,
141
+ },
142
+ });
143
+ }
105
144
  if (!toolUseId) {
145
+ if (subtype === "task_updated" && taskId) {
146
+ bridgeLogger.debug({
147
+ target: LOG_TARGETS.APP_TOOL,
148
+ eventName: "task_updated_unlinked",
149
+ message: "task update skipped because no visible tool call was linked",
150
+ outcome: "skipped",
151
+ sessionId: session.sessionId,
152
+ fields: { task_id: taskId, subtype },
153
+ });
154
+ }
106
155
  return;
107
156
  }
108
157
  const toolCall = ensureToolCallVisible(session, toolUseId, "Agent", {});
@@ -133,9 +182,32 @@ export function handleTaskSystemMessage(session, subtype, msg) {
133
182
  }, "task_progress");
134
183
  return;
135
184
  }
185
+ if (subtype === "task_updated") {
186
+ const fields = taskUpdatedFields(msg);
187
+ if (Object.keys(fields).length === 0) {
188
+ return;
189
+ }
190
+ bridgeLogger.debug({
191
+ target: LOG_TARGETS.APP_TOOL,
192
+ eventName: "task_updated_emitted",
193
+ message: "task update mapped to tool call update",
194
+ outcome: "success",
195
+ sessionId: session.sessionId,
196
+ toolCallId: toolUseId,
197
+ fields: {
198
+ task_id: taskId,
199
+ mapped_status: fields.status,
200
+ has_description: fields.content !== undefined,
201
+ has_error: Boolean(fields.task_metadata?.error),
202
+ is_backgrounded: fields.task_metadata?.is_backgrounded,
203
+ },
204
+ });
205
+ emitToolCallUpdate(session, toolUseId, fields, "task_updated");
206
+ return;
207
+ }
136
208
  const status = typeof msg.status === "string" ? msg.status : "";
137
209
  const summary = typeof msg.summary === "string" ? msg.summary : "";
138
- const finalStatus = status === "completed" ? "completed" : "failed";
210
+ const finalStatus = status === "completed" ? "completed" : status === "stopped" ? "killed" : "failed";
139
211
  const fields = { status: finalStatus };
140
212
  if (summary) {
141
213
  fields.raw_output = summary;
@@ -146,7 +218,27 @@ export function handleTaskSystemMessage(session, subtype, msg) {
146
218
  session.taskToolUseIds.delete(taskId);
147
219
  }
148
220
  }
149
- export function handleContentBlock(session, block) {
221
+ function logContentBlockLinkage(session, blockType, toolUseId, toolName, linkage) {
222
+ if (!toolUseId && !linkage?.parentToolUseId) {
223
+ return;
224
+ }
225
+ bridgeLogger.debug({
226
+ target: LOG_TARGETS.APP_TOOL,
227
+ eventName: "sdk_tool_linkage_observed",
228
+ message: "SDK tool linkage observed",
229
+ outcome: linkage?.parentToolUseId ? "child" : "root_or_unknown",
230
+ sessionId: session.sessionId,
231
+ toolCallId: toolUseId || undefined,
232
+ fields: {
233
+ source: linkage?.source,
234
+ block_type: blockType || undefined,
235
+ tool_name: toolName,
236
+ tool_use_id: toolUseId || undefined,
237
+ parent_tool_use_id: linkage?.parentToolUseId,
238
+ },
239
+ });
240
+ }
241
+ export function handleContentBlock(session, block, linkage) {
150
242
  const blockType = typeof block.type === "string" ? block.type : "";
151
243
  if (blockType === "text") {
152
244
  const text = typeof block.text === "string" ? block.text : "";
@@ -169,8 +261,9 @@ export function handleContentBlock(session, block) {
169
261
  if (!toolUseId) {
170
262
  return;
171
263
  }
264
+ logContentBlockLinkage(session, blockType, toolUseId, name, linkage);
172
265
  emitPlanIfTodoWrite(session, name, input);
173
- emitToolCall(session, toolUseId, name, input);
266
+ emitToolCall(session, toolUseId, name, input, linkage?.parentToolUseId ?? null);
174
267
  return;
175
268
  }
176
269
  if (TOOL_RESULT_TYPES.has(blockType)) {
@@ -178,15 +271,19 @@ export function handleContentBlock(session, block) {
178
271
  if (!toolUseId) {
179
272
  return;
180
273
  }
274
+ logContentBlockLinkage(session, blockType, toolUseId, undefined, linkage);
181
275
  const isError = Boolean(block.is_error);
182
276
  emitToolResultUpdate(session, toolUseId, isError, block.content, block);
183
277
  }
184
278
  }
185
- export function handleStreamEvent(session, event) {
279
+ export function handleStreamEvent(session, event, parentToolUseId) {
186
280
  const eventType = typeof event.type === "string" ? event.type : "";
187
281
  if (eventType === "content_block_start") {
188
282
  if (event.content_block && typeof event.content_block === "object") {
189
- handleContentBlock(session, event.content_block);
283
+ handleContentBlock(session, event.content_block, {
284
+ source: "stream_event",
285
+ parentToolUseId,
286
+ });
190
287
  }
191
288
  return;
192
289
  }
@@ -232,7 +329,8 @@ export function handleAssistantMessage(session, message) {
232
329
  blockType === "server_tool_use" ||
233
330
  blockType === "mcp_tool_use" ||
234
331
  TOOL_RESULT_TYPES.has(blockType)) {
235
- handleContentBlock(session, blockRecord);
332
+ const parentToolUseId = typeof message.parent_tool_use_id === "string" ? message.parent_tool_use_id : undefined;
333
+ handleContentBlock(session, blockRecord, { source: "assistant", parentToolUseId });
236
334
  }
237
335
  }
238
336
  }
@@ -251,17 +349,23 @@ export function handleUserToolResultBlocks(session, message) {
251
349
  const blockRecord = block;
252
350
  const blockType = typeof blockRecord.type === "string" ? blockRecord.type : "";
253
351
  if (TOOL_RESULT_TYPES.has(blockType)) {
254
- handleContentBlock(session, blockRecord);
352
+ const parentToolUseId = typeof message.parent_tool_use_id === "string" ? message.parent_tool_use_id : undefined;
353
+ handleContentBlock(session, blockRecord, { source: "user", parentToolUseId });
255
354
  }
256
355
  }
257
356
  }
258
357
  export function handleResultMessage(session, message) {
259
358
  emitFastModeUpdateIfChanged(session, message.fast_mode_state);
359
+ const terminalReason = terminalReasonFromValue(message.terminal_reason);
260
360
  const subtype = typeof message.subtype === "string" ? message.subtype : "";
261
361
  if (subtype === "success") {
262
362
  session.lastAssistantError = undefined;
263
363
  finalizeOpenToolCalls(session, "completed");
264
- writeEvent({ event: "turn_complete", session_id: session.sessionId });
364
+ writeEvent({
365
+ event: "turn_complete",
366
+ session_id: session.sessionId,
367
+ ...(terminalReason ? { terminal_reason: terminalReason } : {}),
368
+ });
265
369
  return;
266
370
  }
267
371
  const errors = Array.isArray(message.errors) && message.errors.every((entry) => typeof entry === "string")
@@ -285,25 +389,63 @@ export function handleResultMessage(session, message) {
285
389
  error_kind: errorKind,
286
390
  ...(subtype ? { sdk_result_subtype: subtype } : {}),
287
391
  ...(assistantError ? { assistant_error: assistantError } : {}),
392
+ ...(terminalReason ? { terminal_reason: terminalReason } : {}),
288
393
  });
289
394
  session.lastAssistantError = undefined;
290
395
  }
396
+ function terminalReasonFromValue(value) {
397
+ switch (value) {
398
+ case "blocking_limit":
399
+ case "rapid_refill_breaker":
400
+ case "prompt_too_long":
401
+ case "image_error":
402
+ case "model_error":
403
+ case "aborted_streaming":
404
+ case "aborted_tools":
405
+ case "stop_hook_prevented":
406
+ case "hook_stopped":
407
+ case "tool_deferred":
408
+ case "max_turns":
409
+ case "completed":
410
+ return value;
411
+ default:
412
+ return undefined;
413
+ }
414
+ }
291
415
  export function handleSdkMessage(session, message) {
292
416
  const msg = message;
293
417
  const type = typeof msg.type === "string" ? msg.type : "";
294
418
  if (type === "system") {
295
419
  const subtype = typeof msg.subtype === "string" ? msg.subtype : "";
420
+ if (subtype === "api_retry") {
421
+ const update = buildApiRetryUpdate(msg);
422
+ if (update) {
423
+ emitSessionUpdate(session.sessionId, update);
424
+ }
425
+ return;
426
+ }
427
+ if (subtype === "session_state_changed") {
428
+ const state = parseRuntimeSessionState(msg.state);
429
+ if (state) {
430
+ emitSessionUpdate(session.sessionId, {
431
+ type: "runtime_session_state_update",
432
+ state,
433
+ });
434
+ }
435
+ return;
436
+ }
296
437
  if (subtype === "init") {
297
438
  const previousSessionId = session.sessionId;
298
439
  const incomingSessionId = typeof msg.session_id === "string" ? msg.session_id : session.sessionId;
299
440
  updateSessionId(session, incomingSessionId);
300
- const previousModelName = session.model;
301
441
  const modelName = typeof msg.model === "string" ? msg.model : session.model;
302
442
  session.model = modelName;
443
+ const currentModelChanged = refreshCurrentModel(session, false);
303
444
  const incomingMode = typeof msg.permissionMode === "string" ? toPermissionMode(msg.permissionMode) : null;
304
445
  if (incomingMode) {
305
446
  session.mode = incomingMode;
306
447
  }
448
+ refreshSupportedModesForSession(session);
307
449
  emitFastModeUpdateIfChanged(session, msg.fast_mode_state);
308
450
  if (!session.connected) {
309
451
  emitConnectEvent(session);
@@ -312,17 +454,13 @@ export function handleSdkMessage(session, message) {
312
454
  emitSessionReplacedEvent(session);
313
455
  }
314
456
  else {
315
- if (session.model !== previousModelName) {
316
- emitSessionUpdate(session.sessionId, {
317
- type: "config_option_update",
318
- option_id: "model",
319
- value: session.model,
320
- });
457
+ if (currentModelChanged) {
458
+ emitCurrentModelUpdate(session);
321
459
  }
322
460
  if (incomingMode) {
323
461
  emitSessionUpdate(session.sessionId, {
324
462
  type: "mode_state_update",
325
- mode: buildModeState(incomingMode),
463
+ mode: buildModeState(session, incomingMode),
326
464
  });
327
465
  }
328
466
  }
@@ -351,12 +489,19 @@ export function handleSdkMessage(session, message) {
351
489
  // Best-effort only; slash commands from init were already emitted.
352
490
  });
353
491
  refreshAvailableAgents(session);
492
+ for (const settingsError of normalizeSettingsParseErrors(msg.settings_errors ?? msg.settingsErrors)) {
493
+ emitSessionUpdate(session.sessionId, {
494
+ type: "settings_parse_error",
495
+ ...settingsError,
496
+ });
497
+ }
354
498
  return;
355
499
  }
356
500
  if (subtype === "status") {
357
501
  const mode = typeof msg.permissionMode === "string" ? toPermissionMode(msg.permissionMode) : null;
358
502
  if (mode) {
359
503
  session.mode = mode;
504
+ refreshSupportedModesForSession(session);
360
505
  emitSessionUpdate(session.sessionId, { type: "current_mode_update", current_mode_id: mode });
361
506
  }
362
507
  if (msg.status === "compacting") {
@@ -412,6 +557,22 @@ export function handleSdkMessage(session, message) {
412
557
  handleTaskSystemMessage(session, subtype, msg);
413
558
  return;
414
559
  }
560
+ if (type === "prompt_suggestion") {
561
+ const suggestion = typeof msg.suggestion === "string" ? msg.suggestion.trim() : "";
562
+ if (suggestion) {
563
+ emitSessionUpdate(session.sessionId, { type: "prompt_suggestion_update", suggestion });
564
+ }
565
+ return;
566
+ }
567
+ if (type === "settings_parse_error") {
568
+ for (const settingsError of normalizeSettingsParseErrors(msg)) {
569
+ emitSessionUpdate(session.sessionId, {
570
+ type: "settings_parse_error",
571
+ ...settingsError,
572
+ });
573
+ }
574
+ return;
575
+ }
415
576
  if (type === "auth_status") {
416
577
  const output = Array.isArray(msg.output)
417
578
  ? msg.output.filter((entry) => typeof entry === "string").join("\n")
@@ -425,13 +586,28 @@ export function handleSdkMessage(session, message) {
425
586
  }
426
587
  if (type === "stream_event") {
427
588
  if (msg.event && typeof msg.event === "object") {
428
- handleStreamEvent(session, msg.event);
589
+ const parentToolUseId = typeof msg.parent_tool_use_id === "string" ? msg.parent_tool_use_id : undefined;
590
+ handleStreamEvent(session, msg.event, parentToolUseId);
429
591
  }
430
592
  return;
431
593
  }
432
594
  if (type === "tool_progress") {
433
595
  const toolUseId = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
434
596
  const toolName = typeof msg.tool_name === "string" ? msg.tool_name : "Tool";
597
+ bridgeLogger.debug({
598
+ target: LOG_TARGETS.APP_TOOL,
599
+ eventName: "sdk_tool_progress_linkage_observed",
600
+ message: "SDK tool progress linkage observed",
601
+ outcome: typeof msg.parent_tool_use_id === "string" ? "child" : "root_or_unknown",
602
+ sessionId: session.sessionId,
603
+ toolCallId: toolUseId || undefined,
604
+ fields: {
605
+ tool_name: toolName,
606
+ tool_use_id: toolUseId || undefined,
607
+ parent_tool_use_id: typeof msg.parent_tool_use_id === "string" ? msg.parent_tool_use_id : undefined,
608
+ task_id: typeof msg.task_id === "string" ? msg.task_id : undefined,
609
+ },
610
+ });
435
611
  if (toolUseId) {
436
612
  emitToolProgressUpdate(session, toolUseId, toolName);
437
613
  }
@@ -450,7 +626,43 @@ export function handleSdkMessage(session, message) {
450
626
  return;
451
627
  }
452
628
  if (type === "rate_limit_event") {
629
+ const rateLimitInfo = asRecordOrNull(msg.rate_limit_info);
453
630
  const update = buildRateLimitUpdate(msg.rate_limit_info);
631
+ bridgeLogger.debug({
632
+ target: LOG_TARGETS.APP_SESSION,
633
+ eventName: "sdk_rate_limit_event_received",
634
+ message: "SDK rate limit event received",
635
+ outcome: update ? "success" : "dropped",
636
+ sessionId: session.sessionId,
637
+ fields: {
638
+ raw_status: typeof rateLimitInfo?.status === "string" ? rateLimitInfo.status : undefined,
639
+ raw_rate_limit_type: typeof rateLimitInfo?.rateLimitType === "string" ? rateLimitInfo.rateLimitType : undefined,
640
+ raw_utilization: numberField(rateLimitInfo ?? {}, "utilization"),
641
+ raw_resets_at: numberField(rateLimitInfo ?? {}, "resetsAt"),
642
+ raw_overage_status: typeof rateLimitInfo?.overageStatus === "string" ? rateLimitInfo.overageStatus : undefined,
643
+ raw_overage_resets_at: numberField(rateLimitInfo ?? {}, "overageResetsAt"),
644
+ raw_is_using_overage: typeof rateLimitInfo?.isUsingOverage === "boolean" ? rateLimitInfo.isUsingOverage : undefined,
645
+ raw_surpassed_threshold: numberField(rateLimitInfo ?? {}, "surpassedThreshold"),
646
+ parsed_status: update?.status,
647
+ parsed_rate_limit_type: update?.rate_limit_type,
648
+ parsed_utilization: update?.utilization,
649
+ parsed_resets_at: update?.resets_at,
650
+ parsed_overage_status: update?.overage_status,
651
+ parsed_overage_resets_at: update?.overage_resets_at,
652
+ parsed_is_using_overage: update?.is_using_overage,
653
+ parsed_surpassed_threshold: update?.surpassed_threshold,
654
+ },
655
+ });
656
+ bridgeLogger.debug({
657
+ target: LOG_TARGETS.APP_SESSION,
658
+ eventName: "sdk_rate_limit_event_raw",
659
+ message: "SDK rate limit event raw payload",
660
+ outcome: rateLimitInfo ? "success" : "dropped",
661
+ sessionId: session.sessionId,
662
+ fields: {
663
+ raw_rate_limit_info: msg.rate_limit_info,
664
+ },
665
+ });
454
666
  if (update) {
455
667
  emitSessionUpdate(session.sessionId, update);
456
668
  }