claude-code-rust 0.9.0 → 0.11.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.
@@ -1,13 +1,14 @@
1
1
  import { asRecordOrNull } from "./shared.js";
2
- import { toPermissionMode, buildModeState } from "./commands.js";
3
- import { writeEvent, emitSessionUpdate, emitConnectEvent, refreshSessionsList } from "./events.js";
2
+ import { toPermissionMode, buildModeState, refreshSupportedModesForSession } from "./commands.js";
3
+ import { writeEvent, emitSessionUpdate, emitConnectEvent, emitSessionReplacedEvent, } from "./events.js";
4
4
  import { TOOL_RESULT_TYPES, unwrapToolUseResult } from "./tooling.js";
5
- import { emitToolCall, 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
+ import { bridgeLogger, LOG_TARGETS } from "./logger.js";
11
12
  export function textFromPrompt(command) {
12
13
  const chunks = command.chunks ?? [];
13
14
  return chunks
@@ -20,8 +21,83 @@ export function textFromPrompt(command) {
20
21
  .filter((part) => part.length > 0)
21
22
  .join("");
22
23
  }
24
+ /** MIME types supported by the Anthropic Vision API.
25
+ * NOTE: Keep in sync with `SUPPORTED_IMAGE_MIME_TYPES` in
26
+ * `src/app/clipboard_image.rs`. */
27
+ const SUPPORTED_IMAGE_MIME_TYPES = new Set([
28
+ "image/png",
29
+ "image/jpeg",
30
+ "image/gif",
31
+ "image/webp",
32
+ ]);
33
+ /** Fast check that a string looks like valid base64 (non-empty, correct charset & padding). */
34
+ function isValidBase64(data) {
35
+ if (!data)
36
+ return false;
37
+ const clean = data.replace(/\s/g, "");
38
+ if (clean.length % 4 !== 0)
39
+ return false;
40
+ // Padding ('=') must only appear at the end and be at most 2 characters.
41
+ return /^[A-Za-z0-9+/]+={0,2}$/.test(clean);
42
+ }
43
+ /**
44
+ * Build a content array from prompt chunks, supporting both text and image blocks.
45
+ * Returns the Anthropic API content block format expected by MessageParam.
46
+ */
47
+ export function contentFromPrompt(command) {
48
+ const chunks = command.chunks ?? [];
49
+ const content = [];
50
+ for (const chunk of chunks) {
51
+ if (chunk.kind === "text") {
52
+ const text = typeof chunk.value === "string" ? chunk.value : "";
53
+ if (text.trim()) {
54
+ content.push({ type: "text", text });
55
+ }
56
+ }
57
+ else if (chunk.kind === "image") {
58
+ const val = chunk.value && typeof chunk.value === "object" ? chunk.value : null;
59
+ if (!val)
60
+ continue;
61
+ const data = typeof val.data === "string" ? val.data : "";
62
+ const mimeType = typeof val.mime_type === "string" ? val.mime_type : "image/png";
63
+ if (!SUPPORTED_IMAGE_MIME_TYPES.has(mimeType)) {
64
+ bridgeLogger.warn({
65
+ target: LOG_TARGETS.BRIDGE_PROTOCOL,
66
+ eventName: "prompt_image_skipped",
67
+ message: "skipping unsupported prompt image type",
68
+ outcome: "skipped",
69
+ fields: { mime_type: mimeType },
70
+ });
71
+ continue;
72
+ }
73
+ if (!isValidBase64(data)) {
74
+ bridgeLogger.warn({
75
+ target: LOG_TARGETS.BRIDGE_PROTOCOL,
76
+ eventName: "prompt_image_skipped",
77
+ message: "skipping prompt image with invalid base64 data",
78
+ outcome: "skipped",
79
+ fields: { mime_type: mimeType, reason: "invalid_base64" },
80
+ });
81
+ continue;
82
+ }
83
+ const supportedMimeType = mimeType;
84
+ content.push({
85
+ type: "image",
86
+ source: {
87
+ type: "base64",
88
+ media_type: supportedMimeType,
89
+ data,
90
+ },
91
+ });
92
+ }
93
+ }
94
+ return content;
95
+ }
23
96
  export function handleTaskSystemMessage(session, subtype, msg) {
24
- 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") {
25
101
  return;
26
102
  }
27
103
  const taskId = typeof msg.task_id === "string" ? msg.task_id : "";
@@ -30,33 +106,68 @@ export function handleTaskSystemMessage(session, subtype, msg) {
30
106
  session.taskToolUseIds.set(taskId, explicitToolUseId);
31
107
  }
32
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
+ }
33
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
+ }
34
155
  return;
35
156
  }
36
157
  const toolCall = ensureToolCallVisible(session, toolUseId, "Agent", {});
37
158
  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
- });
159
+ emitToolCallUpdate(session, toolUseId, { status: "in_progress" }, "progress");
43
160
  }
44
161
  if (subtype === "task_started") {
45
162
  const description = typeof msg.description === "string" ? msg.description : "";
46
163
  if (!description) {
47
164
  return;
48
165
  }
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
- });
166
+ emitToolCallUpdate(session, toolUseId, {
167
+ status: "in_progress",
168
+ raw_output: description,
169
+ content: [{ type: "content", content: { type: "text", text: description } }],
170
+ }, "task_started");
60
171
  return;
61
172
  }
62
173
  if (subtype === "task_progress") {
@@ -64,37 +175,70 @@ export function handleTaskSystemMessage(session, subtype, msg) {
64
175
  if (!progress) {
65
176
  return;
66
177
  }
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
- },
178
+ emitToolCallUpdate(session, toolUseId, {
179
+ status: "in_progress",
180
+ raw_output: progress,
181
+ content: [{ type: "content", content: { type: "text", text: progress } }],
182
+ }, "task_progress");
183
+ return;
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,
76
203
  },
77
204
  });
205
+ emitToolCallUpdate(session, toolUseId, fields, "task_updated");
78
206
  return;
79
207
  }
80
208
  const status = typeof msg.status === "string" ? msg.status : "";
81
209
  const summary = typeof msg.summary === "string" ? msg.summary : "";
82
- const finalStatus = status === "completed" ? "completed" : "failed";
210
+ const finalStatus = status === "completed" ? "completed" : status === "stopped" ? "killed" : "failed";
83
211
  const fields = { status: finalStatus };
84
212
  if (summary) {
85
213
  fields.raw_output = summary;
86
214
  fields.content = [{ type: "content", content: { type: "text", text: summary } }];
87
215
  }
88
- emitSessionUpdate(session.sessionId, {
89
- type: "tool_call_update",
90
- tool_call_update: { tool_call_id: toolUseId, fields },
91
- });
92
- toolCall.status = finalStatus;
216
+ emitToolCallUpdate(session, toolUseId, fields, "task_notification");
93
217
  if (taskId) {
94
218
  session.taskToolUseIds.delete(taskId);
95
219
  }
96
220
  }
97
- 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) {
98
242
  const blockType = typeof block.type === "string" ? block.type : "";
99
243
  if (blockType === "text") {
100
244
  const text = typeof block.text === "string" ? block.text : "";
@@ -117,8 +261,9 @@ export function handleContentBlock(session, block) {
117
261
  if (!toolUseId) {
118
262
  return;
119
263
  }
264
+ logContentBlockLinkage(session, blockType, toolUseId, name, linkage);
120
265
  emitPlanIfTodoWrite(session, name, input);
121
- emitToolCall(session, toolUseId, name, input);
266
+ emitToolCall(session, toolUseId, name, input, linkage?.parentToolUseId ?? null);
122
267
  return;
123
268
  }
124
269
  if (TOOL_RESULT_TYPES.has(blockType)) {
@@ -126,15 +271,19 @@ export function handleContentBlock(session, block) {
126
271
  if (!toolUseId) {
127
272
  return;
128
273
  }
274
+ logContentBlockLinkage(session, blockType, toolUseId, undefined, linkage);
129
275
  const isError = Boolean(block.is_error);
130
276
  emitToolResultUpdate(session, toolUseId, isError, block.content, block);
131
277
  }
132
278
  }
133
- export function handleStreamEvent(session, event) {
279
+ export function handleStreamEvent(session, event, parentToolUseId) {
134
280
  const eventType = typeof event.type === "string" ? event.type : "";
135
281
  if (eventType === "content_block_start") {
136
282
  if (event.content_block && typeof event.content_block === "object") {
137
- handleContentBlock(session, event.content_block);
283
+ handleContentBlock(session, event.content_block, {
284
+ source: "stream_event",
285
+ parentToolUseId,
286
+ });
138
287
  }
139
288
  return;
140
289
  }
@@ -180,7 +329,8 @@ export function handleAssistantMessage(session, message) {
180
329
  blockType === "server_tool_use" ||
181
330
  blockType === "mcp_tool_use" ||
182
331
  TOOL_RESULT_TYPES.has(blockType)) {
183
- 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 });
184
334
  }
185
335
  }
186
336
  }
@@ -199,17 +349,23 @@ export function handleUserToolResultBlocks(session, message) {
199
349
  const blockRecord = block;
200
350
  const blockType = typeof blockRecord.type === "string" ? blockRecord.type : "";
201
351
  if (TOOL_RESULT_TYPES.has(blockType)) {
202
- 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 });
203
354
  }
204
355
  }
205
356
  }
206
357
  export function handleResultMessage(session, message) {
207
358
  emitFastModeUpdateIfChanged(session, message.fast_mode_state);
359
+ const terminalReason = terminalReasonFromValue(message.terminal_reason);
208
360
  const subtype = typeof message.subtype === "string" ? message.subtype : "";
209
361
  if (subtype === "success") {
210
362
  session.lastAssistantError = undefined;
211
363
  finalizeOpenToolCalls(session, "completed");
212
- 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
+ });
213
369
  return;
214
370
  }
215
371
  const errors = Array.isArray(message.errors) && message.errors.every((entry) => typeof entry === "string")
@@ -233,57 +389,78 @@ export function handleResultMessage(session, message) {
233
389
  error_kind: errorKind,
234
390
  ...(subtype ? { sdk_result_subtype: subtype } : {}),
235
391
  ...(assistantError ? { assistant_error: assistantError } : {}),
392
+ ...(terminalReason ? { terminal_reason: terminalReason } : {}),
236
393
  });
237
394
  session.lastAssistantError = undefined;
238
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
+ }
239
415
  export function handleSdkMessage(session, message) {
240
416
  const msg = message;
241
417
  const type = typeof msg.type === "string" ? msg.type : "";
242
418
  if (type === "system") {
243
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
+ }
244
437
  if (subtype === "init") {
245
438
  const previousSessionId = session.sessionId;
246
439
  const incomingSessionId = typeof msg.session_id === "string" ? msg.session_id : session.sessionId;
247
440
  updateSessionId(session, incomingSessionId);
248
- const previousModelName = session.model;
249
441
  const modelName = typeof msg.model === "string" ? msg.model : session.model;
250
442
  session.model = modelName;
443
+ const currentModelChanged = refreshCurrentModel(session, false);
251
444
  const incomingMode = typeof msg.permissionMode === "string" ? toPermissionMode(msg.permissionMode) : null;
252
445
  if (incomingMode) {
253
446
  session.mode = incomingMode;
254
447
  }
448
+ refreshSupportedModesForSession(session);
255
449
  emitFastModeUpdateIfChanged(session, msg.fast_mode_state);
256
450
  if (!session.connected) {
257
451
  emitConnectEvent(session);
258
452
  }
259
453
  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();
454
+ emitSessionReplacedEvent(session);
274
455
  }
275
456
  else {
276
- if (session.model !== previousModelName) {
277
- emitSessionUpdate(session.sessionId, {
278
- type: "config_option_update",
279
- option_id: "model",
280
- value: session.model,
281
- });
457
+ if (currentModelChanged) {
458
+ emitCurrentModelUpdate(session);
282
459
  }
283
460
  if (incomingMode) {
284
461
  emitSessionUpdate(session.sessionId, {
285
462
  type: "mode_state_update",
286
- mode: buildModeState(incomingMode),
463
+ mode: buildModeState(session, incomingMode),
287
464
  });
288
465
  }
289
466
  }
@@ -312,12 +489,19 @@ export function handleSdkMessage(session, message) {
312
489
  // Best-effort only; slash commands from init were already emitted.
313
490
  });
314
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
+ }
315
498
  return;
316
499
  }
317
500
  if (subtype === "status") {
318
501
  const mode = typeof msg.permissionMode === "string" ? toPermissionMode(msg.permissionMode) : null;
319
502
  if (mode) {
320
503
  session.mode = mode;
504
+ refreshSupportedModesForSession(session);
321
505
  emitSessionUpdate(session.sessionId, { type: "current_mode_update", current_mode_id: mode });
322
506
  }
323
507
  if (msg.status === "compacting") {
@@ -373,6 +557,22 @@ export function handleSdkMessage(session, message) {
373
557
  handleTaskSystemMessage(session, subtype, msg);
374
558
  return;
375
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
+ }
376
576
  if (type === "auth_status") {
377
577
  const output = Array.isArray(msg.output)
378
578
  ? msg.output.filter((entry) => typeof entry === "string").join("\n")
@@ -386,13 +586,28 @@ export function handleSdkMessage(session, message) {
386
586
  }
387
587
  if (type === "stream_event") {
388
588
  if (msg.event && typeof msg.event === "object") {
389
- 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);
390
591
  }
391
592
  return;
392
593
  }
393
594
  if (type === "tool_progress") {
394
595
  const toolUseId = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
395
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
+ });
396
611
  if (toolUseId) {
397
612
  emitToolProgressUpdate(session, toolUseId, toolName);
398
613
  }
@@ -411,7 +626,43 @@ export function handleSdkMessage(session, message) {
411
626
  return;
412
627
  }
413
628
  if (type === "rate_limit_event") {
629
+ const rateLimitInfo = asRecordOrNull(msg.rate_limit_info);
414
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
+ });
415
666
  if (update) {
416
667
  emitSessionUpdate(session.sessionId, update);
417
668
  }