claude-code-rust 0.10.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 +7 -6
- package/agent-sdk/README.md +1 -1
- package/agent-sdk/dist/bridge/commands.js +71 -2
- package/agent-sdk/dist/bridge/events.js +11 -4
- package/agent-sdk/dist/bridge/history.js +9 -4
- package/agent-sdk/dist/bridge/logger.js +8 -0
- package/agent-sdk/dist/bridge/message_handlers.js +235 -23
- package/agent-sdk/dist/bridge/session_lifecycle.js +389 -7
- package/agent-sdk/dist/bridge/state_parsing.js +61 -0
- package/agent-sdk/dist/bridge/tool_calls.js +96 -8
- package/agent-sdk/dist/bridge/tooling.js +60 -27
- package/agent-sdk/dist/bridge.js +203 -21
- package/agent-sdk/dist/bridge.test.js +765 -23
- package/package.json +2 -2
|
@@ -51,6 +51,7 @@ function updateOutcome(status) {
|
|
|
51
51
|
case "completed":
|
|
52
52
|
return "success";
|
|
53
53
|
case "failed":
|
|
54
|
+
case "killed":
|
|
54
55
|
return "failure";
|
|
55
56
|
case "in_progress":
|
|
56
57
|
return "partial";
|
|
@@ -60,6 +61,25 @@ function updateOutcome(status) {
|
|
|
60
61
|
return "partial";
|
|
61
62
|
}
|
|
62
63
|
}
|
|
64
|
+
function parentToolUseIdFromMeta(meta) {
|
|
65
|
+
if (!meta || typeof meta !== "object") {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const claudeCode = "claudeCode" in meta && meta.claudeCode && typeof meta.claudeCode === "object" ? meta.claudeCode : undefined;
|
|
69
|
+
const parentToolUseId = claudeCode && "parentToolUseId" in claudeCode && typeof claudeCode.parentToolUseId === "string"
|
|
70
|
+
? claudeCode.parentToolUseId
|
|
71
|
+
: null;
|
|
72
|
+
return parentToolUseId;
|
|
73
|
+
}
|
|
74
|
+
function mergeTaskMetadata(current, update) {
|
|
75
|
+
if (update === undefined) {
|
|
76
|
+
return current;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
...(current ?? {}),
|
|
80
|
+
...update,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
63
83
|
function applyFieldsToBase(base, fields) {
|
|
64
84
|
if (fields.title !== undefined) {
|
|
65
85
|
base.title = fields.title;
|
|
@@ -82,6 +102,9 @@ function applyFieldsToBase(base, fields) {
|
|
|
82
102
|
if (fields.output_metadata !== undefined) {
|
|
83
103
|
base.output_metadata = fields.output_metadata;
|
|
84
104
|
}
|
|
105
|
+
if (fields.task_metadata !== undefined) {
|
|
106
|
+
base.task_metadata = mergeTaskMetadata(base.task_metadata, fields.task_metadata);
|
|
107
|
+
}
|
|
85
108
|
if (fields.meta !== undefined) {
|
|
86
109
|
base.meta = fields.meta;
|
|
87
110
|
}
|
|
@@ -132,10 +155,11 @@ function logToolCallUpdateEmitted(sessionId, toolUseId, fields, base, updateKind
|
|
|
132
155
|
location_count: fields.locations?.length,
|
|
133
156
|
raw_output_chars: rawOutput?.length,
|
|
134
157
|
has_output_metadata: fields.output_metadata !== undefined || base?.output_metadata !== undefined,
|
|
158
|
+
has_task_metadata: fields.task_metadata !== undefined || base?.task_metadata !== undefined,
|
|
135
159
|
failure_kind: failureKind,
|
|
136
160
|
},
|
|
137
161
|
};
|
|
138
|
-
if (nextStatus === "failed") {
|
|
162
|
+
if (nextStatus === "failed" || nextStatus === "killed") {
|
|
139
163
|
bridgeLogger.warn(commonEvent);
|
|
140
164
|
return;
|
|
141
165
|
}
|
|
@@ -165,11 +189,12 @@ export function emitToolCallUpdate(session, toolUseId, fields, updateKind) {
|
|
|
165
189
|
applyFieldsToBase(base, fields);
|
|
166
190
|
}
|
|
167
191
|
}
|
|
168
|
-
export function emitToolCall(session, toolUseId, name, input) {
|
|
169
|
-
const
|
|
192
|
+
export function emitToolCall(session, toolUseId, name, input, parentToolUseId = null) {
|
|
193
|
+
const existing = session.toolCalls.get(toolUseId);
|
|
194
|
+
const resolvedParentToolUseId = parentToolUseId ?? parentToolUseIdFromMeta(existing?.meta);
|
|
195
|
+
const toolCall = createToolCall(toolUseId, name, input, resolvedParentToolUseId);
|
|
170
196
|
const status = "in_progress";
|
|
171
197
|
toolCall.status = status;
|
|
172
|
-
const existing = session.toolCalls.get(toolUseId);
|
|
173
198
|
if (!existing) {
|
|
174
199
|
emitInitialToolCall(session, toolCall);
|
|
175
200
|
return;
|
|
@@ -187,12 +212,17 @@ export function emitToolCall(session, toolUseId, name, input) {
|
|
|
187
212
|
}
|
|
188
213
|
emitToolCallUpdate(session, toolUseId, fields, "refresh");
|
|
189
214
|
}
|
|
190
|
-
export function ensureToolCallVisible(session, toolUseId, toolName, input) {
|
|
215
|
+
export function ensureToolCallVisible(session, toolUseId, toolName, input, parentToolUseId = null) {
|
|
191
216
|
const existing = session.toolCalls.get(toolUseId);
|
|
192
217
|
if (existing) {
|
|
218
|
+
const existingParentToolUseId = parentToolUseIdFromMeta(existing.meta);
|
|
219
|
+
if (parentToolUseId && existingParentToolUseId !== parentToolUseId) {
|
|
220
|
+
const refreshed = createToolCall(toolUseId, toolName, input, parentToolUseId);
|
|
221
|
+
emitToolCallUpdate(session, toolUseId, { meta: refreshed.meta }, "refresh");
|
|
222
|
+
}
|
|
193
223
|
return existing;
|
|
194
224
|
}
|
|
195
|
-
const toolCall = createToolCall(toolUseId, toolName, input);
|
|
225
|
+
const toolCall = createToolCall(toolUseId, toolName, input, parentToolUseId);
|
|
196
226
|
emitInitialToolCall(session, toolCall);
|
|
197
227
|
return toolCall;
|
|
198
228
|
}
|
|
@@ -238,7 +268,8 @@ export function emitToolProgressUpdate(session, toolUseId, toolName) {
|
|
|
238
268
|
}
|
|
239
269
|
if (existing.status === "in_progress" ||
|
|
240
270
|
existing.status === "completed" ||
|
|
241
|
-
existing.status === "failed"
|
|
271
|
+
existing.status === "failed" ||
|
|
272
|
+
existing.status === "killed") {
|
|
242
273
|
return;
|
|
243
274
|
}
|
|
244
275
|
emitToolCallUpdate(session, toolUseId, { status: "in_progress" }, "progress");
|
|
@@ -249,7 +280,7 @@ export function emitToolSummaryUpdate(session, toolUseId, summary) {
|
|
|
249
280
|
return;
|
|
250
281
|
}
|
|
251
282
|
const fields = {
|
|
252
|
-
status: base.status === "failed"
|
|
283
|
+
status: base.status === "failed" || base.status === "killed" ? base.status : "completed",
|
|
253
284
|
raw_output: summary,
|
|
254
285
|
content: [{ type: "content", content: { type: "text", text: summary } }],
|
|
255
286
|
};
|
|
@@ -290,3 +321,60 @@ export function taskProgressText(msg) {
|
|
|
290
321
|
}
|
|
291
322
|
return description || lastTool;
|
|
292
323
|
}
|
|
324
|
+
function taskPatchStatus(value) {
|
|
325
|
+
switch (value) {
|
|
326
|
+
case "pending":
|
|
327
|
+
return "pending";
|
|
328
|
+
case "running":
|
|
329
|
+
return "in_progress";
|
|
330
|
+
case "completed":
|
|
331
|
+
return "completed";
|
|
332
|
+
case "failed":
|
|
333
|
+
return "failed";
|
|
334
|
+
case "killed":
|
|
335
|
+
return "killed";
|
|
336
|
+
default:
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function buildTaskMetadata(patch) {
|
|
341
|
+
const taskMetadata = {};
|
|
342
|
+
if (typeof patch.error === "string" && patch.error.length > 0) {
|
|
343
|
+
taskMetadata.error = patch.error;
|
|
344
|
+
}
|
|
345
|
+
if (typeof patch.is_backgrounded === "boolean") {
|
|
346
|
+
taskMetadata.is_backgrounded = patch.is_backgrounded;
|
|
347
|
+
}
|
|
348
|
+
if (typeof patch.end_time === "number" && Number.isFinite(patch.end_time) && patch.end_time >= 0) {
|
|
349
|
+
taskMetadata.end_time = Math.trunc(patch.end_time);
|
|
350
|
+
}
|
|
351
|
+
if (typeof patch.total_paused_ms === "number" &&
|
|
352
|
+
Number.isFinite(patch.total_paused_ms) &&
|
|
353
|
+
patch.total_paused_ms >= 0) {
|
|
354
|
+
taskMetadata.total_paused_ms = Math.trunc(patch.total_paused_ms);
|
|
355
|
+
}
|
|
356
|
+
return Object.keys(taskMetadata).length > 0 ? taskMetadata : undefined;
|
|
357
|
+
}
|
|
358
|
+
export function taskUpdatedFields(msg) {
|
|
359
|
+
const patch = msg.patch && typeof msg.patch === "object" ? msg.patch : {};
|
|
360
|
+
const fields = {};
|
|
361
|
+
const status = taskPatchStatus(patch.status);
|
|
362
|
+
const description = typeof patch.description === "string" ? patch.description : "";
|
|
363
|
+
const error = typeof patch.error === "string" ? patch.error : "";
|
|
364
|
+
if (status) {
|
|
365
|
+
fields.status = status;
|
|
366
|
+
}
|
|
367
|
+
if (description) {
|
|
368
|
+
fields.raw_output = description;
|
|
369
|
+
fields.content = [{ type: "content", content: { type: "text", text: description } }];
|
|
370
|
+
}
|
|
371
|
+
else if ((status === "failed" || status === "killed") && error) {
|
|
372
|
+
fields.raw_output = error;
|
|
373
|
+
fields.content = [{ type: "content", content: { type: "text", text: error } }];
|
|
374
|
+
}
|
|
375
|
+
const taskMetadata = buildTaskMetadata(patch);
|
|
376
|
+
if (taskMetadata) {
|
|
377
|
+
fields.task_metadata = taskMetadata;
|
|
378
|
+
}
|
|
379
|
+
return fields;
|
|
380
|
+
}
|
|
@@ -110,7 +110,7 @@ function editDiffContent(name, input) {
|
|
|
110
110
|
}
|
|
111
111
|
return [];
|
|
112
112
|
}
|
|
113
|
-
export function createToolCall(toolUseId, name, input) {
|
|
113
|
+
export function createToolCall(toolUseId, name, input, parentToolUseId = null) {
|
|
114
114
|
return {
|
|
115
115
|
tool_call_id: toolUseId,
|
|
116
116
|
title: toolTitle(name, input),
|
|
@@ -122,6 +122,7 @@ export function createToolCall(toolUseId, name, input) {
|
|
|
122
122
|
meta: {
|
|
123
123
|
claudeCode: {
|
|
124
124
|
toolName: name,
|
|
125
|
+
parentToolUseId,
|
|
125
126
|
},
|
|
126
127
|
},
|
|
127
128
|
};
|
|
@@ -134,18 +135,33 @@ function resultRecordCandidates(rawResult, rawContent) {
|
|
|
134
135
|
candidates.push(record);
|
|
135
136
|
}
|
|
136
137
|
};
|
|
138
|
+
const pushRecords = (value) => {
|
|
139
|
+
if (Array.isArray(value)) {
|
|
140
|
+
for (const entry of value) {
|
|
141
|
+
pushRecord(entry);
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
pushRecord(value);
|
|
146
|
+
};
|
|
137
147
|
const pushNestedRecords = (value) => {
|
|
148
|
+
if (Array.isArray(value)) {
|
|
149
|
+
for (const entry of value) {
|
|
150
|
+
pushNestedRecords(entry);
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
138
154
|
const record = asRecordOrNull(value);
|
|
139
155
|
if (!record) {
|
|
140
156
|
return;
|
|
141
157
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
158
|
+
pushRecords(record.result);
|
|
159
|
+
pushRecords(record.data);
|
|
160
|
+
pushRecords(record.content);
|
|
145
161
|
};
|
|
146
|
-
|
|
162
|
+
pushRecords(rawResult);
|
|
147
163
|
pushNestedRecords(rawResult);
|
|
148
|
-
|
|
164
|
+
pushRecords(rawContent);
|
|
149
165
|
pushNestedRecords(rawContent);
|
|
150
166
|
return candidates;
|
|
151
167
|
}
|
|
@@ -230,15 +246,9 @@ function extractToolOutputMetadata(toolName, rawResult, rawContent) {
|
|
|
230
246
|
if (toolName === "Bash") {
|
|
231
247
|
for (const candidate of candidates) {
|
|
232
248
|
const hasAssistantAutoBackgrounded = typeof candidate.assistantAutoBackgrounded === "boolean";
|
|
233
|
-
|
|
234
|
-
if (hasAssistantAutoBackgrounded || hasTokenSaverOutput) {
|
|
249
|
+
if (hasAssistantAutoBackgrounded) {
|
|
235
250
|
const bashMetadata = {};
|
|
236
|
-
|
|
237
|
-
bashMetadata.assistant_auto_backgrounded = candidate.assistantAutoBackgrounded;
|
|
238
|
-
}
|
|
239
|
-
if (hasTokenSaverOutput) {
|
|
240
|
-
bashMetadata.token_saver_active = true;
|
|
241
|
-
}
|
|
251
|
+
bashMetadata.assistant_auto_backgrounded = candidate.assistantAutoBackgrounded;
|
|
242
252
|
return {
|
|
243
253
|
bash: bashMetadata,
|
|
244
254
|
};
|
|
@@ -246,14 +256,6 @@ function extractToolOutputMetadata(toolName, rawResult, rawContent) {
|
|
|
246
256
|
}
|
|
247
257
|
return undefined;
|
|
248
258
|
}
|
|
249
|
-
if (toolName === "ExitPlanMode") {
|
|
250
|
-
for (const candidate of candidates) {
|
|
251
|
-
if (typeof candidate.isUltraplan === "boolean") {
|
|
252
|
-
return { exit_plan_mode: { is_ultraplan: candidate.isUltraplan } };
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
return undefined;
|
|
256
|
-
}
|
|
257
259
|
if (toolName === "TodoWrite") {
|
|
258
260
|
for (const candidate of candidates) {
|
|
259
261
|
if (typeof candidate.verificationNudgeNeeded === "boolean") {
|
|
@@ -476,8 +478,7 @@ function findBashResultRecord(rawResult, rawContent) {
|
|
|
476
478
|
"stderr" in candidate ||
|
|
477
479
|
"backgroundTaskId" in candidate ||
|
|
478
480
|
"backgroundedByUser" in candidate ||
|
|
479
|
-
"assistantAutoBackgrounded" in candidate
|
|
480
|
-
"tokenSaverOutput" in candidate);
|
|
481
|
+
"assistantAutoBackgrounded" in candidate);
|
|
481
482
|
}
|
|
482
483
|
function bashBackgroundMessage(record) {
|
|
483
484
|
const backgroundTaskId = typeof record.backgroundTaskId === "string" ? record.backgroundTaskId : "";
|
|
@@ -511,16 +512,48 @@ function buildBashDisplayOutput(record) {
|
|
|
511
512
|
}
|
|
512
513
|
return segments.join("\n");
|
|
513
514
|
}
|
|
515
|
+
function fileUnchangedResultText(rawResult, rawContent) {
|
|
516
|
+
for (const candidate of resultRecordCandidates(rawResult, rawContent)) {
|
|
517
|
+
if (candidate.type !== "file_unchanged") {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
const file = asRecordOrNull(candidate.file);
|
|
521
|
+
const filePath = typeof file?.filePath === "string" ? file.filePath.trim() : "";
|
|
522
|
+
if (filePath) {
|
|
523
|
+
return `File unchanged: ${filePath}`;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return "";
|
|
527
|
+
}
|
|
528
|
+
function agentTitleFromAgentOutput(rawResult, rawContent) {
|
|
529
|
+
for (const candidate of resultRecordCandidates(rawResult, rawContent)) {
|
|
530
|
+
const agentType = typeof candidate.agentType === "string" ? candidate.agentType.trim() : "";
|
|
531
|
+
if (agentType) {
|
|
532
|
+
return agentType;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return "";
|
|
536
|
+
}
|
|
514
537
|
export function buildToolResultFields(isError, rawContent, base, rawResult) {
|
|
515
538
|
const toolName = resolveToolName(base);
|
|
539
|
+
const fields = {
|
|
540
|
+
status: isError ? "failed" : "completed",
|
|
541
|
+
};
|
|
542
|
+
const fileUnchangedText = !isError && toolName === "Read" ? fileUnchangedResultText(rawResult, rawContent) : "";
|
|
543
|
+
if (fileUnchangedText) {
|
|
544
|
+
fields.raw_output = fileUnchangedText;
|
|
545
|
+
fields.content = [{ type: "content", content: { type: "text", text: fileUnchangedText } }];
|
|
546
|
+
return fields;
|
|
547
|
+
}
|
|
548
|
+
const agentTitle = !isError && toolName === "Agent" ? agentTitleFromAgentOutput(rawResult, rawContent) : "";
|
|
549
|
+
if (agentTitle) {
|
|
550
|
+
fields.title = agentTitle;
|
|
551
|
+
}
|
|
516
552
|
const bashResultRecord = toolName === "Bash" ? findBashResultRecord(rawResult, rawContent) : undefined;
|
|
517
553
|
const normalizedRawOutput = normalizeToolResultText(rawContent, isError);
|
|
518
554
|
const rawOutput = bashResultRecord
|
|
519
555
|
? buildBashDisplayOutput(bashResultRecord)
|
|
520
556
|
: normalizedRawOutput || JSON.stringify(rawContent);
|
|
521
|
-
const fields = {
|
|
522
|
-
status: isError ? "failed" : "completed",
|
|
523
|
-
};
|
|
524
557
|
if (rawOutput) {
|
|
525
558
|
fields.raw_output = rawOutput;
|
|
526
559
|
}
|
package/agent-sdk/dist/bridge.js
CHANGED
|
@@ -4,11 +4,12 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
import readline from "node:readline";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { getSessionMessages, listSessions, renameSession, } from "@anthropic-ai/claude-agent-sdk";
|
|
7
|
-
import { parseCommandEnvelope, toPermissionMode } from "./bridge/commands.js";
|
|
8
|
-
import { writeEvent, failConnection, slashError, emitSessionUpdate, emitSessionsList, currentSessionListOptions, setSessionListingDir, } from "./bridge/events.js";
|
|
7
|
+
import { buildModeState, markModeUnavailableForSession, parseCommandEnvelope, permissionModeFailureLooksUnsupported, refreshSupportedModesForSession, toPermissionMode, } from "./bridge/commands.js";
|
|
8
|
+
import { writeEvent, failConnection, slashError, emitRuntimeReloadCompleted, emitRuntimeReloadFailed, emitSessionUpdate, emitSessionsList, currentSessionListOptions, setSessionListingDir, } from "./bridge/events.js";
|
|
9
9
|
import { contentFromPrompt } from "./bridge/message_handlers.js";
|
|
10
|
-
import { sessions, sessionById, createSession, closeAllSessions, handleElicitationResponse, handlePermissionResponse, handleQuestionResponse, } from "./bridge/session_lifecycle.js";
|
|
10
|
+
import { sessions, sessionById, createSession, closeAllSessions, handleElicitationResponse, handlePermissionResponse, handleQuestionResponse, emitCurrentModelUpdate, refreshCurrentModel, shouldInvalidateResolvedRuntimeModel, } from "./bridge/session_lifecycle.js";
|
|
11
11
|
import { mapSessionMessagesToUpdates } from "./bridge/history.js";
|
|
12
|
+
import { emitAvailableAgentsIfChanged, mapAvailableAgents } from "./bridge/agents.js";
|
|
12
13
|
import { MCP_STALE_STATUS_REVALIDATION_COOLDOWN_MS, handleMcpAuthenticateCommand, handleMcpClearAuthCommand, handleMcpOauthCallbackUrlCommand, handleMcpReconnectCommand, handleMcpSetServersCommand, handleMcpStatusCommand, handleMcpToggleCommand, staleMcpAuthCandidates, } from "./bridge/mcp.js";
|
|
13
14
|
import { bridgeLogger, LOG_TARGETS, logBridgeCommandReceived } from "./bridge/logger.js";
|
|
14
15
|
// Re-exports: all symbols that tests and external consumers import from bridge.js.
|
|
@@ -21,10 +22,10 @@ export { parseCommandEnvelope } from "./bridge/commands.js";
|
|
|
21
22
|
export { buildSessionListOptions } from "./bridge/events.js";
|
|
22
23
|
export { permissionOptionsFromSuggestions, permissionResultFromOutcome, } from "./bridge/permissions.js";
|
|
23
24
|
export { mapSessionMessagesToUpdates, mapSdkSessions, } from "./bridge/history.js";
|
|
24
|
-
export { handleTaskSystemMessage } from "./bridge/message_handlers.js";
|
|
25
|
+
export { handleSdkMessage, handleTaskSystemMessage } from "./bridge/message_handlers.js";
|
|
25
26
|
export { mapAvailableAgents } from "./bridge/agents.js";
|
|
26
|
-
export { buildQueryOptions, mapAvailableModels } from "./bridge/session_lifecycle.js";
|
|
27
|
-
export { parseFastModeState, parseRateLimitStatus, buildRateLimitUpdate, } from "./bridge/state_parsing.js";
|
|
27
|
+
export { attachRequestUserDialogInterceptor, buildQueryOptions, mapAvailableModels, } from "./bridge/session_lifecycle.js";
|
|
28
|
+
export { parseFastModeState, parseRateLimitStatus, parseRuntimeSessionState, parseApiRetryError, buildRateLimitUpdate, buildApiRetryUpdate, normalizeSettingsParseError, normalizeSettingsParseErrors, } from "./bridge/state_parsing.js";
|
|
28
29
|
export { MCP_STALE_STATUS_REVALIDATION_COOLDOWN_MS, staleMcpAuthCandidates };
|
|
29
30
|
export function buildSessionMutationOptions(cwd) {
|
|
30
31
|
return cwd ? { dir: cwd } : undefined;
|
|
@@ -42,7 +43,7 @@ export async function generatePersistedSessionTitle(query, description) {
|
|
|
42
43
|
}
|
|
43
44
|
return title;
|
|
44
45
|
}
|
|
45
|
-
const EXPECTED_AGENT_SDK_VERSION = "0.2.
|
|
46
|
+
const EXPECTED_AGENT_SDK_VERSION = "0.2.104";
|
|
46
47
|
const require = createRequire(import.meta.url);
|
|
47
48
|
export function resolveInstalledAgentSdkVersion() {
|
|
48
49
|
try {
|
|
@@ -238,7 +239,7 @@ async function handleCommand(command, requestId) {
|
|
|
238
239
|
if (content.length === 0) {
|
|
239
240
|
return;
|
|
240
241
|
}
|
|
241
|
-
|
|
242
|
+
const message = {
|
|
242
243
|
type: "user",
|
|
243
244
|
session_id: session.sessionId,
|
|
244
245
|
parent_tool_use_id: null,
|
|
@@ -246,7 +247,8 @@ async function handleCommand(command, requestId) {
|
|
|
246
247
|
role: "user",
|
|
247
248
|
content,
|
|
248
249
|
},
|
|
249
|
-
}
|
|
250
|
+
};
|
|
251
|
+
session.input.enqueue(message);
|
|
250
252
|
return;
|
|
251
253
|
}
|
|
252
254
|
case "cancel_turn": {
|
|
@@ -264,13 +266,80 @@ async function handleCommand(command, requestId) {
|
|
|
264
266
|
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
265
267
|
return;
|
|
266
268
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
269
|
+
bridgeLogger.info({
|
|
270
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
271
|
+
eventName: "set_model_started",
|
|
272
|
+
message: "set model started",
|
|
273
|
+
outcome: "start",
|
|
274
|
+
sessionId: session.sessionId,
|
|
275
|
+
requestId,
|
|
276
|
+
fields: {
|
|
277
|
+
requested_model: command.model,
|
|
278
|
+
previous_requested_model: session.requestedModelId,
|
|
279
|
+
previous_session_model: session.model,
|
|
280
|
+
previous_resolved_runtime_model: session.resolvedRuntimeModelId,
|
|
281
|
+
previous_current_model: session.currentModel?.resolved_id,
|
|
282
|
+
},
|
|
273
283
|
});
|
|
284
|
+
try {
|
|
285
|
+
const previousRequestedModel = session.requestedModelId;
|
|
286
|
+
const previousSessionModel = session.model;
|
|
287
|
+
await session.query.setModel(command.model);
|
|
288
|
+
session.requestedModelId = command.model;
|
|
289
|
+
session.model = command.model;
|
|
290
|
+
const invalidatedResolvedRuntimeModel = shouldInvalidateResolvedRuntimeModel(previousRequestedModel, previousSessionModel, command.model);
|
|
291
|
+
if (invalidatedResolvedRuntimeModel) {
|
|
292
|
+
session.resolvedRuntimeModelId = undefined;
|
|
293
|
+
}
|
|
294
|
+
const changed = refreshCurrentModel(session, true);
|
|
295
|
+
const forcedCurrentModelUpdate = !changed && emitCurrentModelUpdate(session);
|
|
296
|
+
bridgeLogger.info({
|
|
297
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
298
|
+
eventName: "set_model_succeeded",
|
|
299
|
+
message: "set model completed",
|
|
300
|
+
outcome: "success",
|
|
301
|
+
sessionId: session.sessionId,
|
|
302
|
+
requestId,
|
|
303
|
+
fields: {
|
|
304
|
+
requested_model: command.model,
|
|
305
|
+
session_model_after: session.model,
|
|
306
|
+
resolved_runtime_model_after: session.resolvedRuntimeModelId,
|
|
307
|
+
current_model_after: session.currentModel?.resolved_id,
|
|
308
|
+
current_model_display_short: session.currentModel?.display_name_short,
|
|
309
|
+
current_model_display_long: session.currentModel?.display_name_long,
|
|
310
|
+
current_model_update_emitted: changed || forcedCurrentModelUpdate,
|
|
311
|
+
current_model_update_forced: forcedCurrentModelUpdate,
|
|
312
|
+
resolved_runtime_model_invalidated: invalidatedResolvedRuntimeModel,
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
refreshSupportedModesForSession(session);
|
|
316
|
+
if (session.mode) {
|
|
317
|
+
emitSessionUpdate(session.sessionId, {
|
|
318
|
+
type: "mode_state_update",
|
|
319
|
+
mode: buildModeState(session, session.mode),
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
325
|
+
bridgeLogger.warn({
|
|
326
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
327
|
+
eventName: "set_model_failed",
|
|
328
|
+
message: "set model failed",
|
|
329
|
+
outcome: "failure",
|
|
330
|
+
sessionId: session.sessionId,
|
|
331
|
+
requestId,
|
|
332
|
+
fields: {
|
|
333
|
+
requested_model: command.model,
|
|
334
|
+
error_message: message,
|
|
335
|
+
previous_requested_model: session.requestedModelId,
|
|
336
|
+
previous_session_model: session.model,
|
|
337
|
+
previous_resolved_runtime_model: session.resolvedRuntimeModelId,
|
|
338
|
+
previous_current_model: session.currentModel?.resolved_id,
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
slashError(command.session_id, `failed to set model: ${message}`, requestId);
|
|
342
|
+
}
|
|
274
343
|
return;
|
|
275
344
|
}
|
|
276
345
|
case "set_mode": {
|
|
@@ -284,12 +353,28 @@ async function handleCommand(command, requestId) {
|
|
|
284
353
|
slashError(command.session_id, `unsupported mode: ${command.mode}`, requestId);
|
|
285
354
|
return;
|
|
286
355
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
356
|
+
try {
|
|
357
|
+
await session.query.setPermissionMode(mode);
|
|
358
|
+
session.mode = mode;
|
|
359
|
+
refreshSupportedModesForSession(session);
|
|
360
|
+
emitSessionUpdate(session.sessionId, {
|
|
361
|
+
type: "current_mode_update",
|
|
362
|
+
current_mode_id: mode,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
367
|
+
if (permissionModeFailureLooksUnsupported(mode, message)) {
|
|
368
|
+
const changed = markModeUnavailableForSession(session, mode);
|
|
369
|
+
if (changed && session.mode) {
|
|
370
|
+
emitSessionUpdate(session.sessionId, {
|
|
371
|
+
type: "mode_state_update",
|
|
372
|
+
mode: buildModeState(session, session.mode),
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
slashError(command.session_id, `failed to set mode to ${mode}: ${message}`, requestId);
|
|
377
|
+
}
|
|
293
378
|
return;
|
|
294
379
|
}
|
|
295
380
|
case "generate_session_title": {
|
|
@@ -347,6 +432,7 @@ async function handleCommand(command, requestId) {
|
|
|
347
432
|
subscription_type: account.subscriptionType,
|
|
348
433
|
token_source: account.tokenSource,
|
|
349
434
|
api_key_source: account.apiKeySource,
|
|
435
|
+
api_provider: account.apiProvider,
|
|
350
436
|
},
|
|
351
437
|
});
|
|
352
438
|
writeEvent({
|
|
@@ -358,6 +444,7 @@ async function handleCommand(command, requestId) {
|
|
|
358
444
|
subscription_type: account.subscriptionType,
|
|
359
445
|
token_source: account.tokenSource,
|
|
360
446
|
api_key_source: account.apiKeySource,
|
|
447
|
+
api_provider: account.apiProvider,
|
|
361
448
|
},
|
|
362
449
|
}, requestId);
|
|
363
450
|
}
|
|
@@ -376,6 +463,100 @@ async function handleCommand(command, requestId) {
|
|
|
376
463
|
}
|
|
377
464
|
return;
|
|
378
465
|
}
|
|
466
|
+
case "get_context_usage": {
|
|
467
|
+
const session = sessionById(command.session_id);
|
|
468
|
+
if (!session) {
|
|
469
|
+
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
try {
|
|
473
|
+
const usage = await session.query.getContextUsage();
|
|
474
|
+
if (typeof usage.model === "string" && usage.model.trim().length > 0) {
|
|
475
|
+
session.resolvedRuntimeModelId = usage.model.trim();
|
|
476
|
+
refreshCurrentModel(session, true);
|
|
477
|
+
}
|
|
478
|
+
const rawPercentage = typeof usage.percentage === "number" ? usage.percentage : undefined;
|
|
479
|
+
const normalizedPercentage = rawPercentage === undefined || !Number.isFinite(rawPercentage)
|
|
480
|
+
? undefined
|
|
481
|
+
: Math.max(0, Math.min(100, Math.round(rawPercentage)));
|
|
482
|
+
bridgeLogger.debug({
|
|
483
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
484
|
+
eventName: "context_usage_succeeded",
|
|
485
|
+
message: "session context usage received from SDK",
|
|
486
|
+
outcome: "success",
|
|
487
|
+
...(requestId ? { requestId } : {}),
|
|
488
|
+
sessionId: session.sessionId,
|
|
489
|
+
fields: {
|
|
490
|
+
raw_percentage: rawPercentage,
|
|
491
|
+
normalized_percentage: normalizedPercentage,
|
|
492
|
+
total_tokens: typeof usage.totalTokens === "number" ? usage.totalTokens : undefined,
|
|
493
|
+
max_tokens: typeof usage.maxTokens === "number" ? usage.maxTokens : undefined,
|
|
494
|
+
raw_max_tokens: typeof usage.rawMaxTokens === "number" ? usage.rawMaxTokens : undefined,
|
|
495
|
+
model: typeof usage.model === "string" ? usage.model : undefined,
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
writeEvent({
|
|
499
|
+
event: "context_usage",
|
|
500
|
+
session_id: session.sessionId,
|
|
501
|
+
...(normalizedPercentage !== undefined ? { percentage: normalizedPercentage } : {}),
|
|
502
|
+
}, requestId);
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
506
|
+
bridgeLogger.warn({
|
|
507
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
508
|
+
eventName: "context_usage_failed",
|
|
509
|
+
message: "failed to get session context usage",
|
|
510
|
+
outcome: "failure",
|
|
511
|
+
...(requestId ? { requestId } : {}),
|
|
512
|
+
sessionId: session.sessionId,
|
|
513
|
+
fields: { error_message: message },
|
|
514
|
+
});
|
|
515
|
+
writeEvent({
|
|
516
|
+
event: "context_usage",
|
|
517
|
+
session_id: session.sessionId,
|
|
518
|
+
}, requestId);
|
|
519
|
+
}
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
case "reload_plugins": {
|
|
523
|
+
const session = sessionById(command.session_id);
|
|
524
|
+
if (!session) {
|
|
525
|
+
slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
const result = await session.query.reloadPlugins();
|
|
530
|
+
const commands = Array.isArray(result.commands)
|
|
531
|
+
? result.commands.map((entry) => ({
|
|
532
|
+
name: entry.name,
|
|
533
|
+
description: entry.description ?? "",
|
|
534
|
+
input_hint: entry.argumentHint ?? undefined,
|
|
535
|
+
}))
|
|
536
|
+
: [];
|
|
537
|
+
emitSessionUpdate(session.sessionId, {
|
|
538
|
+
type: "available_commands_update",
|
|
539
|
+
commands,
|
|
540
|
+
});
|
|
541
|
+
emitAvailableAgentsIfChanged(session, mapAvailableAgents(result.agents));
|
|
542
|
+
await handleMcpStatusCommand(session, requestId);
|
|
543
|
+
emitRuntimeReloadCompleted(session.sessionId, requestId);
|
|
544
|
+
}
|
|
545
|
+
catch (error) {
|
|
546
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
547
|
+
bridgeLogger.warn({
|
|
548
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
549
|
+
eventName: "reload_plugins_failed",
|
|
550
|
+
message: "failed to reload session plugins",
|
|
551
|
+
outcome: "failure",
|
|
552
|
+
...(requestId ? { requestId } : {}),
|
|
553
|
+
sessionId: session.sessionId,
|
|
554
|
+
fields: { error_message: message },
|
|
555
|
+
});
|
|
556
|
+
emitRuntimeReloadFailed(session.sessionId, message, requestId);
|
|
557
|
+
}
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
379
560
|
case "mcp_status": {
|
|
380
561
|
const session = sessionById(command.session_id);
|
|
381
562
|
if (!session) {
|
|
@@ -465,6 +646,7 @@ async function handleCommand(command, requestId) {
|
|
|
465
646
|
...(requestId ? { requestId } : {}),
|
|
466
647
|
});
|
|
467
648
|
process.exit(0);
|
|
649
|
+
return;
|
|
468
650
|
default:
|
|
469
651
|
bridgeLogger.error({
|
|
470
652
|
target: LOG_TARGETS.BRIDGE_PROTOCOL,
|