claude-code-rust 0.9.0 → 0.10.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 +9 -3
- package/agent-sdk/dist/bridge/events.js +116 -8
- package/agent-sdk/dist/bridge/logger.js +245 -0
- package/agent-sdk/dist/bridge/mcp.js +81 -5
- package/agent-sdk/dist/bridge/message_handlers.js +87 -48
- package/agent-sdk/dist/bridge/session_lifecycle.js +385 -51
- package/agent-sdk/dist/bridge/shared.js +0 -7
- package/agent-sdk/dist/bridge/tool_calls.js +174 -59
- package/agent-sdk/dist/bridge/user_interaction.js +34 -23
- package/agent-sdk/dist/bridge.js +203 -21
- package/package.json +1 -1
|
@@ -1,13 +1,177 @@
|
|
|
1
1
|
import { emitSessionUpdate } from "./events.js";
|
|
2
|
+
import { bridgeLogger, LOG_TARGETS } from "./logger.js";
|
|
2
3
|
import { buildToolResultFields, createToolCall } from "./tooling.js";
|
|
4
|
+
function jsonSize(value) {
|
|
5
|
+
if (value === undefined) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
return Buffer.byteLength(JSON.stringify(value));
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function toolNameFromMeta(meta) {
|
|
16
|
+
if (!meta || typeof meta !== "object") {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const claudeCode = "claudeCode" in meta && meta.claudeCode && typeof meta.claudeCode === "object" ? meta.claudeCode : undefined;
|
|
20
|
+
const toolName = claudeCode && "toolName" in claudeCode && typeof claudeCode.toolName === "string" ? claudeCode.toolName : "";
|
|
21
|
+
return toolName || undefined;
|
|
22
|
+
}
|
|
23
|
+
function toolName(base, fields) {
|
|
24
|
+
const fieldMeta = fields?.meta;
|
|
25
|
+
if (fieldMeta && typeof fieldMeta === "object") {
|
|
26
|
+
const fromFields = toolNameFromMeta(fieldMeta);
|
|
27
|
+
if (fromFields) {
|
|
28
|
+
return fromFields;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return toolNameFromMeta(base?.meta);
|
|
32
|
+
}
|
|
33
|
+
function classifyFailureKind(rawOutput) {
|
|
34
|
+
if (!rawOutput) {
|
|
35
|
+
return "failed";
|
|
36
|
+
}
|
|
37
|
+
const normalized = rawOutput.trim().toLowerCase();
|
|
38
|
+
if (normalized.includes("permission denied") ||
|
|
39
|
+
normalized.includes("cancelled by user") ||
|
|
40
|
+
normalized.includes("plan rejected") ||
|
|
41
|
+
normalized.includes("question cancelled")) {
|
|
42
|
+
return "refused";
|
|
43
|
+
}
|
|
44
|
+
if (normalized.includes("timed out") || normalized.includes("timeout")) {
|
|
45
|
+
return "timeout";
|
|
46
|
+
}
|
|
47
|
+
return "failed";
|
|
48
|
+
}
|
|
49
|
+
function updateOutcome(status) {
|
|
50
|
+
switch (status) {
|
|
51
|
+
case "completed":
|
|
52
|
+
return "success";
|
|
53
|
+
case "failed":
|
|
54
|
+
return "failure";
|
|
55
|
+
case "in_progress":
|
|
56
|
+
return "partial";
|
|
57
|
+
case "pending":
|
|
58
|
+
return "start";
|
|
59
|
+
default:
|
|
60
|
+
return "partial";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function applyFieldsToBase(base, fields) {
|
|
64
|
+
if (fields.title !== undefined) {
|
|
65
|
+
base.title = fields.title;
|
|
66
|
+
}
|
|
67
|
+
if (fields.kind !== undefined) {
|
|
68
|
+
base.kind = fields.kind;
|
|
69
|
+
}
|
|
70
|
+
if (fields.status !== undefined) {
|
|
71
|
+
base.status = fields.status;
|
|
72
|
+
}
|
|
73
|
+
if (fields.raw_input !== undefined) {
|
|
74
|
+
base.raw_input = fields.raw_input;
|
|
75
|
+
}
|
|
76
|
+
if (fields.raw_output !== undefined) {
|
|
77
|
+
base.raw_output = fields.raw_output;
|
|
78
|
+
}
|
|
79
|
+
if (fields.locations !== undefined) {
|
|
80
|
+
base.locations = fields.locations;
|
|
81
|
+
}
|
|
82
|
+
if (fields.output_metadata !== undefined) {
|
|
83
|
+
base.output_metadata = fields.output_metadata;
|
|
84
|
+
}
|
|
85
|
+
if (fields.meta !== undefined) {
|
|
86
|
+
base.meta = fields.meta;
|
|
87
|
+
}
|
|
88
|
+
if (fields.content !== undefined) {
|
|
89
|
+
base.content = fields.content;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function logToolCallSubmitted(sessionId, toolCall, updateKind) {
|
|
93
|
+
bridgeLogger.info({
|
|
94
|
+
target: LOG_TARGETS.APP_TOOL,
|
|
95
|
+
eventName: "tool_call_submitted",
|
|
96
|
+
message: "tool call submitted",
|
|
97
|
+
outcome: "success",
|
|
98
|
+
sessionId,
|
|
99
|
+
toolCallId: toolCall.tool_call_id,
|
|
100
|
+
sizeBytes: jsonSize(toolCall.raw_input),
|
|
101
|
+
fields: {
|
|
102
|
+
submission_kind: updateKind,
|
|
103
|
+
tool_name: toolNameFromMeta(toolCall.meta),
|
|
104
|
+
tool_title: toolCall.title,
|
|
105
|
+
tool_kind: toolCall.kind,
|
|
106
|
+
status: toolCall.status,
|
|
107
|
+
content_block_count: toolCall.content.length,
|
|
108
|
+
location_count: toolCall.locations.length,
|
|
109
|
+
has_output_metadata: toolCall.output_metadata !== undefined,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function logToolCallUpdateEmitted(sessionId, toolUseId, fields, base, updateKind) {
|
|
114
|
+
const nextStatus = fields.status ?? base?.status;
|
|
115
|
+
const rawOutput = typeof fields.raw_output === "string" ? fields.raw_output : base?.raw_output;
|
|
116
|
+
const failureKind = nextStatus === "failed" ? classifyFailureKind(rawOutput) : undefined;
|
|
117
|
+
const commonEvent = {
|
|
118
|
+
target: LOG_TARGETS.APP_TOOL,
|
|
119
|
+
eventName: "tool_call_update_emitted",
|
|
120
|
+
message: "tool call update emitted",
|
|
121
|
+
outcome: updateOutcome(nextStatus),
|
|
122
|
+
sessionId,
|
|
123
|
+
toolCallId: toolUseId,
|
|
124
|
+
sizeBytes: jsonSize(fields.raw_input),
|
|
125
|
+
fields: {
|
|
126
|
+
update_kind: updateKind,
|
|
127
|
+
tool_name: toolName(base, fields),
|
|
128
|
+
previous_status: base?.status,
|
|
129
|
+
next_status: nextStatus,
|
|
130
|
+
title_changed: fields.title !== undefined && fields.title !== base?.title,
|
|
131
|
+
content_block_count: fields.content?.length,
|
|
132
|
+
location_count: fields.locations?.length,
|
|
133
|
+
raw_output_chars: rawOutput?.length,
|
|
134
|
+
has_output_metadata: fields.output_metadata !== undefined || base?.output_metadata !== undefined,
|
|
135
|
+
failure_kind: failureKind,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
if (nextStatus === "failed") {
|
|
139
|
+
bridgeLogger.warn(commonEvent);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (nextStatus === "completed") {
|
|
143
|
+
bridgeLogger.info(commonEvent);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (nextStatus === "in_progress" || nextStatus === "pending") {
|
|
147
|
+
bridgeLogger.debug(commonEvent);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
bridgeLogger.debug(commonEvent);
|
|
151
|
+
}
|
|
152
|
+
function emitInitialToolCall(session, toolCall, updateKind = "initial") {
|
|
153
|
+
session.toolCalls.set(toolCall.tool_call_id, toolCall);
|
|
154
|
+
logToolCallSubmitted(session.sessionId, toolCall, updateKind);
|
|
155
|
+
emitSessionUpdate(session.sessionId, { type: "tool_call", tool_call: toolCall });
|
|
156
|
+
}
|
|
157
|
+
export function emitToolCallUpdate(session, toolUseId, fields, updateKind) {
|
|
158
|
+
const base = session.toolCalls.get(toolUseId);
|
|
159
|
+
logToolCallUpdateEmitted(session.sessionId, toolUseId, fields, base, updateKind);
|
|
160
|
+
emitSessionUpdate(session.sessionId, {
|
|
161
|
+
type: "tool_call_update",
|
|
162
|
+
tool_call_update: { tool_call_id: toolUseId, fields },
|
|
163
|
+
});
|
|
164
|
+
if (base) {
|
|
165
|
+
applyFieldsToBase(base, fields);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
3
168
|
export function emitToolCall(session, toolUseId, name, input) {
|
|
4
169
|
const toolCall = createToolCall(toolUseId, name, input);
|
|
5
170
|
const status = "in_progress";
|
|
6
171
|
toolCall.status = status;
|
|
7
172
|
const existing = session.toolCalls.get(toolUseId);
|
|
8
173
|
if (!existing) {
|
|
9
|
-
session
|
|
10
|
-
emitSessionUpdate(session.sessionId, { type: "tool_call", tool_call: toolCall });
|
|
174
|
+
emitInitialToolCall(session, toolCall);
|
|
11
175
|
return;
|
|
12
176
|
}
|
|
13
177
|
const fields = {
|
|
@@ -21,19 +185,7 @@ export function emitToolCall(session, toolUseId, name, input) {
|
|
|
21
185
|
if (toolCall.content.length > 0) {
|
|
22
186
|
fields.content = toolCall.content;
|
|
23
187
|
}
|
|
24
|
-
|
|
25
|
-
type: "tool_call_update",
|
|
26
|
-
tool_call_update: { tool_call_id: toolUseId, fields },
|
|
27
|
-
});
|
|
28
|
-
existing.title = toolCall.title;
|
|
29
|
-
existing.kind = toolCall.kind;
|
|
30
|
-
existing.status = status;
|
|
31
|
-
existing.raw_input = toolCall.raw_input;
|
|
32
|
-
existing.locations = toolCall.locations;
|
|
33
|
-
existing.meta = toolCall.meta;
|
|
34
|
-
if (toolCall.content.length > 0) {
|
|
35
|
-
existing.content = toolCall.content;
|
|
36
|
-
}
|
|
188
|
+
emitToolCallUpdate(session, toolUseId, fields, "refresh");
|
|
37
189
|
}
|
|
38
190
|
export function ensureToolCallVisible(session, toolUseId, toolName, input) {
|
|
39
191
|
const existing = session.toolCalls.get(toolUseId);
|
|
@@ -41,8 +193,7 @@ export function ensureToolCallVisible(session, toolUseId, toolName, input) {
|
|
|
41
193
|
return existing;
|
|
42
194
|
}
|
|
43
195
|
const toolCall = createToolCall(toolUseId, toolName, input);
|
|
44
|
-
session
|
|
45
|
-
emitSessionUpdate(session.sessionId, { type: "tool_call", tool_call: toolCall });
|
|
196
|
+
emitInitialToolCall(session, toolCall);
|
|
46
197
|
return toolCall;
|
|
47
198
|
}
|
|
48
199
|
export function emitPlanIfTodoWrite(session, name, input) {
|
|
@@ -68,34 +219,15 @@ export function emitPlanIfTodoWrite(session, name, input) {
|
|
|
68
219
|
}
|
|
69
220
|
}
|
|
70
221
|
export function emitToolResultUpdate(session, toolUseId, isError, rawContent, rawResult = rawContent) {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
const update = { tool_call_id: toolUseId, fields };
|
|
74
|
-
emitSessionUpdate(session.sessionId, { type: "tool_call_update", tool_call_update: update });
|
|
75
|
-
if (base) {
|
|
76
|
-
base.status = fields.status ?? base.status;
|
|
77
|
-
if (fields.raw_output) {
|
|
78
|
-
base.raw_output = fields.raw_output;
|
|
79
|
-
}
|
|
80
|
-
if (fields.content) {
|
|
81
|
-
base.content = fields.content;
|
|
82
|
-
}
|
|
83
|
-
if (fields.output_metadata) {
|
|
84
|
-
base.output_metadata = fields.output_metadata;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
222
|
+
const fields = buildToolResultFields(isError, rawContent, session.toolCalls.get(toolUseId), rawResult);
|
|
223
|
+
emitToolCallUpdate(session, toolUseId, fields, "result");
|
|
87
224
|
}
|
|
88
225
|
export function finalizeOpenToolCalls(session, status) {
|
|
89
226
|
for (const [toolUseId, toolCall] of session.toolCalls) {
|
|
90
227
|
if (toolCall.status !== "pending" && toolCall.status !== "in_progress") {
|
|
91
228
|
continue;
|
|
92
229
|
}
|
|
93
|
-
|
|
94
|
-
emitSessionUpdate(session.sessionId, {
|
|
95
|
-
type: "tool_call_update",
|
|
96
|
-
tool_call_update: { tool_call_id: toolUseId, fields },
|
|
97
|
-
});
|
|
98
|
-
toolCall.status = status;
|
|
230
|
+
emitToolCallUpdate(session, toolUseId, { status }, "finalize");
|
|
99
231
|
}
|
|
100
232
|
}
|
|
101
233
|
export function emitToolProgressUpdate(session, toolUseId, toolName) {
|
|
@@ -109,12 +241,7 @@ export function emitToolProgressUpdate(session, toolUseId, toolName) {
|
|
|
109
241
|
existing.status === "failed") {
|
|
110
242
|
return;
|
|
111
243
|
}
|
|
112
|
-
|
|
113
|
-
emitSessionUpdate(session.sessionId, {
|
|
114
|
-
type: "tool_call_update",
|
|
115
|
-
tool_call_update: { tool_call_id: toolUseId, fields },
|
|
116
|
-
});
|
|
117
|
-
existing.status = "in_progress";
|
|
244
|
+
emitToolCallUpdate(session, toolUseId, { status: "in_progress" }, "progress");
|
|
118
245
|
}
|
|
119
246
|
export function emitToolSummaryUpdate(session, toolUseId, summary) {
|
|
120
247
|
const base = session.toolCalls.get(toolUseId);
|
|
@@ -126,12 +253,7 @@ export function emitToolSummaryUpdate(session, toolUseId, summary) {
|
|
|
126
253
|
raw_output: summary,
|
|
127
254
|
content: [{ type: "content", content: { type: "text", text: summary } }],
|
|
128
255
|
};
|
|
129
|
-
|
|
130
|
-
type: "tool_call_update",
|
|
131
|
-
tool_call_update: { tool_call_id: toolUseId, fields },
|
|
132
|
-
});
|
|
133
|
-
base.status = fields.status ?? base.status;
|
|
134
|
-
base.raw_output = summary;
|
|
256
|
+
emitToolCallUpdate(session, toolUseId, fields, "summary");
|
|
135
257
|
}
|
|
136
258
|
export function setToolCallStatus(session, toolUseId, status, message) {
|
|
137
259
|
const base = session.toolCalls.get(toolUseId);
|
|
@@ -143,14 +265,7 @@ export function setToolCallStatus(session, toolUseId, status, message) {
|
|
|
143
265
|
fields.raw_output = message;
|
|
144
266
|
fields.content = [{ type: "content", content: { type: "text", text: message } }];
|
|
145
267
|
}
|
|
146
|
-
|
|
147
|
-
type: "tool_call_update",
|
|
148
|
-
tool_call_update: { tool_call_id: toolUseId, fields },
|
|
149
|
-
});
|
|
150
|
-
base.status = status;
|
|
151
|
-
if (fields.raw_output) {
|
|
152
|
-
base.raw_output = fields.raw_output;
|
|
153
|
-
}
|
|
268
|
+
emitToolCallUpdate(session, toolUseId, fields, "status");
|
|
154
269
|
}
|
|
155
270
|
export function resolveTaskToolUseId(session, msg) {
|
|
156
271
|
const direct = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { asRecordOrNull } from "./shared.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { emitPermissionRequestEvent, emitQuestionRequestEvent, } from "./events.js";
|
|
3
|
+
import { bridgeLogger, LOG_TARGETS } from "./logger.js";
|
|
4
|
+
import { emitToolCallUpdate, setToolCallStatus } from "./tool_calls.js";
|
|
4
5
|
export const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
|
|
5
6
|
export const QUESTION_CHOICE_KIND = "question_choice";
|
|
6
7
|
export const EXIT_PLAN_MODE_TOOL_NAME = "ExitPlanMode";
|
|
@@ -31,7 +32,20 @@ export async function requestExitPlanModeApproval(session, toolUseId, inputData,
|
|
|
31
32
|
toolName: EXIT_PLAN_MODE_TOOL_NAME,
|
|
32
33
|
inputData,
|
|
33
34
|
});
|
|
34
|
-
|
|
35
|
+
bridgeLogger.info({
|
|
36
|
+
target: LOG_TARGETS.BRIDGE_PERMISSION,
|
|
37
|
+
eventName: "permission_request_created",
|
|
38
|
+
message: "exit plan mode permission request created",
|
|
39
|
+
outcome: "start",
|
|
40
|
+
sessionId: session.sessionId,
|
|
41
|
+
toolCallId: toolUseId,
|
|
42
|
+
count: request.options.length,
|
|
43
|
+
fields: {
|
|
44
|
+
tool_name: EXIT_PLAN_MODE_TOOL_NAME,
|
|
45
|
+
option_count: request.options.length,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
emitPermissionRequestEvent(session.sessionId, request);
|
|
35
49
|
});
|
|
36
50
|
if (outcome.outcome !== "selected" || outcome.option_id === "reject") {
|
|
37
51
|
setToolCallStatus(session, toolUseId, "failed", "Plan rejected");
|
|
@@ -159,16 +173,7 @@ export async function requestAskUserQuestionAnswers(session, toolUseId, inputDat
|
|
|
159
173
|
status: "in_progress",
|
|
160
174
|
raw_input: promptToolCall.raw_input,
|
|
161
175
|
};
|
|
162
|
-
|
|
163
|
-
type: "tool_call_update",
|
|
164
|
-
tool_call_update: { tool_call_id: toolUseId, fields },
|
|
165
|
-
});
|
|
166
|
-
const tracked = session.toolCalls.get(toolUseId);
|
|
167
|
-
if (tracked) {
|
|
168
|
-
tracked.title = promptToolCall.title;
|
|
169
|
-
tracked.status = "in_progress";
|
|
170
|
-
tracked.raw_input = promptToolCall.raw_input;
|
|
171
|
-
}
|
|
176
|
+
emitToolCallUpdate(session, toolUseId, fields, "status");
|
|
172
177
|
const request = buildQuestionRequest(promptToolCall, prompt, index, prompts.length);
|
|
173
178
|
const outcome = await new Promise((resolve) => {
|
|
174
179
|
session.pendingQuestions.set(toolUseId, {
|
|
@@ -176,7 +181,21 @@ export async function requestAskUserQuestionAnswers(session, toolUseId, inputDat
|
|
|
176
181
|
toolName: ASK_USER_QUESTION_TOOL_NAME,
|
|
177
182
|
inputData,
|
|
178
183
|
});
|
|
179
|
-
|
|
184
|
+
bridgeLogger.info({
|
|
185
|
+
target: LOG_TARGETS.BRIDGE_PERMISSION,
|
|
186
|
+
eventName: "question_request_created",
|
|
187
|
+
message: "ask user question request created",
|
|
188
|
+
outcome: "start",
|
|
189
|
+
sessionId: session.sessionId,
|
|
190
|
+
toolCallId: toolUseId,
|
|
191
|
+
count: request.prompt.options.length,
|
|
192
|
+
fields: {
|
|
193
|
+
tool_name: ASK_USER_QUESTION_TOOL_NAME,
|
|
194
|
+
question_index: index,
|
|
195
|
+
total_questions: prompts.length,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
emitQuestionRequestEvent(session.sessionId, request);
|
|
180
199
|
});
|
|
181
200
|
if (outcome.outcome !== "answered") {
|
|
182
201
|
setToolCallStatus(session, toolUseId, "failed", "Question cancelled");
|
|
@@ -201,15 +220,7 @@ export async function requestAskUserQuestionAnswers(session, toolUseId, inputDat
|
|
|
201
220
|
raw_output: summary,
|
|
202
221
|
content: [{ type: "content", content: { type: "text", text: summary } }],
|
|
203
222
|
};
|
|
204
|
-
|
|
205
|
-
type: "tool_call_update",
|
|
206
|
-
tool_call_update: { tool_call_id: toolUseId, fields: progressFields },
|
|
207
|
-
});
|
|
208
|
-
if (tracked) {
|
|
209
|
-
tracked.status = progressFields.status ?? tracked.status;
|
|
210
|
-
tracked.raw_output = summary;
|
|
211
|
-
tracked.content = progressFields.content ?? tracked.content;
|
|
212
|
-
}
|
|
223
|
+
emitToolCallUpdate(session, toolUseId, progressFields, "summary");
|
|
213
224
|
}
|
|
214
225
|
return {
|
|
215
226
|
behavior: "allow",
|