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.
- package/README.md +16 -9
- package/agent-sdk/README.md +1 -1
- package/agent-sdk/dist/bridge/commands.js +71 -2
- package/agent-sdk/dist/bridge/events.js +127 -12
- package/agent-sdk/dist/bridge/history.js +9 -4
- package/agent-sdk/dist/bridge/logger.js +253 -0
- package/agent-sdk/dist/bridge/mcp.js +81 -5
- package/agent-sdk/dist/bridge/message_handlers.js +318 -67
- package/agent-sdk/dist/bridge/session_lifecycle.js +774 -58
- package/agent-sdk/dist/bridge/shared.js +0 -7
- package/agent-sdk/dist/bridge/state_parsing.js +61 -0
- package/agent-sdk/dist/bridge/tool_calls.js +269 -66
- package/agent-sdk/dist/bridge/tooling.js +60 -27
- package/agent-sdk/dist/bridge/user_interaction.js +34 -23
- package/agent-sdk/dist/bridge.js +406 -42
- package/agent-sdk/dist/bridge.test.js +765 -23
- package/package.json +2 -2
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { asRecordOrNull } from "./shared.js";
|
|
2
|
-
import { toPermissionMode, buildModeState } from "./commands.js";
|
|
3
|
-
import { writeEvent, emitSessionUpdate, emitConnectEvent,
|
|
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" &&
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
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 (
|
|
277
|
-
|
|
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
|
-
|
|
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
|
}
|