claude-code-rust 0.10.0 → 0.11.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 +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
package/README.md
CHANGED
|
@@ -25,11 +25,9 @@ Claude Code Rust replaces the stock Claude Code terminal interface with a native
|
|
|
25
25
|
npm install -g claude-code-rust
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
The published package installs a `claude-rs` command and fetches the matching
|
|
29
|
-
prebuilt release binary for your platform during install.
|
|
28
|
+
The published package installs a `claude-rs` command and fetches the matching prebuilt release binary for your platform during install.
|
|
30
29
|
|
|
31
|
-
If `claude-rs` resolves to an older global shim, ensure your npm global bin
|
|
32
|
-
directory comes first on `PATH` or remove the stale shim before retrying.
|
|
30
|
+
If `claude-rs` resolves to an older global shim, ensure your npm global bin directory comes first on `PATH` or remove the stale shim before retrying.
|
|
33
31
|
|
|
34
32
|
## Usage
|
|
35
33
|
|
|
@@ -63,10 +61,13 @@ Three-layer design:
|
|
|
63
61
|
|
|
64
62
|
This project is pre-1.0 and under active development. See [CONTRIBUTING.md](CONTRIBUTING.md) for how to get involved.
|
|
65
63
|
|
|
64
|
+
## Limitations
|
|
65
|
+
|
|
66
|
+
Startup is still constrained by the upstream Claude Agent SDK runtime that this TUI wraps. The Rust interface itself is fast, but end-to-end readiness can still take noticeable time before a session is fully available. Improving that remains an active area of work.
|
|
67
|
+
|
|
66
68
|
## License
|
|
67
69
|
|
|
68
|
-
This project is licensed under the [Apache License 2.0](LICENSE).
|
|
69
|
-
Apache-2.0 was chosen to keep usage and redistribution straightforward for individual users, downstream packagers, and commercial adopters.
|
|
70
|
+
This project is licensed under the [Apache License 2.0](LICENSE). Apache-2.0 was chosen to keep usage and redistribution straightforward for individual users, downstream packagers, and commercial adopters.
|
|
70
71
|
|
|
71
72
|
## Disclaimer and Legal Notice
|
|
72
73
|
|
package/agent-sdk/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# claude-rs agent-sdk bridge
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
NDJSON stdio bridge that connects the Rust TUI (`claude-code-rust`) with `@anthropic-ai/claude-agent-sdk`. Spawned as a child process by the Rust binary and communicates via line-delimited JSON envelopes over stdin/stdout.
|
|
4
4
|
|
|
5
5
|
## Local build
|
|
6
6
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { resolveCurrentModel } from "./session_lifecycle.js";
|
|
1
2
|
const MODE_NAMES = {
|
|
2
3
|
default: "Default",
|
|
4
|
+
auto: "Auto",
|
|
3
5
|
acceptEdits: "Accept Edits",
|
|
4
6
|
bypassPermissions: "Bypass Permissions",
|
|
5
7
|
plan: "Plan",
|
|
@@ -7,11 +9,67 @@ const MODE_NAMES = {
|
|
|
7
9
|
};
|
|
8
10
|
const MODE_OPTIONS = [
|
|
9
11
|
{ id: "default", name: "Default", description: "Standard permission flow" },
|
|
12
|
+
{ id: "auto", name: "Auto", description: "Model-classified permission approvals" },
|
|
10
13
|
{ id: "acceptEdits", name: "Accept Edits", description: "Auto-approve edit operations" },
|
|
11
14
|
{ id: "plan", name: "Plan", description: "No tool execution" },
|
|
12
15
|
{ id: "dontAsk", name: "Don't Ask", description: "Reject non-approved tools" },
|
|
13
16
|
{ id: "bypassPermissions", name: "Bypass Permissions", description: "Auto-approve all tools" },
|
|
14
17
|
];
|
|
18
|
+
const BASE_SUPPORTED_MODE_IDS = [
|
|
19
|
+
"default",
|
|
20
|
+
"acceptEdits",
|
|
21
|
+
"plan",
|
|
22
|
+
"dontAsk",
|
|
23
|
+
];
|
|
24
|
+
function currentModelSupportsAutoMode(session) {
|
|
25
|
+
const currentModel = session.currentModel ?? resolveCurrentModel(session);
|
|
26
|
+
return currentModel.supports_auto_mode === true;
|
|
27
|
+
}
|
|
28
|
+
function modeInfoForId(mode) {
|
|
29
|
+
return MODE_OPTIONS.find((entry) => entry.id === mode) ?? {
|
|
30
|
+
id: mode,
|
|
31
|
+
name: MODE_NAMES[mode],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function uniqueModeIds(modeIds) {
|
|
35
|
+
const unique = new Set(modeIds);
|
|
36
|
+
const ordered = MODE_OPTIONS.map((entry) => entry.id).filter((mode) => unique.has(mode));
|
|
37
|
+
const extras = Array.from(unique).filter((mode) => !ordered.includes(mode));
|
|
38
|
+
return [...ordered, ...extras];
|
|
39
|
+
}
|
|
40
|
+
function computedSupportedModeIds(session) {
|
|
41
|
+
const supported = [...BASE_SUPPORTED_MODE_IDS];
|
|
42
|
+
if (currentModelSupportsAutoMode(session)) {
|
|
43
|
+
supported.push("auto");
|
|
44
|
+
}
|
|
45
|
+
if (session.supportsBypassPermissionsMode) {
|
|
46
|
+
supported.push("bypassPermissions");
|
|
47
|
+
}
|
|
48
|
+
if (session.mode) {
|
|
49
|
+
supported.push(session.mode);
|
|
50
|
+
}
|
|
51
|
+
return uniqueModeIds(supported);
|
|
52
|
+
}
|
|
53
|
+
export function refreshSupportedModesForSession(session) {
|
|
54
|
+
const computed = computedSupportedModeIds(session);
|
|
55
|
+
session.supportedModeIds = computed.filter((mode) => mode === session.mode || !session.runtimeUnavailableModeIds.includes(mode));
|
|
56
|
+
}
|
|
57
|
+
export function markModeUnavailableForSession(session, mode) {
|
|
58
|
+
if (session.runtimeUnavailableModeIds.includes(mode)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
session.runtimeUnavailableModeIds = [...session.runtimeUnavailableModeIds, mode];
|
|
62
|
+
refreshSupportedModesForSession(session);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
export function permissionModeFailureLooksUnsupported(mode, message) {
|
|
66
|
+
const normalizedMessage = message.toLowerCase();
|
|
67
|
+
return (normalizedMessage.includes("cannot set permission mode to") &&
|
|
68
|
+
normalizedMessage.includes(mode.toLowerCase()));
|
|
69
|
+
}
|
|
70
|
+
export function availableModesForSession(session) {
|
|
71
|
+
return session.supportedModeIds.map(modeInfoForId);
|
|
72
|
+
}
|
|
15
73
|
function asRecord(value, context) {
|
|
16
74
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
17
75
|
throw new Error(`${context} must be an object`);
|
|
@@ -205,6 +263,16 @@ export function parseCommandEnvelope(line) {
|
|
|
205
263
|
command: "get_status_snapshot",
|
|
206
264
|
session_id: expectString(raw, "session_id", "get_status_snapshot"),
|
|
207
265
|
};
|
|
266
|
+
case "get_context_usage":
|
|
267
|
+
return {
|
|
268
|
+
command: "get_context_usage",
|
|
269
|
+
session_id: expectString(raw, "session_id", "get_context_usage"),
|
|
270
|
+
};
|
|
271
|
+
case "reload_plugins":
|
|
272
|
+
return {
|
|
273
|
+
command: "reload_plugins",
|
|
274
|
+
session_id: expectString(raw, "session_id", "reload_plugins"),
|
|
275
|
+
};
|
|
208
276
|
case "mcp_status":
|
|
209
277
|
case "get_mcp_snapshot":
|
|
210
278
|
return {
|
|
@@ -341,6 +409,7 @@ function parseQuestionAnnotation(value) {
|
|
|
341
409
|
}
|
|
342
410
|
export function toPermissionMode(mode) {
|
|
343
411
|
if (mode === "default" ||
|
|
412
|
+
mode === "auto" ||
|
|
344
413
|
mode === "acceptEdits" ||
|
|
345
414
|
mode === "bypassPermissions" ||
|
|
346
415
|
mode === "plan" ||
|
|
@@ -349,10 +418,10 @@ export function toPermissionMode(mode) {
|
|
|
349
418
|
}
|
|
350
419
|
return null;
|
|
351
420
|
}
|
|
352
|
-
export function buildModeState(mode) {
|
|
421
|
+
export function buildModeState(session, mode) {
|
|
353
422
|
return {
|
|
354
423
|
current_mode_id: mode,
|
|
355
424
|
current_mode_name: MODE_NAMES[mode],
|
|
356
|
-
available_modes:
|
|
425
|
+
available_modes: availableModesForSession(session),
|
|
357
426
|
};
|
|
358
427
|
}
|
|
@@ -2,6 +2,7 @@ import { listSessions } from "@anthropic-ai/claude-agent-sdk";
|
|
|
2
2
|
import { buildModeState } from "./commands.js";
|
|
3
3
|
import { mapSdkSessions } from "./history.js";
|
|
4
4
|
import { bridgeLogger, LOG_TARGETS, logBridgeEventSent } from "./logger.js";
|
|
5
|
+
import { resolveCurrentModel } from "./session_lifecycle.js";
|
|
5
6
|
const SESSION_LIST_LIMIT = 50;
|
|
6
7
|
let sessionListingDir;
|
|
7
8
|
export function buildSessionListOptions(dir, limit = SESSION_LIST_LIMIT) {
|
|
@@ -28,6 +29,12 @@ export function failConnection(message, requestId) {
|
|
|
28
29
|
export function slashError(sessionId, message, requestId) {
|
|
29
30
|
writeEvent({ event: "slash_error", session_id: sessionId, message }, requestId);
|
|
30
31
|
}
|
|
32
|
+
export function emitRuntimeReloadCompleted(sessionId, requestId) {
|
|
33
|
+
writeEvent({ event: "runtime_reload_completed", session_id: sessionId }, requestId);
|
|
34
|
+
}
|
|
35
|
+
export function emitRuntimeReloadFailed(sessionId, message, requestId) {
|
|
36
|
+
writeEvent({ event: "runtime_reload_failed", session_id: sessionId, message }, requestId);
|
|
37
|
+
}
|
|
31
38
|
export function emitMcpOperationError(sessionId, error, requestId) {
|
|
32
39
|
writeEvent({ event: "mcp_operation_error", session_id: sessionId, error }, requestId);
|
|
33
40
|
}
|
|
@@ -92,18 +99,18 @@ function buildConnectBridgeEvent(session, eventName) {
|
|
|
92
99
|
event: "session_replaced",
|
|
93
100
|
session_id: session.sessionId,
|
|
94
101
|
cwd: session.cwd,
|
|
95
|
-
|
|
102
|
+
current_model: session.currentModel ?? resolveCurrentModel(session),
|
|
96
103
|
available_models: session.availableModels,
|
|
97
|
-
mode: session.mode ? buildModeState(session.mode) : null,
|
|
104
|
+
mode: session.mode ? buildModeState(session, session.mode) : null,
|
|
98
105
|
...(historyUpdates && historyUpdates.length > 0 ? { history_updates: historyUpdates } : {}),
|
|
99
106
|
}
|
|
100
107
|
: {
|
|
101
108
|
event: "connected",
|
|
102
109
|
session_id: session.sessionId,
|
|
103
110
|
cwd: session.cwd,
|
|
104
|
-
|
|
111
|
+
current_model: session.currentModel ?? resolveCurrentModel(session),
|
|
105
112
|
available_models: session.availableModels,
|
|
106
|
-
mode: session.mode ? buildModeState(session.mode) : null,
|
|
113
|
+
mode: session.mode ? buildModeState(session, session.mode) : null,
|
|
107
114
|
...(historyUpdates && historyUpdates.length > 0 ? { history_updates: historyUpdates } : {}),
|
|
108
115
|
};
|
|
109
116
|
}
|
|
@@ -29,14 +29,14 @@ 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) {
|
|
32
|
+
function pushResumeToolUse(updates, toolCalls, 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
|
-
const toolCall = createToolCall(toolUseId, name, input);
|
|
39
|
+
const toolCall = createToolCall(toolUseId, name, input, parentToolUseId);
|
|
40
40
|
toolCall.status = "in_progress";
|
|
41
41
|
toolCalls.set(toolUseId, toolCall);
|
|
42
42
|
updates.push({ type: "tool_call", tool_call: toolCall });
|
|
@@ -75,7 +75,7 @@ export function mapSdkSessionInfo(info) {
|
|
|
75
75
|
session_id: info.sessionId,
|
|
76
76
|
summary: summaryFromSession(info),
|
|
77
77
|
last_modified_ms: info.lastModified,
|
|
78
|
-
file_size_bytes: info.fileSize,
|
|
78
|
+
file_size_bytes: info.fileSize ?? 0,
|
|
79
79
|
...(nonEmptyTrimmed(info.cwd) ? { cwd: info.cwd?.trim() } : {}),
|
|
80
80
|
...(nonEmptyTrimmed(info.gitBranch) ? { git_branch: info.gitBranch?.trim() } : {}),
|
|
81
81
|
...(nonEmptyTrimmed(info.customTitle) ? { custom_title: info.customTitle?.trim() } : {}),
|
|
@@ -106,6 +106,11 @@ export function mapSessionMessagesToUpdates(messages) {
|
|
|
106
106
|
for (const message of messageCandidates(entry.message)) {
|
|
107
107
|
const roleCandidate = message.role;
|
|
108
108
|
const role = roleCandidate === "assistant" || roleCandidate === "user" ? roleCandidate : fallbackRole;
|
|
109
|
+
const parentToolUseId = typeof entry.parent_tool_use_id === "string"
|
|
110
|
+
? entry.parent_tool_use_id
|
|
111
|
+
: typeof message.parent_tool_use_id === "string"
|
|
112
|
+
? message.parent_tool_use_id
|
|
113
|
+
: null;
|
|
109
114
|
const content = Array.isArray(message.content) ? message.content : [];
|
|
110
115
|
for (const item of content) {
|
|
111
116
|
const block = asRecordOrNull(item);
|
|
@@ -121,7 +126,7 @@ export function mapSessionMessagesToUpdates(messages) {
|
|
|
121
126
|
continue;
|
|
122
127
|
}
|
|
123
128
|
if (isToolUseBlockType(blockType) && role === "assistant") {
|
|
124
|
-
pushResumeToolUse(updates, toolCalls, block);
|
|
129
|
+
pushResumeToolUse(updates, toolCalls, block, parentToolUseId);
|
|
125
130
|
continue;
|
|
126
131
|
}
|
|
127
132
|
if (TOOL_RESULT_TYPES.has(blockType)) {
|
|
@@ -104,6 +104,8 @@ function commandToolCallId(command) {
|
|
|
104
104
|
case "new_session":
|
|
105
105
|
case "elicitation_response":
|
|
106
106
|
case "get_status_snapshot":
|
|
107
|
+
case "get_context_usage":
|
|
108
|
+
case "reload_plugins":
|
|
107
109
|
case "mcp_status":
|
|
108
110
|
case "mcp_reconnect":
|
|
109
111
|
case "mcp_toggle":
|
|
@@ -131,10 +133,13 @@ function eventToolCallId(event) {
|
|
|
131
133
|
case "turn_complete":
|
|
132
134
|
case "turn_error":
|
|
133
135
|
case "slash_error":
|
|
136
|
+
case "runtime_reload_completed":
|
|
137
|
+
case "runtime_reload_failed":
|
|
134
138
|
case "session_replaced":
|
|
135
139
|
case "initialized":
|
|
136
140
|
case "sessions_listed":
|
|
137
141
|
case "status_snapshot":
|
|
142
|
+
case "context_usage":
|
|
138
143
|
case "mcp_snapshot":
|
|
139
144
|
return undefined;
|
|
140
145
|
}
|
|
@@ -163,6 +168,7 @@ function protocolEventLevel(event) {
|
|
|
163
168
|
case "mcp_operation_error":
|
|
164
169
|
case "turn_error":
|
|
165
170
|
case "slash_error":
|
|
171
|
+
case "runtime_reload_failed":
|
|
166
172
|
return "warn";
|
|
167
173
|
case "session_update":
|
|
168
174
|
case "permission_request":
|
|
@@ -174,6 +180,8 @@ function protocolEventLevel(event) {
|
|
|
174
180
|
return "trace";
|
|
175
181
|
case "sessions_listed":
|
|
176
182
|
case "status_snapshot":
|
|
183
|
+
case "context_usage":
|
|
184
|
+
case "runtime_reload_completed":
|
|
177
185
|
case "mcp_snapshot":
|
|
178
186
|
return "debug";
|
|
179
187
|
}
|