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 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
 
@@ -1,6 +1,6 @@
1
1
  # claude-rs agent-sdk bridge
2
2
 
3
- Initial scaffold for the NDJSON stdio bridge that will connect Rust (`claude-code-rust`) with `@anthropic-ai/claude-agent-sdk`.
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: MODE_OPTIONS,
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
- model_name: session.model,
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
- model_name: session.model,
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
  }