claude-code-rust 0.11.3 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -8
- package/agent-sdk/dist/bridge/history.js +15 -5
- package/agent-sdk/dist/bridge/message_handlers.js +48 -2
- package/agent-sdk/dist/bridge/session_lifecycle.js +15 -5
- package/agent-sdk/dist/bridge/state_parsing.js +8 -1
- package/agent-sdk/dist/bridge/tool_calls.js +13 -0
- package/agent-sdk/dist/bridge/tooling.js +7 -0
- package/agent-sdk/dist/bridge.js +1 -1
- package/agent-sdk/dist/bridge.test.js +240 -7
- package/package.json +5 -2
- package/scripts/postinstall.js +48 -0
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ A native Rust terminal interface for Claude Code. Drop-in replacement for Anthro
|
|
|
5
5
|
[](https://www.npmjs.com/package/claude-code-rust)
|
|
6
6
|
[](https://www.npmjs.com/package/claude-code-rust)
|
|
7
7
|
[](https://github.com/srothgan/claude-code-rust/actions/workflows/ci.yml)
|
|
8
|
+
[](https://srothgan.github.io/claude-code-rust/)
|
|
8
9
|
[](https://www.apache.org/licenses/LICENSE-2.0)
|
|
9
10
|
[](https://nodejs.org/)
|
|
10
11
|
|
|
@@ -35,6 +36,15 @@ If `claude-rs` resolves to an older global shim, ensure your npm global bin dire
|
|
|
35
36
|
claude-rs
|
|
36
37
|
```
|
|
37
38
|
|
|
39
|
+
Full documentation is available at [srothgan.github.io/claude-code-rust](https://srothgan.github.io/claude-code-rust/).
|
|
40
|
+
|
|
41
|
+
> [!WARNING]
|
|
42
|
+
> **Agent SDK billing changes on June 15, 2026.** Anthropic says Agent SDK usage, `claude -p`, Claude Code GitHub Actions, and third-party Agent SDK apps will use a separate monthly Agent SDK credit instead of normal interactive Claude or Claude Code subscription limits. Because Claude Code Rust wraps the Agent SDK, treat usage through this project as Agent SDK usage. If that credit is exhausted, continued use may require enabling extra usage billed at standard API rates, or requests may pause until the credit refreshes.
|
|
43
|
+
>
|
|
44
|
+
> Sources:
|
|
45
|
+
> - [Anthropic support: Use the Claude Agent SDK with your Claude plan](https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan)
|
|
46
|
+
> - [ClaudeDevs announcement](https://x.com/ClaudeDevs/status/2054610152817619388)
|
|
47
|
+
|
|
38
48
|
## Why
|
|
39
49
|
|
|
40
50
|
The stock Claude Code TUI runs on Node.js with React Ink. This causes real problems:
|
|
@@ -47,15 +57,15 @@ The stock Claude Code TUI runs on Node.js with React Ink. This causes real probl
|
|
|
47
57
|
|
|
48
58
|
Claude Code Rust fixes all of these by compiling to a single native binary with direct terminal control via Crossterm.
|
|
49
59
|
|
|
50
|
-
##
|
|
51
|
-
|
|
52
|
-
Three-layer design:
|
|
53
|
-
|
|
54
|
-
**Presentation** (Rust/Ratatui) - Single binary with an async event loop (Tokio) handling keyboard input and bridge client events concurrently. Virtual-scrolled chat history with syntax-highlighted code blocks.
|
|
60
|
+
## Documentation
|
|
55
61
|
|
|
56
|
-
|
|
62
|
+
The manual covers installation from npm and source, help, slash commands, keyboard shortcuts, settings, diagnostics, architecture, and the changelog:
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
- [Installation](https://srothgan.github.io/claude-code-rust/installation.html)
|
|
65
|
+
- [Usage](https://srothgan.github.io/claude-code-rust/usage.html)
|
|
66
|
+
- [Help](https://srothgan.github.io/claude-code-rust/help.html)
|
|
67
|
+
- [Slash commands](https://srothgan.github.io/claude-code-rust/commands.html)
|
|
68
|
+
- [Settings](https://srothgan.github.io/claude-code-rust/settings.html)
|
|
59
69
|
|
|
60
70
|
## Status
|
|
61
71
|
|
|
@@ -75,7 +85,7 @@ This project is not affiliated with, endorsed by, or supported by Anthropic.
|
|
|
75
85
|
|
|
76
86
|
A quick note on where this project stands, since I know people worry about this kind of thing: claude-code-rust is a terminal UI that I wrote from scratch in Rust. It is not a fork, copy or port of the latest Claude Code source leak -- it talks to Anthropic's official [Agent SDK](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/agent-sdk) as a runtime dependency instead, the same way any other third-party tool would. No Anthropic source code was read or used as reference at any point during development.
|
|
77
87
|
|
|
78
|
-
The project
|
|
88
|
+
The project authenticates through your existing Claude Code account via the Agent SDK, and the Agent SDK's terms allow building on top of it. Billing, credits, limits, and overage behavior are controlled by Anthropic, including the Agent SDK credit change noted above. Other community projects do the same. As far as I can tell, using this project is fine -- but I am a single maintainer, not a lawyer. If anything changes on Anthropic's end, I will update this section and adjust the project accordingly.
|
|
79
89
|
|
|
80
90
|
This project's source code is licensed under [Apache-2.0](LICENSE). The Agent SDK itself is proprietary and governed by [Anthropic's Commercial Terms of Service](https://www.anthropic.com/legal/commercial-terms).
|
|
81
91
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { asRecordOrNull } from "./shared.js";
|
|
2
|
-
import { TOOL_RESULT_TYPES, buildToolResultFields, createToolCall, isToolUseBlockType } from "./tooling.js";
|
|
2
|
+
import { TOOL_RESULT_TYPES, buildToolResultFields, createToolCall, isToolSearchToolName, isToolSearchToolResultType, isToolUseBlockType, } from "./tooling.js";
|
|
3
3
|
function nonEmptyTrimmed(value) {
|
|
4
4
|
if (typeof value !== "string") {
|
|
5
5
|
return undefined;
|
|
@@ -29,23 +29,32 @@ function pushResumeTextChunk(updates, role, text) {
|
|
|
29
29
|
}
|
|
30
30
|
updates.push({ type: "user_message_chunk", content: { type: "text", text } });
|
|
31
31
|
}
|
|
32
|
-
function pushResumeToolUse(updates, toolCalls, block, parentToolUseId) {
|
|
32
|
+
function pushResumeToolUse(updates, toolCalls, hiddenToolUseIds, block, parentToolUseId) {
|
|
33
33
|
const toolUseId = typeof block.id === "string" ? block.id : "";
|
|
34
34
|
if (!toolUseId) {
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
const name = typeof block.name === "string" ? block.name : "Tool";
|
|
38
38
|
const input = asRecordOrNull(block.input) ?? {};
|
|
39
|
+
if (isToolSearchToolName(name)) {
|
|
40
|
+
hiddenToolUseIds.add(toolUseId);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
39
43
|
const toolCall = createToolCall(toolUseId, name, input, parentToolUseId);
|
|
40
44
|
toolCall.status = "in_progress";
|
|
41
45
|
toolCalls.set(toolUseId, toolCall);
|
|
42
46
|
updates.push({ type: "tool_call", tool_call: toolCall });
|
|
43
47
|
}
|
|
44
|
-
function pushResumeToolResult(updates, toolCalls, block) {
|
|
48
|
+
function pushResumeToolResult(updates, toolCalls, hiddenToolUseIds, block) {
|
|
45
49
|
const toolUseId = typeof block.tool_use_id === "string" ? block.tool_use_id : "";
|
|
46
50
|
if (!toolUseId) {
|
|
47
51
|
return;
|
|
48
52
|
}
|
|
53
|
+
const blockType = typeof block.type === "string" ? block.type : "";
|
|
54
|
+
if (isToolSearchToolResultType(blockType) || hiddenToolUseIds.has(toolUseId)) {
|
|
55
|
+
hiddenToolUseIds.add(toolUseId);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
49
58
|
const isError = Boolean(block.is_error);
|
|
50
59
|
const base = toolCalls.get(toolUseId);
|
|
51
60
|
const fields = buildToolResultFields(isError, block.content, base, block);
|
|
@@ -101,6 +110,7 @@ export function mapSdkSessions(infos, limit = 50) {
|
|
|
101
110
|
export function mapSessionMessagesToUpdates(messages) {
|
|
102
111
|
const updates = [];
|
|
103
112
|
const toolCalls = new Map();
|
|
113
|
+
const hiddenToolUseIds = new Set();
|
|
104
114
|
for (const entry of messages) {
|
|
105
115
|
const fallbackRole = entry.type === "assistant" ? "assistant" : "user";
|
|
106
116
|
for (const message of messageCandidates(entry.message)) {
|
|
@@ -126,11 +136,11 @@ export function mapSessionMessagesToUpdates(messages) {
|
|
|
126
136
|
continue;
|
|
127
137
|
}
|
|
128
138
|
if (isToolUseBlockType(blockType) && role === "assistant") {
|
|
129
|
-
pushResumeToolUse(updates, toolCalls, block, parentToolUseId);
|
|
139
|
+
pushResumeToolUse(updates, toolCalls, hiddenToolUseIds, block, parentToolUseId);
|
|
130
140
|
continue;
|
|
131
141
|
}
|
|
132
142
|
if (TOOL_RESULT_TYPES.has(blockType)) {
|
|
133
|
-
pushResumeToolResult(updates, toolCalls, block);
|
|
143
|
+
pushResumeToolResult(updates, toolCalls, hiddenToolUseIds, block);
|
|
134
144
|
continue;
|
|
135
145
|
}
|
|
136
146
|
if (blockType === "image") {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { asRecordOrNull } from "./shared.js";
|
|
2
2
|
import { toPermissionMode, buildModeState, refreshSupportedModesForSession } from "./commands.js";
|
|
3
3
|
import { writeEvent, emitSessionUpdate, emitConnectEvent, emitSessionReplacedEvent, } from "./events.js";
|
|
4
|
-
import { TOOL_RESULT_TYPES, unwrapToolUseResult } from "./tooling.js";
|
|
5
|
-
import { emitToolCall, emitToolCallUpdate, emitPlanIfTodoWrite, emitToolResultUpdate, finalizeOpenToolCalls, emitToolProgressUpdate, emitToolSummaryUpdate, ensureToolCallVisible, resolveTaskToolUseId, taskProgressText, taskUpdatedFields, } from "./tool_calls.js";
|
|
4
|
+
import { TOOL_RESULT_TYPES, isToolSearchToolName, isToolSearchToolResultType, unwrapToolUseResult, } from "./tooling.js";
|
|
5
|
+
import { emitToolCall, emitToolCallUpdate, emitPlanIfTodoWrite, emitToolResultUpdate, finalizeOpenToolCalls, emitToolProgressUpdate, emitToolSummaryUpdate, ensureToolCallVisible, resolveTaskToolUseId, toolAcceptsTaskLifecycle, 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
8
|
import { buildApiRetryUpdate, buildRateLimitUpdate, normalizeSettingsParseErrors, numberField, parseRuntimeSessionState, } from "./state_parsing.js";
|
|
@@ -155,6 +155,12 @@ export function handleTaskSystemMessage(session, subtype, msg) {
|
|
|
155
155
|
return;
|
|
156
156
|
}
|
|
157
157
|
const toolCall = ensureToolCallVisible(session, toolUseId, "Agent", {});
|
|
158
|
+
if (!toolAcceptsTaskLifecycle(toolCall)) {
|
|
159
|
+
if (taskId) {
|
|
160
|
+
session.taskToolUseIds.delete(taskId);
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
158
164
|
if (toolCall.status === "pending") {
|
|
159
165
|
emitToolCallUpdate(session, toolUseId, { status: "in_progress" }, "progress");
|
|
160
166
|
}
|
|
@@ -238,6 +244,31 @@ function logContentBlockLinkage(session, blockType, toolUseId, toolName, linkage
|
|
|
238
244
|
},
|
|
239
245
|
});
|
|
240
246
|
}
|
|
247
|
+
function hideToolUse(session, toolUseId) {
|
|
248
|
+
if (toolUseId) {
|
|
249
|
+
session.hiddenToolUseIds.add(toolUseId);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function isHiddenToolUse(session, toolUseId, toolName) {
|
|
253
|
+
if (!toolUseId) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
if (isToolSearchToolName(toolName)) {
|
|
257
|
+
hideToolUse(session, toolUseId);
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
return session.hiddenToolUseIds.has(toolUseId);
|
|
261
|
+
}
|
|
262
|
+
function isHiddenToolResult(session, toolUseId, blockType) {
|
|
263
|
+
if (!toolUseId) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
if (isToolSearchToolResultType(blockType)) {
|
|
267
|
+
hideToolUse(session, toolUseId);
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
return session.hiddenToolUseIds.has(toolUseId);
|
|
271
|
+
}
|
|
241
272
|
export function handleContentBlock(session, block, linkage) {
|
|
242
273
|
const blockType = typeof block.type === "string" ? block.type : "";
|
|
243
274
|
if (blockType === "text") {
|
|
@@ -261,6 +292,9 @@ export function handleContentBlock(session, block, linkage) {
|
|
|
261
292
|
if (!toolUseId) {
|
|
262
293
|
return;
|
|
263
294
|
}
|
|
295
|
+
if (isHiddenToolUse(session, toolUseId, name)) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
264
298
|
logContentBlockLinkage(session, blockType, toolUseId, name, linkage);
|
|
265
299
|
emitPlanIfTodoWrite(session, name, input);
|
|
266
300
|
emitToolCall(session, toolUseId, name, input, linkage?.parentToolUseId ?? null);
|
|
@@ -271,6 +305,9 @@ export function handleContentBlock(session, block, linkage) {
|
|
|
271
305
|
if (!toolUseId) {
|
|
272
306
|
return;
|
|
273
307
|
}
|
|
308
|
+
if (isHiddenToolResult(session, toolUseId, blockType)) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
274
311
|
logContentBlockLinkage(session, blockType, toolUseId, undefined, linkage);
|
|
275
312
|
const isError = Boolean(block.is_error);
|
|
276
313
|
emitToolResultUpdate(session, toolUseId, isError, block.content, block);
|
|
@@ -594,6 +631,9 @@ export function handleSdkMessage(session, message) {
|
|
|
594
631
|
if (type === "tool_progress") {
|
|
595
632
|
const toolUseId = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
|
|
596
633
|
const toolName = typeof msg.tool_name === "string" ? msg.tool_name : "Tool";
|
|
634
|
+
if (isHiddenToolUse(session, toolUseId, toolName)) {
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
597
637
|
bridgeLogger.debug({
|
|
598
638
|
target: LOG_TARGETS.APP_TOOL,
|
|
599
639
|
eventName: "sdk_tool_progress_linkage_observed",
|
|
@@ -620,6 +660,9 @@ export function handleSdkMessage(session, message) {
|
|
|
620
660
|
: [];
|
|
621
661
|
if (summary && toolIds.length > 0) {
|
|
622
662
|
for (const toolUseId of toolIds) {
|
|
663
|
+
if (session.hiddenToolUseIds.has(toolUseId)) {
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
623
666
|
emitToolSummaryUpdate(session, toolUseId, summary);
|
|
624
667
|
}
|
|
625
668
|
}
|
|
@@ -672,6 +715,9 @@ export function handleSdkMessage(session, message) {
|
|
|
672
715
|
handleUserToolResultBlocks(session, msg);
|
|
673
716
|
const toolUseId = typeof msg.parent_tool_use_id === "string" ? msg.parent_tool_use_id : "";
|
|
674
717
|
if (toolUseId && "tool_use_result" in msg) {
|
|
718
|
+
if (session.hiddenToolUseIds.has(toolUseId)) {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
675
721
|
const parsed = unwrapToolUseResult(msg.tool_use_result);
|
|
676
722
|
emitToolResultUpdate(session, toolUseId, parsed.isError, parsed.content, msg.tool_use_result);
|
|
677
723
|
}
|
|
@@ -7,9 +7,13 @@ import { AsyncQueue } from "./shared.js";
|
|
|
7
7
|
import { permissionOptionsFromSuggestions, permissionResultFromOutcome, } from "./permissions.js";
|
|
8
8
|
import { failConnection, emitSessionUpdate, emitConnectEvent, emitPermissionRequestEvent, emitElicitationRequestEvent, } from "./events.js";
|
|
9
9
|
import { ensureToolCallVisible, setToolCallStatus, } from "./tool_calls.js";
|
|
10
|
+
import { isToolSearchToolName } from "./tooling.js";
|
|
10
11
|
import { requestExitPlanModeApproval, requestAskUserQuestionAnswers, EXIT_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "./user_interaction.js";
|
|
11
12
|
import { mapAvailableAgents, emitAvailableAgentsIfChanged, refreshAvailableAgents } from "./agents.js";
|
|
12
13
|
import { emitAuthRequired, emitFastModeUpdateIfChanged } from "./error_classification.js";
|
|
14
|
+
const BRIDGE_RUNTIME_PROCESS_NAME = process.platform === "win32" ? "claude-rs-bridge-node.exe" : "claude-rs-bridge-node";
|
|
15
|
+
const BRIDGE_RUNTIME_GUARD_PROMPT = `Do not terminate the Claude Rust bridge runtime process \`${BRIDGE_RUNTIME_PROCESS_NAME}\`; ` +
|
|
16
|
+
"when cleaning up development servers, only stop processes by explicit PIDs you started in this session.";
|
|
13
17
|
function permissionDisplayFromCanUseOptions(options) {
|
|
14
18
|
const title = typeof options.title === "string" ? options.title.trim() : "";
|
|
15
19
|
const displayName = typeof options.displayName === "string" ? options.displayName.trim() : "";
|
|
@@ -218,6 +222,10 @@ export async function createSession(params) {
|
|
|
218
222
|
const sessionIdForLogs = () => session?.sessionId ?? provisionalSessionId;
|
|
219
223
|
const canUseTool = async (toolName, inputData, options) => {
|
|
220
224
|
const toolUseId = options.toolUseID;
|
|
225
|
+
if (isToolSearchToolName(toolName)) {
|
|
226
|
+
session?.hiddenToolUseIds.add(toolUseId);
|
|
227
|
+
return { behavior: "allow", updatedInput: inputData, toolUseID: toolUseId };
|
|
228
|
+
}
|
|
221
229
|
if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
|
|
222
230
|
const existing = ensureToolCallVisible(session, toolUseId, toolName, inputData);
|
|
223
231
|
return await requestExitPlanModeApproval(session, toolUseId, inputData, existing);
|
|
@@ -340,6 +348,7 @@ export async function createSession(params) {
|
|
|
340
348
|
pendingQuestions: new Map(),
|
|
341
349
|
pendingElicitations: new Map(),
|
|
342
350
|
mcpStatusRevalidatedAt: new Map(),
|
|
351
|
+
hiddenToolUseIds: new Set(),
|
|
343
352
|
authHintSent: false,
|
|
344
353
|
...(params.resumeUpdates && params.resumeUpdates.length > 0
|
|
345
354
|
? { resumeUpdates: params.resumeUpdates }
|
|
@@ -578,14 +587,15 @@ function startupPermissionModeOptions(launchSettings) {
|
|
|
578
587
|
}
|
|
579
588
|
function systemPromptFromLaunchSettings(launchSettings) {
|
|
580
589
|
const language = launchSettings.language?.trim();
|
|
581
|
-
|
|
582
|
-
|
|
590
|
+
const appendLines = [BRIDGE_RUNTIME_GUARD_PROMPT];
|
|
591
|
+
if (language) {
|
|
592
|
+
appendLines.push(`Always respond to the user in ${language} unless the user explicitly asks for a different language. ` +
|
|
593
|
+
`Keep code, shell commands, file paths, API names, tool names, and raw error text unchanged unless the user explicitly asks for translation.`);
|
|
583
594
|
}
|
|
584
595
|
return {
|
|
585
596
|
type: "preset",
|
|
586
597
|
preset: "claude_code",
|
|
587
|
-
append:
|
|
588
|
-
`Keep code, shell commands, file paths, API names, tool names, and raw error text unchanged unless the user explicitly asks for translation.`,
|
|
598
|
+
append: appendLines.join(" "),
|
|
589
599
|
};
|
|
590
600
|
}
|
|
591
601
|
export function buildQueryOptions(params) {
|
|
@@ -603,7 +613,7 @@ export function buildQueryOptions(params) {
|
|
|
603
613
|
...modelOption,
|
|
604
614
|
...permissionModeOptions,
|
|
605
615
|
toolConfig: { askUserQuestion: { previewFormat: "markdown" } },
|
|
606
|
-
|
|
616
|
+
systemPrompt,
|
|
607
617
|
...(params.launchSettings.agent_progress_summaries !== undefined
|
|
608
618
|
? { agentProgressSummaries: params.launchSettings.agent_progress_summaries }
|
|
609
619
|
: {}),
|
|
@@ -8,6 +8,13 @@ export function numberField(record, ...keys) {
|
|
|
8
8
|
}
|
|
9
9
|
return undefined;
|
|
10
10
|
}
|
|
11
|
+
function nonNegativeNumberField(record, ...keys) {
|
|
12
|
+
const value = numberField(record, ...keys);
|
|
13
|
+
if (value === undefined || value < 0) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
11
18
|
export function parseFastModeState(value) {
|
|
12
19
|
if (value === "off" || value === "cooldown" || value === "on") {
|
|
13
20
|
return value;
|
|
@@ -86,7 +93,7 @@ export function buildRateLimitUpdate(rateLimitInfo) {
|
|
|
86
93
|
export function buildApiRetryUpdate(message) {
|
|
87
94
|
const attempt = numberField(message, "attempt");
|
|
88
95
|
const maxRetries = numberField(message, "max_retries", "maxRetries");
|
|
89
|
-
const retryDelayMs =
|
|
96
|
+
const retryDelayMs = nonNegativeNumberField(message, "retry_delay_ms", "retryDelayMs");
|
|
90
97
|
if (attempt === undefined || maxRetries === undefined || retryDelayMs === undefined) {
|
|
91
98
|
return null;
|
|
92
99
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { emitSessionUpdate } from "./events.js";
|
|
2
2
|
import { bridgeLogger, LOG_TARGETS } from "./logger.js";
|
|
3
3
|
import { buildToolResultFields, createToolCall } from "./tooling.js";
|
|
4
|
+
const TOOL_SUMMARY_TOOL_NAMES = new Set(["Agent", "Task", "WebSearch", "WebFetch", "ExitPlanMode"]);
|
|
5
|
+
const TASK_LIFECYCLE_TOOL_NAMES = new Set(["Agent", "Task"]);
|
|
4
6
|
function jsonSize(value) {
|
|
5
7
|
if (value === undefined) {
|
|
6
8
|
return undefined;
|
|
@@ -30,6 +32,14 @@ function toolName(base, fields) {
|
|
|
30
32
|
}
|
|
31
33
|
return toolNameFromMeta(base?.meta);
|
|
32
34
|
}
|
|
35
|
+
export function toolUsesSummaryOutput(base) {
|
|
36
|
+
const baseToolName = toolName(base);
|
|
37
|
+
return Boolean(baseToolName && TOOL_SUMMARY_TOOL_NAMES.has(baseToolName));
|
|
38
|
+
}
|
|
39
|
+
export function toolAcceptsTaskLifecycle(base) {
|
|
40
|
+
const baseToolName = toolName(base);
|
|
41
|
+
return Boolean(baseToolName && TASK_LIFECYCLE_TOOL_NAMES.has(baseToolName));
|
|
42
|
+
}
|
|
33
43
|
function classifyFailureKind(rawOutput) {
|
|
34
44
|
if (!rawOutput) {
|
|
35
45
|
return "failed";
|
|
@@ -279,6 +289,9 @@ export function emitToolSummaryUpdate(session, toolUseId, summary) {
|
|
|
279
289
|
if (!base) {
|
|
280
290
|
return;
|
|
281
291
|
}
|
|
292
|
+
if (!toolUsesSummaryOutput(base)) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
282
295
|
const fields = {
|
|
283
296
|
status: base.status === "failed" || base.status === "killed" ? base.status : "completed",
|
|
284
297
|
raw_output: summary,
|
|
@@ -10,6 +10,13 @@ export const TOOL_RESULT_TYPES = new Set([
|
|
|
10
10
|
"text_editor_code_execution_tool_result",
|
|
11
11
|
"mcp_tool_result",
|
|
12
12
|
]);
|
|
13
|
+
export function isToolSearchToolName(name) {
|
|
14
|
+
const normalized = name.replace(/[\s_-]+/g, "").toLowerCase();
|
|
15
|
+
return normalized === "toolsearch" || normalized === "toolsearchtool";
|
|
16
|
+
}
|
|
17
|
+
export function isToolSearchToolResultType(blockType) {
|
|
18
|
+
return blockType === "tool_search_tool_result";
|
|
19
|
+
}
|
|
13
20
|
export function isToolUseBlockType(blockType) {
|
|
14
21
|
return blockType === "tool_use" || blockType === "server_tool_use" || blockType === "mcp_tool_use";
|
|
15
22
|
}
|
package/agent-sdk/dist/bridge.js
CHANGED
|
@@ -43,7 +43,7 @@ export async function generatePersistedSessionTitle(query, description) {
|
|
|
43
43
|
}
|
|
44
44
|
return title;
|
|
45
45
|
}
|
|
46
|
-
const EXPECTED_AGENT_SDK_VERSION = "0.
|
|
46
|
+
const EXPECTED_AGENT_SDK_VERSION = "0.3.146";
|
|
47
47
|
const require = createRequire(import.meta.url);
|
|
48
48
|
export function resolveInstalledAgentSdkVersion() {
|
|
49
49
|
try {
|
|
@@ -6,6 +6,11 @@ import { emitCurrentModelUpdate, refreshCurrentModel, resolveCurrentModel, shoul
|
|
|
6
6
|
import { emitToolProgressUpdate } from "./bridge/tool_calls.js";
|
|
7
7
|
import { requestAskUserQuestionAnswers } from "./bridge/user_interaction.js";
|
|
8
8
|
import { handleResultMessage } from "./bridge/message_handlers.js";
|
|
9
|
+
const BRIDGE_RUNTIME_PROCESS_NAME = process.platform === "win32" ? "claude-rs-bridge-node.exe" : "claude-rs-bridge-node";
|
|
10
|
+
const BRIDGE_RUNTIME_GUARD_PROMPT = `Do not terminate the Claude Rust bridge runtime process \`${BRIDGE_RUNTIME_PROCESS_NAME}\`; ` +
|
|
11
|
+
"when cleaning up development servers, only stop processes by explicit PIDs you started in this session.";
|
|
12
|
+
const GERMAN_LANGUAGE_PROMPT = "Always respond to the user in German unless the user explicitly asks for a different language. " +
|
|
13
|
+
"Keep code, shell commands, file paths, API names, tool names, and raw error text unchanged unless the user explicitly asks for translation.";
|
|
9
14
|
function makeSessionState() {
|
|
10
15
|
const input = new AsyncQueue();
|
|
11
16
|
return {
|
|
@@ -28,6 +33,7 @@ function makeSessionState() {
|
|
|
28
33
|
pendingQuestions: new Map(),
|
|
29
34
|
pendingElicitations: new Map(),
|
|
30
35
|
mcpStatusRevalidatedAt: new Map(),
|
|
36
|
+
hiddenToolUseIds: new Set(),
|
|
31
37
|
authHintSent: false,
|
|
32
38
|
};
|
|
33
39
|
}
|
|
@@ -425,8 +431,7 @@ test("buildQueryOptions maps launch settings into sdk query options", () => {
|
|
|
425
431
|
assert.deepEqual(options.systemPrompt, {
|
|
426
432
|
type: "preset",
|
|
427
433
|
preset: "claude_code",
|
|
428
|
-
append:
|
|
429
|
-
"Keep code, shell commands, file paths, API names, tool names, and raw error text unchanged unless the user explicitly asks for translation.",
|
|
434
|
+
append: `${BRIDGE_RUNTIME_GUARD_PROMPT} ${GERMAN_LANGUAGE_PROMPT}`,
|
|
430
435
|
});
|
|
431
436
|
assert.equal(options.model, "haiku");
|
|
432
437
|
assert.equal(options.permissionMode, "plan");
|
|
@@ -536,7 +541,7 @@ test("buildQueryOptions enables dangerous skip flag for bypass permissions start
|
|
|
536
541
|
assert.equal(options.permissionMode, "bypassPermissions");
|
|
537
542
|
assert.equal(options.allowDangerouslySkipPermissions, true);
|
|
538
543
|
});
|
|
539
|
-
test("buildQueryOptions omits startup overrides
|
|
544
|
+
test("buildQueryOptions omits optional startup overrides but keeps bridge guard prompt", () => {
|
|
540
545
|
const input = new AsyncQueue();
|
|
541
546
|
const options = buildQueryOptions({
|
|
542
547
|
cwd: "C:/work",
|
|
@@ -551,7 +556,11 @@ test("buildQueryOptions omits startup overrides for default logout path", () =>
|
|
|
551
556
|
assert.equal("model" in options, false);
|
|
552
557
|
assert.equal("permissionMode" in options, false);
|
|
553
558
|
assert.equal("allowDangerouslySkipPermissions" in options, false);
|
|
554
|
-
assert.
|
|
559
|
+
assert.deepEqual(options.systemPrompt, {
|
|
560
|
+
type: "preset",
|
|
561
|
+
preset: "claude_code",
|
|
562
|
+
append: BRIDGE_RUNTIME_GUARD_PROMPT,
|
|
563
|
+
});
|
|
555
564
|
assert.equal("agentProgressSummaries" in options, false);
|
|
556
565
|
});
|
|
557
566
|
test("buildQueryOptions makes sandbox fallback explicit when enabled", () => {
|
|
@@ -714,6 +723,161 @@ test("handleTaskSystemMessage final summary replaces prior task content and fina
|
|
|
714
723
|
});
|
|
715
724
|
assert.equal(session.taskToolUseIds.has("task-1"), false);
|
|
716
725
|
});
|
|
726
|
+
test("handleTaskSystemMessage ignores lifecycle content for concrete output tools", () => {
|
|
727
|
+
const session = makeSessionState();
|
|
728
|
+
const protectedTools = [
|
|
729
|
+
createToolCall("tool-bash", "Bash", { command: "git status" }),
|
|
730
|
+
createToolCall("tool-read", "Read", { file_path: "src/main.rs" }),
|
|
731
|
+
createToolCall("tool-write", "Write", {
|
|
732
|
+
file_path: "src/main.rs",
|
|
733
|
+
content: "updated file contents",
|
|
734
|
+
}),
|
|
735
|
+
];
|
|
736
|
+
for (const toolCall of protectedTools) {
|
|
737
|
+
toolCall.status = "in_progress";
|
|
738
|
+
toolCall.raw_output = `actual output for ${toolCall.tool_call_id}`;
|
|
739
|
+
session.toolCalls.set(toolCall.tool_call_id, toolCall);
|
|
740
|
+
}
|
|
741
|
+
const events = captureBridgeEvents(() => {
|
|
742
|
+
for (const toolCall of protectedTools) {
|
|
743
|
+
const taskId = `task-${toolCall.tool_call_id}`;
|
|
744
|
+
handleTaskSystemMessage(session, "task_started", {
|
|
745
|
+
task_id: taskId,
|
|
746
|
+
tool_use_id: toolCall.tool_call_id,
|
|
747
|
+
description: "Show working tree status",
|
|
748
|
+
});
|
|
749
|
+
handleTaskSystemMessage(session, "task_notification", {
|
|
750
|
+
task_id: taskId,
|
|
751
|
+
tool_use_id: toolCall.tool_call_id,
|
|
752
|
+
status: "completed",
|
|
753
|
+
summary: "Show diff summary for unstaged changes",
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
assert.deepEqual(events, []);
|
|
758
|
+
for (const toolCall of protectedTools) {
|
|
759
|
+
const stored = session.toolCalls.get(toolCall.tool_call_id);
|
|
760
|
+
assert.equal(stored?.status, "in_progress");
|
|
761
|
+
assert.equal(stored?.raw_output, `actual output for ${toolCall.tool_call_id}`);
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
test("handleSdkMessage ignores tool_use_summary for Bash Read and Write tools", () => {
|
|
765
|
+
const session = makeSessionState();
|
|
766
|
+
const protectedTools = [
|
|
767
|
+
createToolCall("tool-bash", "Bash", { command: "git diff" }),
|
|
768
|
+
createToolCall("tool-read", "Read", { file_path: "src/main.rs" }),
|
|
769
|
+
createToolCall("tool-write", "Write", {
|
|
770
|
+
file_path: "src/main.rs",
|
|
771
|
+
content: "updated file contents",
|
|
772
|
+
}),
|
|
773
|
+
];
|
|
774
|
+
for (const toolCall of protectedTools) {
|
|
775
|
+
toolCall.status = "completed";
|
|
776
|
+
toolCall.raw_output = `actual output for ${toolCall.tool_call_id}`;
|
|
777
|
+
session.toolCalls.set(toolCall.tool_call_id, toolCall);
|
|
778
|
+
}
|
|
779
|
+
const events = captureBridgeEvents(() => {
|
|
780
|
+
handleSdkMessage(session, {
|
|
781
|
+
type: "tool_use_summary",
|
|
782
|
+
summary: "Show commits on this branch since diverging from main",
|
|
783
|
+
preceding_tool_use_ids: protectedTools.map((toolCall) => toolCall.tool_call_id),
|
|
784
|
+
uuid: "message-summary",
|
|
785
|
+
session_id: "session-1",
|
|
786
|
+
});
|
|
787
|
+
});
|
|
788
|
+
assert.deepEqual(events, []);
|
|
789
|
+
for (const toolCall of protectedTools) {
|
|
790
|
+
assert.equal(session.toolCalls.get(toolCall.tool_call_id)?.raw_output, `actual output for ${toolCall.tool_call_id}`);
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
test("handleSdkMessage applies tool_use_summary for summary-oriented tools", () => {
|
|
794
|
+
const session = makeSessionState();
|
|
795
|
+
const toolCall = createToolCall("tool-agent", "Agent", { prompt: "Inspect auth flow" });
|
|
796
|
+
session.toolCalls.set(toolCall.tool_call_id, toolCall);
|
|
797
|
+
const events = captureBridgeEvents(() => {
|
|
798
|
+
handleSdkMessage(session, {
|
|
799
|
+
type: "tool_use_summary",
|
|
800
|
+
summary: "Inspected auth flow and found the failing check",
|
|
801
|
+
preceding_tool_use_ids: [toolCall.tool_call_id],
|
|
802
|
+
uuid: "message-summary",
|
|
803
|
+
session_id: "session-1",
|
|
804
|
+
});
|
|
805
|
+
});
|
|
806
|
+
const lastEvent = events.at(-1);
|
|
807
|
+
assert.ok(lastEvent);
|
|
808
|
+
assert.equal(lastEvent.event, "session_update");
|
|
809
|
+
assert.deepEqual(lastEvent.update, {
|
|
810
|
+
type: "tool_call_update",
|
|
811
|
+
tool_call_update: {
|
|
812
|
+
tool_call_id: "tool-agent",
|
|
813
|
+
fields: {
|
|
814
|
+
status: "completed",
|
|
815
|
+
raw_output: "Inspected auth flow and found the failing check",
|
|
816
|
+
content: [
|
|
817
|
+
{
|
|
818
|
+
type: "content",
|
|
819
|
+
content: { type: "text", text: "Inspected auth flow and found the failing check" },
|
|
820
|
+
},
|
|
821
|
+
],
|
|
822
|
+
},
|
|
823
|
+
},
|
|
824
|
+
});
|
|
825
|
+
assert.equal(session.toolCalls.get(toolCall.tool_call_id)?.raw_output, "Inspected auth flow and found the failing check");
|
|
826
|
+
});
|
|
827
|
+
test("handleSdkMessage suppresses ToolSearch bridge events without denying SDK use", () => {
|
|
828
|
+
const session = makeSessionState();
|
|
829
|
+
const events = captureBridgeEvents(() => {
|
|
830
|
+
handleSdkMessage(session, {
|
|
831
|
+
type: "stream_event",
|
|
832
|
+
event: {
|
|
833
|
+
type: "content_block_start",
|
|
834
|
+
content_block: {
|
|
835
|
+
type: "server_tool_use",
|
|
836
|
+
id: "tool-search-1",
|
|
837
|
+
name: "ToolSearch",
|
|
838
|
+
input: { query: "src/" },
|
|
839
|
+
},
|
|
840
|
+
},
|
|
841
|
+
uuid: "message-search-start",
|
|
842
|
+
session_id: "session-1",
|
|
843
|
+
});
|
|
844
|
+
handleSdkMessage(session, {
|
|
845
|
+
type: "tool_progress",
|
|
846
|
+
tool_use_id: "tool-search-1",
|
|
847
|
+
tool_name: "ToolSearch",
|
|
848
|
+
uuid: "message-search-progress",
|
|
849
|
+
session_id: "session-1",
|
|
850
|
+
});
|
|
851
|
+
handleSdkMessage(session, {
|
|
852
|
+
type: "user",
|
|
853
|
+
parent_tool_use_id: "tool-search-1",
|
|
854
|
+
tool_use_result: { content: "matched src/main.rs", is_error: false },
|
|
855
|
+
message: {
|
|
856
|
+
role: "user",
|
|
857
|
+
content: [
|
|
858
|
+
{
|
|
859
|
+
type: "tool_search_tool_result",
|
|
860
|
+
tool_use_id: "tool-search-1",
|
|
861
|
+
content: "matched src/main.rs",
|
|
862
|
+
is_error: false,
|
|
863
|
+
},
|
|
864
|
+
],
|
|
865
|
+
},
|
|
866
|
+
uuid: "message-search-result",
|
|
867
|
+
session_id: "session-1",
|
|
868
|
+
});
|
|
869
|
+
handleSdkMessage(session, {
|
|
870
|
+
type: "tool_use_summary",
|
|
871
|
+
summary: "Found source files",
|
|
872
|
+
preceding_tool_use_ids: ["tool-search-1"],
|
|
873
|
+
uuid: "message-search-summary",
|
|
874
|
+
session_id: "session-1",
|
|
875
|
+
});
|
|
876
|
+
});
|
|
877
|
+
assert.deepEqual(events, []);
|
|
878
|
+
assert.equal(session.hiddenToolUseIds.has("tool-search-1"), true);
|
|
879
|
+
assert.equal(session.toolCalls.has("tool-search-1"), false);
|
|
880
|
+
});
|
|
717
881
|
test("handleTaskSystemMessage applies task_updated description patches to the linked task", () => {
|
|
718
882
|
const session = makeSessionState();
|
|
719
883
|
const events = captureBridgeEvents(() => {
|
|
@@ -873,8 +1037,7 @@ test("buildQueryOptions trims language before appending system prompt", () => {
|
|
|
873
1037
|
assert.deepEqual(options.systemPrompt, {
|
|
874
1038
|
type: "preset",
|
|
875
1039
|
preset: "claude_code",
|
|
876
|
-
append:
|
|
877
|
-
"Keep code, shell commands, file paths, API names, tool names, and raw error text unchanged unless the user explicitly asks for translation.",
|
|
1040
|
+
append: `${BRIDGE_RUNTIME_GUARD_PROMPT} ${GERMAN_LANGUAGE_PROMPT}`,
|
|
878
1041
|
});
|
|
879
1042
|
});
|
|
880
1043
|
test("parseCommandEnvelope rejects missing required fields", () => {
|
|
@@ -1139,7 +1302,28 @@ test("buildApiRetryUpdate maps SDK api_retry messages to wire shape", () => {
|
|
|
1139
1302
|
error_status: null,
|
|
1140
1303
|
error: "unknown",
|
|
1141
1304
|
});
|
|
1305
|
+
assert.deepEqual(buildApiRetryUpdate({
|
|
1306
|
+
attempt: 1,
|
|
1307
|
+
max_retries: 10,
|
|
1308
|
+
retry_delay_ms: 549.8881698459426,
|
|
1309
|
+
error_status: null,
|
|
1310
|
+
error: "unexpected",
|
|
1311
|
+
}), {
|
|
1312
|
+
type: "api_retry_update",
|
|
1313
|
+
attempt: 1,
|
|
1314
|
+
max_retries: 10,
|
|
1315
|
+
retry_delay_ms: 549.8881698459426,
|
|
1316
|
+
error_status: null,
|
|
1317
|
+
error: "unknown",
|
|
1318
|
+
});
|
|
1142
1319
|
assert.equal(buildApiRetryUpdate({ attempt: 1 }), null);
|
|
1320
|
+
assert.equal(buildApiRetryUpdate({
|
|
1321
|
+
attempt: 1,
|
|
1322
|
+
max_retries: 10,
|
|
1323
|
+
retry_delay_ms: -1,
|
|
1324
|
+
error_status: null,
|
|
1325
|
+
error: "server_error",
|
|
1326
|
+
}), null);
|
|
1143
1327
|
});
|
|
1144
1328
|
test("normalizeSettingsParseError accepts only SDK-shaped errors", () => {
|
|
1145
1329
|
assert.deepEqual(normalizeSettingsParseError({
|
|
@@ -1673,7 +1857,7 @@ test("looksLikeAuthRequired detects login hints", () => {
|
|
|
1673
1857
|
assert.equal(looksLikeAuthRequired("normal tool output"), false);
|
|
1674
1858
|
});
|
|
1675
1859
|
test("agent sdk version compatibility check matches pinned version", () => {
|
|
1676
|
-
assert.equal(resolveInstalledAgentSdkVersion(), "0.
|
|
1860
|
+
assert.equal(resolveInstalledAgentSdkVersion(), "0.3.146");
|
|
1677
1861
|
assert.equal(agentSdkVersionCompatibilityError(), undefined);
|
|
1678
1862
|
});
|
|
1679
1863
|
test("mapSessionMessagesToUpdates maps message content blocks", () => {
|
|
@@ -1735,6 +1919,55 @@ test("mapSessionMessagesToUpdates maps message content blocks", () => {
|
|
|
1735
1919
|
assert.equal(variantCounts.get("tool_call"), 1);
|
|
1736
1920
|
assert.equal(variantCounts.get("tool_call_update"), 1);
|
|
1737
1921
|
});
|
|
1922
|
+
test("mapSessionMessagesToUpdates suppresses ToolSearch history blocks", () => {
|
|
1923
|
+
const updates = mapSessionMessagesToUpdates([
|
|
1924
|
+
{
|
|
1925
|
+
type: "assistant",
|
|
1926
|
+
uuid: "a1",
|
|
1927
|
+
session_id: "s1",
|
|
1928
|
+
parent_tool_use_id: null,
|
|
1929
|
+
message: {
|
|
1930
|
+
role: "assistant",
|
|
1931
|
+
content: [
|
|
1932
|
+
{
|
|
1933
|
+
type: "server_tool_use",
|
|
1934
|
+
id: "tool-search-1",
|
|
1935
|
+
name: "ToolSearch",
|
|
1936
|
+
input: { query: "src/" },
|
|
1937
|
+
},
|
|
1938
|
+
{ type: "tool_use", id: "tool-bash", name: "Bash", input: { command: "echo ok" } },
|
|
1939
|
+
],
|
|
1940
|
+
},
|
|
1941
|
+
},
|
|
1942
|
+
{
|
|
1943
|
+
type: "user",
|
|
1944
|
+
uuid: "u1",
|
|
1945
|
+
session_id: "s1",
|
|
1946
|
+
parent_tool_use_id: null,
|
|
1947
|
+
message: {
|
|
1948
|
+
role: "user",
|
|
1949
|
+
content: [
|
|
1950
|
+
{
|
|
1951
|
+
type: "tool_search_tool_result",
|
|
1952
|
+
tool_use_id: "tool-search-1",
|
|
1953
|
+
content: "matched src/main.rs",
|
|
1954
|
+
is_error: false,
|
|
1955
|
+
},
|
|
1956
|
+
{
|
|
1957
|
+
type: "tool_result",
|
|
1958
|
+
tool_use_id: "tool-bash",
|
|
1959
|
+
content: "ok",
|
|
1960
|
+
is_error: false,
|
|
1961
|
+
},
|
|
1962
|
+
],
|
|
1963
|
+
},
|
|
1964
|
+
},
|
|
1965
|
+
]);
|
|
1966
|
+
const toolCalls = updates.filter((update) => update.type === "tool_call");
|
|
1967
|
+
const toolUpdates = updates.filter((update) => update.type === "tool_call_update");
|
|
1968
|
+
assert.deepEqual(toolCalls.map((update) => update.tool_call.tool_call_id), ["tool-bash"]);
|
|
1969
|
+
assert.deepEqual(toolUpdates.map((update) => update.tool_call_update.tool_call_id), ["tool-bash"]);
|
|
1970
|
+
});
|
|
1738
1971
|
test("mapSessionMessagesToUpdates preserves parallel tool results", () => {
|
|
1739
1972
|
const updates = mapSessionMessagesToUpdates([
|
|
1740
1973
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-rust",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Claude Code Rust - native Rust terminal interface for Claude Code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -29,7 +29,10 @@
|
|
|
29
29
|
"README.md"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@anthropic-ai/claude-agent-sdk": "0.
|
|
32
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.146",
|
|
33
|
+
"@anthropic-ai/sdk": "0.97.1",
|
|
34
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
35
|
+
"zod": "4.4.3"
|
|
33
36
|
},
|
|
34
37
|
"scripts": {
|
|
35
38
|
"postinstall": "node ./scripts/postinstall.js",
|
package/scripts/postinstall.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
const fs = require("node:fs");
|
|
5
5
|
const path = require("node:path");
|
|
6
6
|
const https = require("node:https");
|
|
7
|
+
const { spawnSync } = require("node:child_process");
|
|
7
8
|
const { pipeline } = require("node:stream/promises");
|
|
8
9
|
|
|
9
10
|
const TARGETS = {
|
|
@@ -14,6 +15,8 @@ const TARGETS = {
|
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
const MAX_REDIRECTS = 5;
|
|
18
|
+
const BRIDGE_RUNTIME_EXE =
|
|
19
|
+
process.platform === "win32" ? "claude-rs-bridge-node.exe" : "claude-rs-bridge-node";
|
|
17
20
|
|
|
18
21
|
function getTargetInfo() {
|
|
19
22
|
return TARGETS[`${process.platform}:${process.arch}`];
|
|
@@ -56,6 +59,49 @@ async function downloadFile(url, outPath, redirects = 0) {
|
|
|
56
59
|
});
|
|
57
60
|
}
|
|
58
61
|
|
|
62
|
+
function installRenamedBridgeRuntime(installDir) {
|
|
63
|
+
const sourcePath = process.execPath;
|
|
64
|
+
const runtimePath = path.join(installDir, BRIDGE_RUNTIME_EXE);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
if (!sourcePath || !fs.existsSync(sourcePath)) {
|
|
68
|
+
throw new Error("current Node.js executable could not be resolved");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (path.resolve(sourcePath) !== path.resolve(runtimePath)) {
|
|
72
|
+
fs.copyFileSync(sourcePath, runtimePath);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (process.platform !== "win32") {
|
|
76
|
+
fs.chmodSync(runtimePath, 0o755);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const result = spawnSync(runtimePath, ["--version"], {
|
|
80
|
+
encoding: "utf8",
|
|
81
|
+
windowsHide: true
|
|
82
|
+
});
|
|
83
|
+
const version = String(result.stdout || "").trim();
|
|
84
|
+
|
|
85
|
+
if (result.status !== 0 || !/^v\d+\./.test(version)) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`copied runtime failed validation${result.stderr ? `: ${result.stderr.trim()}` : ""}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log(`Installed renamed Agent SDK bridge runtime ${BRIDGE_RUNTIME_EXE} (${version})`);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
try {
|
|
94
|
+
fs.rmSync(runtimePath, { force: true });
|
|
95
|
+
} catch {
|
|
96
|
+
// Best-effort cleanup only; the Rust binary can still fall back to `node`.
|
|
97
|
+
}
|
|
98
|
+
console.warn(
|
|
99
|
+
`Skipping renamed Agent SDK bridge runtime: ${error.message}. ` +
|
|
100
|
+
"claude-rs will fall back to the `node` executable on PATH."
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
59
105
|
async function main() {
|
|
60
106
|
const info = getTargetInfo();
|
|
61
107
|
if (!info) {
|
|
@@ -83,6 +129,8 @@ async function main() {
|
|
|
83
129
|
fs.chmodSync(binaryPath, 0o755);
|
|
84
130
|
}
|
|
85
131
|
|
|
132
|
+
installRenamedBridgeRuntime(installDir);
|
|
133
|
+
|
|
86
134
|
console.log(`Installed claude-code-rust ${version} (${info.target})`);
|
|
87
135
|
}
|
|
88
136
|
|