claude-code-rust 0.14.5 → 0.14.6
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 +6 -6
- package/agent-sdk/dist/bridge/command_dispatch.js +6 -0
- package/agent-sdk/dist/bridge/command_session_control.js +29 -3
- package/agent-sdk/dist/bridge/command_session_data.js +1 -1
- package/agent-sdk/dist/bridge/commands.js +1 -0
- package/agent-sdk/dist/bridge/logger.js +8 -0
- package/agent-sdk/dist/bridge/mcp.js +1 -0
- package/agent-sdk/dist/bridge/message_handlers.js +118 -3
- package/agent-sdk/dist/bridge/resource_links.js +102 -0
- package/agent-sdk/dist/bridge/session_lifecycle.js +7 -1
- package/agent-sdk/dist/bridge/shared.js +3 -2
- package/agent-sdk/dist/bridge/tasks.js +3 -0
- package/agent-sdk/dist/bridge/tool_calls.js +17 -1
- package/agent-sdk/dist/bridge/tooling.js +132 -29
- package/agent-sdk/dist/bridge.js +2 -1
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ A native Rust terminal interface for Claude Code. Drop-in replacement for Anthro
|
|
|
13
13
|
|
|
14
14
|
## About
|
|
15
15
|
|
|
16
|
-
Claude Code Rust replaces the stock Claude Code terminal interface with a native Rust binary built on [Ratatui](https://ratatui.rs/). It connects to the same Claude API through a local Agent SDK bridge. Core Claude Code functionality
|
|
16
|
+
Claude Code Rust replaces the stock Claude Code terminal interface with a native Rust binary built on [Ratatui](https://ratatui.rs/). It connects to the same Claude API through a local Agent SDK bridge. Core Claude Code functionality works unchanged, including tool calls, file editing, terminal commands, and permissions.
|
|
17
17
|
|
|
18
18
|
## Prerequisite
|
|
19
19
|
|
|
@@ -52,13 +52,13 @@ claude-rs
|
|
|
52
52
|
Full documentation is available at [srothgan.github.io/claude-code-rust](https://srothgan.github.io/claude-code-rust/).
|
|
53
53
|
|
|
54
54
|
> [!NOTE]
|
|
55
|
-
> **Agent SDK billing unchanged.** Anthropic has paused the previously announced Agent SDK credit change. For now nothing changes: Claude Agent SDK usage
|
|
55
|
+
> **Agent SDK billing unchanged.** Anthropic has paused the previously announced Agent SDK credit change. For now nothing changes: Claude Agent SDK usage (including `claude -p` and third-party apps like this one) still draws from your normal Claude subscription limits. See [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).
|
|
56
56
|
|
|
57
57
|
## Why
|
|
58
58
|
|
|
59
59
|
The stock Claude Code TUI runs on Node.js with React Ink, which renders by redrawing full frames over raw ANSI escape codes. This causes real, widely-reported problems:
|
|
60
60
|
|
|
61
|
-
- **Flickering**: The whole view is redrawn on every status update, causing constant flicker
|
|
61
|
+
- **Flickering**: The whole view is redrawn on every status update, causing constant flicker (bad enough to crash editors' integrated terminals during long sessions)
|
|
62
62
|
- **CPU**: Sustained high CPU even when idle, and runaway loops that spawn multiple background processes
|
|
63
63
|
- **Memory**: 200-400MB baseline (and climbing with conversation length) vs ~20-50MB for a native binary
|
|
64
64
|
- **Resize**: Window resizing leaves duplicated frames in scrollback, loses lines when shrinking, and can garble the display
|
|
@@ -66,7 +66,7 @@ The stock Claude Code TUI runs on Node.js with React Ink, which renders by redra
|
|
|
66
66
|
- **Scrollback**: Hijacks the terminal's native scrollback, erasing history you can no longer scroll back to
|
|
67
67
|
- **Paste**: Large pastes can flood stdout and freeze the terminal
|
|
68
68
|
|
|
69
|
-
Claude Code Rust addresses these with a native terminal UI that uses diffed, direct terminal control via Crossterm and Ratatui
|
|
69
|
+
Claude Code Rust addresses these with a native terminal UI that uses diffed, direct terminal control via Crossterm and Ratatui, with no full-frame redraws and no React Ink rendering loop.
|
|
70
70
|
|
|
71
71
|
## Documentation
|
|
72
72
|
|
|
@@ -96,9 +96,9 @@ This project is licensed under the [Apache License 2.0](LICENSE). Apache-2.0 was
|
|
|
96
96
|
|
|
97
97
|
This project is not affiliated with, endorsed by, or supported by Anthropic.
|
|
98
98
|
|
|
99
|
-
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
|
|
99
|
+
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.
|
|
100
100
|
|
|
101
|
-
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 any future changes Anthropic may make to how Agent SDK usage is metered. Other community projects do the same. As far as I can tell, using this project is fine
|
|
101
|
+
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 any future changes Anthropic may make to how Agent SDK usage is metered. 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.
|
|
102
102
|
|
|
103
103
|
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).
|
|
104
104
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { writeEvent } from "./events.js";
|
|
1
2
|
import { bridgeLogger, LOG_TARGETS } from "./logger.js";
|
|
2
3
|
export async function dispatchCancelTurnCommand(command, deps) {
|
|
3
4
|
const session = deps.sessionById(command.session_id);
|
|
@@ -9,6 +10,11 @@ export async function dispatchCancelTurnCommand(command, deps) {
|
|
|
9
10
|
const stillQueued = Array.isArray(receipt?.still_queued)
|
|
10
11
|
? receipt.still_queued.filter((entry) => typeof entry === "string")
|
|
11
12
|
: [];
|
|
13
|
+
writeEvent({
|
|
14
|
+
event: "turn_interrupt_receipt",
|
|
15
|
+
session_id: command.session_id,
|
|
16
|
+
still_queued: stillQueued,
|
|
17
|
+
}, deps.requestId);
|
|
12
18
|
if (stillQueued.length > 0) {
|
|
13
19
|
bridgeLogger.info({
|
|
14
20
|
target: LOG_TARGETS.APP_SESSION,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { buildModeState, markModeUnavailableForSession, permissionModeFailureLooksUnsupported, refreshSupportedModesForSession, toPermissionMode, } from "./commands.js";
|
|
2
2
|
import { dispatchCancelTurnCommand } from "./command_dispatch.js";
|
|
3
3
|
import { emitFastModeUpdate } from "./error_classification.js";
|
|
4
|
-
import { emitSessionUpdate, slashError } from "./events.js";
|
|
4
|
+
import { emitSessionUpdate, slashError, writeEvent } from "./events.js";
|
|
5
5
|
import { bridgeLogger, LOG_TARGETS } from "./logger.js";
|
|
6
6
|
import { emitCurrentModelUpdate, refreshCurrentModel, sessionById, shouldInvalidateResolvedRuntimeModel, } from "./session_lifecycle.js";
|
|
7
7
|
export async function handleSessionControlCommand(command, requestId, deps) {
|
|
@@ -38,12 +38,38 @@ export async function handleSessionControlCommand(command, requestId, deps) {
|
|
|
38
38
|
function handlePrompt(command, requestId, deps) {
|
|
39
39
|
const session = requireSession(command.session_id, requestId);
|
|
40
40
|
if (!session) {
|
|
41
|
+
writeEvent({
|
|
42
|
+
event: "user_message_rejected",
|
|
43
|
+
session_id: command.session_id,
|
|
44
|
+
message_uuid: command.message_uuid,
|
|
45
|
+
reason: "unknown session",
|
|
46
|
+
}, requestId);
|
|
41
47
|
return;
|
|
42
48
|
}
|
|
43
49
|
const message = deps.buildPromptUserMessage(command, session.sessionId);
|
|
44
|
-
if (message) {
|
|
45
|
-
|
|
50
|
+
if (!message) {
|
|
51
|
+
writeEvent({
|
|
52
|
+
event: "user_message_rejected",
|
|
53
|
+
session_id: session.sessionId,
|
|
54
|
+
message_uuid: command.message_uuid,
|
|
55
|
+
reason: "prompt contained no supported content",
|
|
56
|
+
}, requestId);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!session.input.enqueue(message)) {
|
|
60
|
+
writeEvent({
|
|
61
|
+
event: "user_message_rejected",
|
|
62
|
+
session_id: session.sessionId,
|
|
63
|
+
message_uuid: command.message_uuid,
|
|
64
|
+
reason: "session input is closed",
|
|
65
|
+
}, requestId);
|
|
66
|
+
return;
|
|
46
67
|
}
|
|
68
|
+
writeEvent({
|
|
69
|
+
event: "user_message_queued",
|
|
70
|
+
session_id: session.sessionId,
|
|
71
|
+
message_uuid: command.message_uuid,
|
|
72
|
+
}, requestId);
|
|
47
73
|
}
|
|
48
74
|
async function setModel(command, requestId) {
|
|
49
75
|
const session = requireSession(command.session_id, requestId);
|
|
@@ -107,7 +107,7 @@ async function getContextUsage(command, requestId) {
|
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
109
109
|
try {
|
|
110
|
-
const usage = await session.query.getContextUsage();
|
|
110
|
+
const usage = await session.query.getContextUsage({ detail: "summary" });
|
|
111
111
|
if (typeof usage.model === "string" && usage.model.trim().length > 0) {
|
|
112
112
|
session.resolvedRuntimeModelId = usage.model.trim();
|
|
113
113
|
refreshCurrentModel(session, true);
|
|
@@ -262,6 +262,7 @@ export function parseCommandEnvelope(line) {
|
|
|
262
262
|
return {
|
|
263
263
|
command: "prompt",
|
|
264
264
|
session_id: expectString(raw, "session_id", "prompt"),
|
|
265
|
+
message_uuid: expectString(raw, "message_uuid", "prompt"),
|
|
265
266
|
chunks: parsePromptChunks(raw, "prompt"),
|
|
266
267
|
};
|
|
267
268
|
case "cancel_turn":
|
|
@@ -146,6 +146,10 @@ function eventToolCallId(event) {
|
|
|
146
146
|
case "mcp_auth_redirect":
|
|
147
147
|
case "mcp_operation_error":
|
|
148
148
|
case "mcp_set_servers_result":
|
|
149
|
+
case "user_message_queued":
|
|
150
|
+
case "user_message_started":
|
|
151
|
+
case "user_message_rejected":
|
|
152
|
+
case "turn_interrupt_receipt":
|
|
149
153
|
case "turn_complete":
|
|
150
154
|
case "turn_error":
|
|
151
155
|
case "slash_error":
|
|
@@ -199,6 +203,7 @@ function protocolEventLevel(event) {
|
|
|
199
203
|
case "elicitation_request":
|
|
200
204
|
case "elicitation_complete":
|
|
201
205
|
case "mcp_auth_redirect":
|
|
206
|
+
case "user_message_started":
|
|
202
207
|
case "turn_complete":
|
|
203
208
|
return "trace";
|
|
204
209
|
case "sessions_listed":
|
|
@@ -209,6 +214,9 @@ function protocolEventLevel(event) {
|
|
|
209
214
|
case "rewind_result":
|
|
210
215
|
case "runtime_reload_completed":
|
|
211
216
|
case "mcp_set_servers_result":
|
|
217
|
+
case "user_message_queued":
|
|
218
|
+
case "user_message_rejected":
|
|
219
|
+
case "turn_interrupt_receipt":
|
|
212
220
|
case "mcp_snapshot":
|
|
213
221
|
return "debug";
|
|
214
222
|
}
|
|
@@ -289,6 +289,7 @@ export async function handleMcpSetServersCommand(session, command, requestId) {
|
|
|
289
289
|
logMcpFailure("mcp_servers_set_failed", "failed to update MCP server configuration", command.session_id, message, requestId, { server_count: Object.keys(command.servers).length });
|
|
290
290
|
emitMcpOperationError(command.session_id, { operation: "set-servers", message }, requestId);
|
|
291
291
|
slashError(command.session_id, `failed to set MCP servers: ${message}`, requestId);
|
|
292
|
+
await handleMcpStatusCommand(session, requestId, "mcp_set_servers");
|
|
292
293
|
}
|
|
293
294
|
}
|
|
294
295
|
export async function handleMcpAuthenticateCommand(session, command, requestId) {
|
|
@@ -2,7 +2,7 @@ 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
4
|
import { TOOL_RESULT_TYPES, isToolSearchToolName, isToolSearchToolResultType, unwrapToolUseResult, parseToolNonExecutionMetadata, } from "./tooling.js";
|
|
5
|
-
import { emitToolCall, emitToolCallUpdate, emitToolResultUpdate, finalizeOpenToolCalls, emitToolProgressUpdate, emitToolSummaryUpdate, ensureToolCallVisible, resolveTaskToolUseId, defersTaskNotificationCompletion, toolAcceptsTaskLifecycle, taskProgressText, taskUpdatedFields, } from "./tool_calls.js";
|
|
5
|
+
import { emitToolCall, emitToolCallUpdate, emitToolResultUpdate, finalizeOpenToolCalls, emitToolProgressUpdate, emitToolSummaryUpdate, ensureToolCallVisible, resolveTaskToolUseId, defersTaskNotificationCompletion, toolAcceptsTaskLifecycle, toolAcceptsTerminalTaskNotification, toolPreservesTaskNotificationOutput, taskProgressText, taskUpdatedFields, } from "./tool_calls.js";
|
|
6
6
|
import { applyBackgroundTasksChanged, applyTaskLifecycleState, } from "./tasks.js";
|
|
7
7
|
import { linkTaskToolUse, unlinkTaskToolUse } from "./task_links.js";
|
|
8
8
|
import { emitAuthRequired, classifyTurnErrorKind, emitFastModeUpdate, emitFastModeUpdateIfChanged, setFastModeSnapshotIfChanged, } from "./error_classification.js";
|
|
@@ -13,6 +13,7 @@ import { looksLikeAuthRequired } from "./auth.js";
|
|
|
13
13
|
import { emitCurrentModelUpdate, refreshCurrentModel, updateSessionId, } from "./session_lifecycle.js";
|
|
14
14
|
import { bridgeLogger, LOG_TARGETS } from "./logger.js";
|
|
15
15
|
import { emitMcpSnapshotFromStatuses } from "./mcp.js";
|
|
16
|
+
import { appendResourceLinks } from "./resource_links.js";
|
|
16
17
|
export function textFromPrompt(command) {
|
|
17
18
|
const chunks = command.chunks ?? [];
|
|
18
19
|
return chunks
|
|
@@ -88,6 +89,7 @@ function sdkTaskMetadata(msg) {
|
|
|
88
89
|
...(typeof msg.is_backgrounded === "boolean"
|
|
89
90
|
? { is_backgrounded: msg.is_backgrounded }
|
|
90
91
|
: {}),
|
|
92
|
+
...(typeof msg.ambient === "boolean" ? { ambient: msg.ambient } : {}),
|
|
91
93
|
...(spawnDepth !== undefined ? { spawn_depth: spawnDepth } : {}),
|
|
92
94
|
};
|
|
93
95
|
return Object.keys(taskMetadata).length > 0 ? taskMetadata : undefined;
|
|
@@ -98,6 +100,44 @@ function sdkMessageOriginKind(msg) {
|
|
|
98
100
|
: null;
|
|
99
101
|
return typeof origin?.kind === "string" ? origin.kind : undefined;
|
|
100
102
|
}
|
|
103
|
+
function diagnosticToken(value) {
|
|
104
|
+
return typeof value === "string" && /^[a-z][a-z0-9_-]{0,63}$/i.test(value)
|
|
105
|
+
? value
|
|
106
|
+
: undefined;
|
|
107
|
+
}
|
|
108
|
+
export function sdkMessageDiagnosticFields(msg) {
|
|
109
|
+
const event = asRecordOrNull(msg.event);
|
|
110
|
+
const delta = asRecordOrNull(event?.delta);
|
|
111
|
+
const uuidFields = Object.fromEntries(Object.entries(msg).filter(([key, value]) => key.toLowerCase().includes("uuid") && typeof value === "string"));
|
|
112
|
+
const hasParentToolUseId = Object.hasOwn(msg, "parent_tool_use_id");
|
|
113
|
+
const parentToolUseId = trimmedStringField(msg, "parent_tool_use_id");
|
|
114
|
+
return {
|
|
115
|
+
sdk_type: typeof msg.type === "string" ? msg.type : undefined,
|
|
116
|
+
sdk_subtype: typeof msg.subtype === "string" ? msg.subtype : undefined,
|
|
117
|
+
sdk_uuid: trimmedStringField(msg, "uuid"),
|
|
118
|
+
user_message_uuid: trimmedStringField(msg, "user_message_uuid"),
|
|
119
|
+
parent_tool_use_id: parentToolUseId,
|
|
120
|
+
parent_tool_use_scope: !hasParentToolUseId
|
|
121
|
+
? "absent"
|
|
122
|
+
: msg.parent_tool_use_id === null
|
|
123
|
+
? "root"
|
|
124
|
+
: parentToolUseId
|
|
125
|
+
? "child"
|
|
126
|
+
: "other",
|
|
127
|
+
request_id: trimmedStringField(msg, "request_id"),
|
|
128
|
+
origin_kind: sdkMessageOriginKind(msg),
|
|
129
|
+
inner_event_type: diagnosticToken(event?.type),
|
|
130
|
+
inner_delta_type: diagnosticToken(delta?.type),
|
|
131
|
+
lifecycle_status: diagnosticToken(msg.status),
|
|
132
|
+
lifecycle_state: diagnosticToken(msg.state),
|
|
133
|
+
lifecycle_operation: diagnosticToken(msg.operation),
|
|
134
|
+
lifecycle_reason: diagnosticToken(msg.reason),
|
|
135
|
+
sdk_keys: Object.keys(msg).sort(),
|
|
136
|
+
...(Object.keys(uuidFields).length > 0
|
|
137
|
+
? { sdk_uuid_fields: uuidFields }
|
|
138
|
+
: {}),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
101
141
|
function externalMessageUpdateFromSdkUser(msg) {
|
|
102
142
|
const origin = asRecordOrNull(msg.origin);
|
|
103
143
|
if (!origin) {
|
|
@@ -490,7 +530,10 @@ export function handleTaskSystemMessage(session, subtype, msg) {
|
|
|
490
530
|
return true;
|
|
491
531
|
}
|
|
492
532
|
const toolCall = ensureToolCallVisible(session, toolUseId, "Agent", {});
|
|
493
|
-
|
|
533
|
+
const acceptsLifecycle = toolAcceptsTaskLifecycle(toolCall);
|
|
534
|
+
const acceptsTerminalNotification = subtype === "task_notification" &&
|
|
535
|
+
toolAcceptsTerminalTaskNotification(toolCall);
|
|
536
|
+
if (!acceptsLifecycle && !acceptsTerminalNotification) {
|
|
494
537
|
if (taskId) {
|
|
495
538
|
unlinkTaskToolUse(session, taskId);
|
|
496
539
|
}
|
|
@@ -576,12 +619,16 @@ export function handleTaskSystemMessage(session, subtype, msg) {
|
|
|
576
619
|
if (messageTaskMetadata) {
|
|
577
620
|
fields.task_metadata = messageTaskMetadata;
|
|
578
621
|
}
|
|
579
|
-
if (summary) {
|
|
622
|
+
if (summary && !toolPreservesTaskNotificationOutput(toolCall)) {
|
|
580
623
|
fields.raw_output = summary;
|
|
581
624
|
fields.content = [
|
|
582
625
|
{ type: "content", content: { type: "text", text: summary } },
|
|
583
626
|
];
|
|
584
627
|
}
|
|
628
|
+
const contentWithResourceLinks = appendResourceLinks(fields.content, msg.resource_links);
|
|
629
|
+
if (contentWithResourceLinks !== undefined) {
|
|
630
|
+
fields.content = contentWithResourceLinks;
|
|
631
|
+
}
|
|
585
632
|
if (Object.keys(fields).length > 0) {
|
|
586
633
|
emitToolCallUpdate(session, toolUseId, fields, "task_notification");
|
|
587
634
|
}
|
|
@@ -908,6 +955,40 @@ export function handleAssistantMessage(session, message) {
|
|
|
908
955
|
}
|
|
909
956
|
}
|
|
910
957
|
}
|
|
958
|
+
function emitUserMessageStarted(session, message, source) {
|
|
959
|
+
const messageUuid = trimmedStringField(message, "user_message_uuid");
|
|
960
|
+
if (!messageUuid) {
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
emitUserMessageStartedForUuid(session, messageUuid, source);
|
|
964
|
+
}
|
|
965
|
+
function emitUserMessageStartedForUuid(session, messageUuid, source) {
|
|
966
|
+
writeEvent({
|
|
967
|
+
event: "user_message_started",
|
|
968
|
+
session_id: session.sessionId,
|
|
969
|
+
message_uuid: messageUuid,
|
|
970
|
+
source,
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
function handleCommandLifecycleMessage(session, message) {
|
|
974
|
+
const state = trimmedStringField(message, "state");
|
|
975
|
+
const commandUuid = trimmedStringField(message, "command_uuid");
|
|
976
|
+
if (state !== "started") {
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
if (!commandUuid) {
|
|
980
|
+
bridgeLogger.warn({
|
|
981
|
+
target: LOG_TARGETS.BRIDGE_SDK,
|
|
982
|
+
eventName: "sdk_command_lifecycle_start_invalid",
|
|
983
|
+
message: "SDK command lifecycle start omitted its command UUID",
|
|
984
|
+
outcome: "ignored",
|
|
985
|
+
sessionId: session.sessionId,
|
|
986
|
+
fields: sdkMessageDiagnosticFields(message),
|
|
987
|
+
});
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
emitUserMessageStartedForUuid(session, commandUuid, "command_lifecycle");
|
|
991
|
+
}
|
|
911
992
|
function messageToolUseResult(message) {
|
|
912
993
|
if (Object.hasOwn(message, "toolUseResult")) {
|
|
913
994
|
return message.toolUseResult;
|
|
@@ -960,8 +1041,13 @@ export function handleUserToolResultBlocks(session, message) {
|
|
|
960
1041
|
return handled;
|
|
961
1042
|
}
|
|
962
1043
|
export function handleResultMessage(session, message) {
|
|
1044
|
+
if (message.parent_tool_use_id === null ||
|
|
1045
|
+
message.parent_tool_use_id === undefined) {
|
|
1046
|
+
emitUserMessageStarted(session, message, "result");
|
|
1047
|
+
}
|
|
963
1048
|
emitFastModeUpdateIfChanged(session, message.fast_mode_state, message.fast_mode_disabled_reason);
|
|
964
1049
|
const terminalReason = terminalReasonFromValue(message.terminal_reason);
|
|
1050
|
+
const queuedTurnCount = nonNegativeIntegerField(message, "queued_turn_count");
|
|
965
1051
|
const subtype = typeof message.subtype === "string" ? message.subtype : "";
|
|
966
1052
|
if (subtype === "success") {
|
|
967
1053
|
session.lastAssistantError = undefined;
|
|
@@ -969,6 +1055,9 @@ export function handleResultMessage(session, message) {
|
|
|
969
1055
|
writeEvent({
|
|
970
1056
|
event: "turn_complete",
|
|
971
1057
|
session_id: session.sessionId,
|
|
1058
|
+
...(queuedTurnCount !== undefined
|
|
1059
|
+
? { queued_turn_count: queuedTurnCount }
|
|
1060
|
+
: {}),
|
|
972
1061
|
...(terminalReason ? { terminal_reason: terminalReason } : {}),
|
|
973
1062
|
});
|
|
974
1063
|
return;
|
|
@@ -1013,6 +1102,9 @@ export function handleResultMessage(session, message) {
|
|
|
1013
1102
|
event: "turn_error",
|
|
1014
1103
|
session_id: session.sessionId,
|
|
1015
1104
|
message: errors.length > 0 ? errors.join("\n") : fallback,
|
|
1105
|
+
...(queuedTurnCount !== undefined
|
|
1106
|
+
? { queued_turn_count: queuedTurnCount }
|
|
1107
|
+
: {}),
|
|
1016
1108
|
error_kind: errorKind,
|
|
1017
1109
|
...(subtype ? { sdk_result_subtype: subtype } : {}),
|
|
1018
1110
|
...(assistantError ? { assistant_error: assistantError } : {}),
|
|
@@ -1406,6 +1498,10 @@ export function handleSdkMessage(session, message) {
|
|
|
1406
1498
|
return;
|
|
1407
1499
|
}
|
|
1408
1500
|
if (type === "stream_event") {
|
|
1501
|
+
if (msg.parent_tool_use_id === null ||
|
|
1502
|
+
msg.parent_tool_use_id === undefined) {
|
|
1503
|
+
emitUserMessageStarted(session, msg, "stream_event");
|
|
1504
|
+
}
|
|
1409
1505
|
if (msg.event && typeof msg.event === "object") {
|
|
1410
1506
|
const parentToolUseId = typeof msg.parent_tool_use_id === "string"
|
|
1411
1507
|
? msg.parent_tool_use_id
|
|
@@ -1580,10 +1676,29 @@ export function handleSdkMessage(session, message) {
|
|
|
1580
1676
|
if (msg.error === "authentication_failed") {
|
|
1581
1677
|
emitAuthRequired(session);
|
|
1582
1678
|
}
|
|
1679
|
+
if (msg.parent_tool_use_id === null ||
|
|
1680
|
+
msg.parent_tool_use_id === undefined) {
|
|
1681
|
+
emitUserMessageStarted(session, msg, "assistant");
|
|
1682
|
+
}
|
|
1583
1683
|
handleAssistantMessage(session, msg);
|
|
1584
1684
|
return;
|
|
1585
1685
|
}
|
|
1586
1686
|
if (type === "result") {
|
|
1587
1687
|
handleResultMessage(session, msg);
|
|
1688
|
+
return;
|
|
1588
1689
|
}
|
|
1690
|
+
// Bundled Claude Code 2.1.258 emits this runtime frame even though the
|
|
1691
|
+
// corresponding SDK 0.3.258 TypeScript union does not declare it.
|
|
1692
|
+
if (type === "command_lifecycle") {
|
|
1693
|
+
handleCommandLifecycleMessage(session, msg);
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
bridgeLogger.debug({
|
|
1697
|
+
target: LOG_TARGETS.BRIDGE_SDK,
|
|
1698
|
+
eventName: "sdk_message_unhandled",
|
|
1699
|
+
message: "SDK message ignored by explicit top-level fallback policy",
|
|
1700
|
+
outcome: "ignored",
|
|
1701
|
+
sessionId: session.sessionId,
|
|
1702
|
+
fields: sdkMessageDiagnosticFields(msg),
|
|
1703
|
+
});
|
|
1589
1704
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { asRecordOrNull } from "./shared.js";
|
|
2
|
+
const MAX_RESOURCE_LINKS = 50;
|
|
3
|
+
const MAX_RESOURCE_LINK_BYTES = 64 * 1024;
|
|
4
|
+
function nonEmptyString(value) {
|
|
5
|
+
return typeof value === "string" && value.trim().length > 0
|
|
6
|
+
? value.trim()
|
|
7
|
+
: undefined;
|
|
8
|
+
}
|
|
9
|
+
function jsonValue(value) {
|
|
10
|
+
if (value === null ||
|
|
11
|
+
typeof value === "string" ||
|
|
12
|
+
typeof value === "boolean") {
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
if (typeof value === "number") {
|
|
16
|
+
return Number.isFinite(value) ? value : undefined;
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
const entries = [];
|
|
20
|
+
for (const entry of value) {
|
|
21
|
+
const parsed = jsonValue(entry);
|
|
22
|
+
if (parsed === undefined) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
entries.push(parsed);
|
|
26
|
+
}
|
|
27
|
+
return entries;
|
|
28
|
+
}
|
|
29
|
+
const record = asRecordOrNull(value);
|
|
30
|
+
if (!record) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const parsed = [];
|
|
34
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
35
|
+
const value = jsonValue(entry);
|
|
36
|
+
if (value === undefined) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
parsed.push([key, value]);
|
|
40
|
+
}
|
|
41
|
+
return Object.fromEntries(parsed);
|
|
42
|
+
}
|
|
43
|
+
function resourceLinkContent(value) {
|
|
44
|
+
const link = asRecordOrNull(value);
|
|
45
|
+
const uri = nonEmptyString(link?.uri);
|
|
46
|
+
const name = nonEmptyString(link?.name);
|
|
47
|
+
if (!link || !uri || !name) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const title = nonEmptyString(link.title);
|
|
51
|
+
const description = nonEmptyString(link.description);
|
|
52
|
+
const mimeType = nonEmptyString(link.mimeType);
|
|
53
|
+
const size = typeof link.size === "number" &&
|
|
54
|
+
Number.isSafeInteger(link.size) &&
|
|
55
|
+
link.size >= 0
|
|
56
|
+
? link.size
|
|
57
|
+
: undefined;
|
|
58
|
+
const annotationsValue = link.annotations === undefined ? undefined : jsonValue(link.annotations);
|
|
59
|
+
const annotations = asRecordOrNull(annotationsValue);
|
|
60
|
+
if (link.annotations !== undefined && !annotations) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
type: "resource_link",
|
|
65
|
+
uri,
|
|
66
|
+
name,
|
|
67
|
+
...(title ? { title } : {}),
|
|
68
|
+
...(description ? { description } : {}),
|
|
69
|
+
...(mimeType ? { mime_type: mimeType } : {}),
|
|
70
|
+
...(size !== undefined ? { size } : {}),
|
|
71
|
+
...(annotations ? { annotations } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function resourceLinkContents(value) {
|
|
75
|
+
if (!Array.isArray(value)) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
const links = [];
|
|
79
|
+
let serializedBytes = 2;
|
|
80
|
+
for (const entry of value) {
|
|
81
|
+
if (links.length >= MAX_RESOURCE_LINKS) {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
const link = resourceLinkContent(entry);
|
|
85
|
+
if (!link) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const linkBytes = Buffer.byteLength(JSON.stringify(link));
|
|
89
|
+
const separatorBytes = links.length > 0 ? 1 : 0;
|
|
90
|
+
if (serializedBytes + separatorBytes + linkBytes >
|
|
91
|
+
MAX_RESOURCE_LINK_BYTES) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
serializedBytes += separatorBytes + linkBytes;
|
|
95
|
+
links.push(link);
|
|
96
|
+
}
|
|
97
|
+
return links;
|
|
98
|
+
}
|
|
99
|
+
export function appendResourceLinks(content, value) {
|
|
100
|
+
const links = resourceLinkContents(value);
|
|
101
|
+
return links.length > 0 ? [...(content ?? []), ...links] : content;
|
|
102
|
+
}
|
|
@@ -344,6 +344,9 @@ export async function createSession(params) {
|
|
|
344
344
|
history_update_count: historyUpdateCount,
|
|
345
345
|
stale_session_count: staleSessionCount,
|
|
346
346
|
stale_session_before_register_count: staleSessionBeforeRegisterCount,
|
|
347
|
+
sdk_debug_enabled: enableSdkDebug,
|
|
348
|
+
sdk_debug_file_configured: Boolean(sdkDebugFile),
|
|
349
|
+
sdk_spawn_debug_enabled: enableSpawnDebug,
|
|
347
350
|
},
|
|
348
351
|
});
|
|
349
352
|
try {
|
|
@@ -608,7 +611,7 @@ export async function awaitSessionInitialization(session) {
|
|
|
608
611
|
// the protocol barrier proving that startup messages have reached the SDK; yield
|
|
609
612
|
// once more so the bridge's query consumer can process the already-enqueued result.
|
|
610
613
|
try {
|
|
611
|
-
await session.query.getContextUsage();
|
|
614
|
+
await session.query.getContextUsage({ detail: "summary" });
|
|
612
615
|
}
|
|
613
616
|
catch (error) {
|
|
614
617
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -762,6 +765,9 @@ function systemPromptFromLaunchSettings(launchSettings) {
|
|
|
762
765
|
type: "preset",
|
|
763
766
|
preset: "claude_code",
|
|
764
767
|
append: appendLines.join(" "),
|
|
768
|
+
// Keep the prompt prefix stable across resume. Updated host language or guard text
|
|
769
|
+
// intentionally takes effect after SDK compaction or in a new session.
|
|
770
|
+
snapshot: true,
|
|
765
771
|
};
|
|
766
772
|
}
|
|
767
773
|
export function buildQueryOptions(params) {
|
|
@@ -10,14 +10,15 @@ export class AsyncQueue {
|
|
|
10
10
|
closed = false;
|
|
11
11
|
enqueue(item) {
|
|
12
12
|
if (this.closed) {
|
|
13
|
-
return;
|
|
13
|
+
return false;
|
|
14
14
|
}
|
|
15
15
|
const waiter = this.waiters.shift();
|
|
16
16
|
if (waiter) {
|
|
17
17
|
waiter({ value: item, done: false });
|
|
18
|
-
return;
|
|
18
|
+
return true;
|
|
19
19
|
}
|
|
20
20
|
this.items.push(item);
|
|
21
|
+
return true;
|
|
21
22
|
}
|
|
22
23
|
close() {
|
|
23
24
|
if (this.closed) {
|
|
@@ -844,6 +844,7 @@ function lifecycleMetadata(msg) {
|
|
|
844
844
|
"total_paused_ms",
|
|
845
845
|
"blocked",
|
|
846
846
|
"parent_agent_id",
|
|
847
|
+
"ambient",
|
|
847
848
|
]) {
|
|
848
849
|
copyValue(msg, key);
|
|
849
850
|
copyValue(patch, key);
|
|
@@ -912,6 +913,7 @@ export function applyBackgroundTasksChanged(session, msg) {
|
|
|
912
913
|
}
|
|
913
914
|
const taskType = nonEmptyString(task.task_type);
|
|
914
915
|
const description = nonEmptyString(task.description);
|
|
916
|
+
const ambient = typeof task.ambient === "boolean" ? task.ambient : undefined;
|
|
915
917
|
return {
|
|
916
918
|
task_id: taskId,
|
|
917
919
|
subject: description ?? taskType ?? taskId,
|
|
@@ -922,6 +924,7 @@ export function applyBackgroundTasksChanged(session, msg) {
|
|
|
922
924
|
metadata: {
|
|
923
925
|
[BACKGROUND_TASK_METADATA_KEY]: true,
|
|
924
926
|
...(taskType ? { task_type: taskType } : {}),
|
|
927
|
+
...(ambient !== undefined ? { ambient } : {}),
|
|
925
928
|
},
|
|
926
929
|
};
|
|
927
930
|
})
|
|
@@ -4,6 +4,7 @@ import { asRecordOrNull } from "./shared.js";
|
|
|
4
4
|
import { applyTaskToolResult } from "./tasks.js";
|
|
5
5
|
import { activeTaskIdForToolUse, linkTaskToolUse, unlinkTaskToolUse, } from "./task_links.js";
|
|
6
6
|
import { applyToolNonExecutionMetadata, backgroundToolLaunchTaskIdFromResult, buildToolResultFields, createToolCall, } from "./tooling.js";
|
|
7
|
+
import { appendResourceLinks } from "./resource_links.js";
|
|
7
8
|
const TOOL_SUMMARY_TOOL_NAMES = new Set([
|
|
8
9
|
"Agent",
|
|
9
10
|
"Task",
|
|
@@ -62,6 +63,16 @@ export function toolAcceptsTaskLifecycle(base) {
|
|
|
62
63
|
const baseToolName = toolName(base);
|
|
63
64
|
return Boolean(baseToolName && TASK_LIFECYCLE_TOOL_NAMES.has(baseToolName));
|
|
64
65
|
}
|
|
66
|
+
export function toolAcceptsTerminalTaskNotification(base) {
|
|
67
|
+
const baseToolName = toolName(base);
|
|
68
|
+
return Boolean(baseToolName &&
|
|
69
|
+
(TASK_LIFECYCLE_TOOL_NAMES.has(baseToolName) ||
|
|
70
|
+
baseToolName === "Bash" ||
|
|
71
|
+
baseToolName.startsWith("mcp__")));
|
|
72
|
+
}
|
|
73
|
+
export function toolPreservesTaskNotificationOutput(base) {
|
|
74
|
+
return toolName(base) === "Bash";
|
|
75
|
+
}
|
|
65
76
|
export function defersTaskNotificationCompletion(base) {
|
|
66
77
|
const baseToolName = toolName(base);
|
|
67
78
|
return baseToolName === "Agent" || baseToolName === "Task";
|
|
@@ -353,6 +364,11 @@ export function emitToolResultUpdate(session, toolUseId, isError, rawContent, ra
|
|
|
353
364
|
const base = session.toolCalls.get(toolUseId);
|
|
354
365
|
const baseToolName = toolNameFromMeta(base?.meta) ?? "";
|
|
355
366
|
const fields = buildToolResultFields(isError, rawContent, base, rawResult, taskTitleContext(session, baseToolName, asRecordOrNull(base?.raw_input) ?? {}));
|
|
367
|
+
const resultRecord = asRecordOrNull(rawResult);
|
|
368
|
+
const contentWithResourceLinks = appendResourceLinks(fields.content, resultRecord?.resourceLinks);
|
|
369
|
+
if (contentWithResourceLinks !== undefined) {
|
|
370
|
+
fields.content = contentWithResourceLinks;
|
|
371
|
+
}
|
|
356
372
|
applyToolNonExecutionMetadata(fields, nonExecutionMetadata);
|
|
357
373
|
if (!isError && !nonExecutionMetadata) {
|
|
358
374
|
const taskId = backgroundToolLaunchTaskIdFromResult(baseToolName, rawResult, rawContent);
|
|
@@ -374,7 +390,7 @@ export function finalizeOpenToolCalls(session, status) {
|
|
|
374
390
|
if (toolCall.status !== "pending" && toolCall.status !== "in_progress") {
|
|
375
391
|
continue;
|
|
376
392
|
}
|
|
377
|
-
if (
|
|
393
|
+
if (toolAcceptsTerminalTaskNotification(toolCall) &&
|
|
378
394
|
activeTaskIdForToolUse(session, toolUseId)) {
|
|
379
395
|
continue;
|
|
380
396
|
}
|
|
@@ -843,49 +843,87 @@ function editDiffFromInput(rawInput) {
|
|
|
843
843
|
},
|
|
844
844
|
];
|
|
845
845
|
}
|
|
846
|
-
function
|
|
847
|
-
|
|
846
|
+
function boundedDisplayText(value, maxCharacters = 240) {
|
|
847
|
+
return value.length > maxCharacters
|
|
848
|
+
? `${value.slice(0, maxCharacters - 3)}...`
|
|
849
|
+
: value;
|
|
850
|
+
}
|
|
851
|
+
function writeResultPresentation(rawContent) {
|
|
852
|
+
const candidates = resultRecordCandidates(rawContent, undefined);
|
|
853
|
+
const parsed = parseJsonCandidate(rawContent);
|
|
854
|
+
if (parsed !== undefined) {
|
|
855
|
+
candidates.push(...resultRecordCandidates(parsed, undefined));
|
|
856
|
+
}
|
|
848
857
|
for (const candidate of candidates) {
|
|
849
|
-
const record =
|
|
850
|
-
if (!record) {
|
|
851
|
-
continue;
|
|
852
|
-
}
|
|
858
|
+
const record = candidate;
|
|
853
859
|
const filePath = typeof record.filePath === "string"
|
|
854
860
|
? record.filePath
|
|
855
861
|
: typeof record.file_path === "string"
|
|
856
862
|
? record.file_path
|
|
857
863
|
: "";
|
|
858
|
-
const content = typeof record.content === "string" ? record.content :
|
|
864
|
+
const content = typeof record.content === "string" ? record.content : undefined;
|
|
859
865
|
const originalRaw = "originalFile" in record
|
|
860
866
|
? record.originalFile
|
|
861
867
|
: "original_file" in record
|
|
862
868
|
? record.original_file
|
|
863
869
|
: undefined;
|
|
864
|
-
const gitDiff = asRecordOrNull(record.gitDiff);
|
|
870
|
+
const gitDiff = asRecordOrNull(record.gitDiff ?? record.git_diff);
|
|
865
871
|
const repository = typeof gitDiff?.repository === "string" &&
|
|
866
872
|
gitDiff.repository.trim().length > 0
|
|
867
873
|
? gitDiff.repository.trim()
|
|
868
874
|
: undefined;
|
|
869
|
-
if (!filePath ||
|
|
875
|
+
if (!filePath || originalRaw === undefined) {
|
|
870
876
|
continue;
|
|
871
877
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
878
|
+
if (originalRaw === null) {
|
|
879
|
+
if (record.type === "create" && content !== undefined) {
|
|
880
|
+
return {
|
|
881
|
+
kind: "diff",
|
|
882
|
+
content: [
|
|
883
|
+
{
|
|
884
|
+
type: "diff",
|
|
885
|
+
old_path: filePath,
|
|
886
|
+
new_path: filePath,
|
|
887
|
+
old: "",
|
|
888
|
+
new: content,
|
|
889
|
+
...(repository ? { repository } : {}),
|
|
890
|
+
},
|
|
891
|
+
],
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
return {
|
|
895
|
+
kind: "text",
|
|
896
|
+
text: record.type === "create"
|
|
897
|
+
? `Created ${boundedDisplayText(filePath)}; the written content was unavailable, so a diff could not be displayed.`
|
|
898
|
+
: `Updated ${boundedDisplayText(filePath)}; the previous content was unavailable, so a diff could not be displayed.`,
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
if (content === undefined) {
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
if (typeof originalRaw === "string") {
|
|
905
|
+
if (originalRaw === content) {
|
|
906
|
+
return {
|
|
907
|
+
kind: "text",
|
|
908
|
+
text: `No changes to ${boundedDisplayText(filePath)}.`,
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
return {
|
|
912
|
+
kind: "diff",
|
|
913
|
+
content: [
|
|
914
|
+
{
|
|
915
|
+
type: "diff",
|
|
916
|
+
old_path: filePath,
|
|
917
|
+
new_path: filePath,
|
|
918
|
+
old: originalRaw,
|
|
919
|
+
new: content,
|
|
920
|
+
...(repository ? { repository } : {}),
|
|
921
|
+
},
|
|
922
|
+
],
|
|
923
|
+
};
|
|
924
|
+
}
|
|
887
925
|
}
|
|
888
|
-
return
|
|
926
|
+
return undefined;
|
|
889
927
|
}
|
|
890
928
|
function editDiffFromResult(rawResult, rawInput) {
|
|
891
929
|
const input = asRecordOrNull(rawInput);
|
|
@@ -1741,7 +1779,11 @@ function hasConcreteReplField(record) {
|
|
|
1741
1779
|
"stderr" in record ||
|
|
1742
1780
|
"registeredTools" in record ||
|
|
1743
1781
|
"images" in record ||
|
|
1744
|
-
"documents" in record
|
|
1782
|
+
"documents" in record ||
|
|
1783
|
+
"imagesOmitted" in record ||
|
|
1784
|
+
"imagePagesFailed" in record ||
|
|
1785
|
+
"imagePagesFailedOmitted" in record ||
|
|
1786
|
+
"documentsOmitted" in record);
|
|
1745
1787
|
}
|
|
1746
1788
|
function isReplWrapperRecord(record) {
|
|
1747
1789
|
const nestedResult = asRecordOrNull(record.result);
|
|
@@ -1801,6 +1843,46 @@ function replResultFields(toolName, rawResult, rawContent) {
|
|
|
1801
1843
|
if (Array.isArray(candidate.documents) && candidate.documents.length > 0) {
|
|
1802
1844
|
lines.push(`Documents: ${candidate.documents.length}`);
|
|
1803
1845
|
}
|
|
1846
|
+
const imagesOmitted = nonNegativeInteger(candidate.imagesOmitted);
|
|
1847
|
+
if (imagesOmitted !== undefined && imagesOmitted > 0) {
|
|
1848
|
+
lines.push(`Images omitted: ${imagesOmitted}`);
|
|
1849
|
+
}
|
|
1850
|
+
const failedPages = Array.isArray(candidate.imagePagesFailed)
|
|
1851
|
+
? candidate.imagePagesFailed
|
|
1852
|
+
: [];
|
|
1853
|
+
const failedPageLimit = 20;
|
|
1854
|
+
let renderedFailedPages = 0;
|
|
1855
|
+
let locallyOmittedFailedPages = 0;
|
|
1856
|
+
for (const value of failedPages) {
|
|
1857
|
+
const failedPage = asRecordOrNull(value);
|
|
1858
|
+
const page = nonNegativeInteger(failedPage?.page);
|
|
1859
|
+
if (!failedPage || page === undefined) {
|
|
1860
|
+
continue;
|
|
1861
|
+
}
|
|
1862
|
+
if (renderedFailedPages >= failedPageLimit) {
|
|
1863
|
+
locallyOmittedFailedPages += 1;
|
|
1864
|
+
continue;
|
|
1865
|
+
}
|
|
1866
|
+
const file = nonEmptyString(failedPage.file);
|
|
1867
|
+
const error = nonEmptyString(failedPage.error);
|
|
1868
|
+
const context = [
|
|
1869
|
+
file ? `file ${boundedDisplayText(file)}` : undefined,
|
|
1870
|
+
error ? boundedDisplayText(error) : undefined,
|
|
1871
|
+
]
|
|
1872
|
+
.filter((value) => value !== undefined)
|
|
1873
|
+
.join(": ");
|
|
1874
|
+
lines.push(`Failed image page: ${page}${context ? ` (${context})` : ""}`);
|
|
1875
|
+
renderedFailedPages += 1;
|
|
1876
|
+
}
|
|
1877
|
+
const upstreamFailedPagesOmitted = nonNegativeInteger(candidate.imagePagesFailedOmitted) ?? 0;
|
|
1878
|
+
const failedPagesOmitted = upstreamFailedPagesOmitted + locallyOmittedFailedPages;
|
|
1879
|
+
if (failedPagesOmitted > 0) {
|
|
1880
|
+
lines.push(`Failed image pages omitted: ${failedPagesOmitted}`);
|
|
1881
|
+
}
|
|
1882
|
+
const documentsOmitted = nonNegativeInteger(candidate.documentsOmitted);
|
|
1883
|
+
if (documentsOmitted !== undefined && documentsOmitted > 0) {
|
|
1884
|
+
lines.push(`Documents omitted: ${documentsOmitted}`);
|
|
1885
|
+
}
|
|
1804
1886
|
return { output: lines.length > 0 ? lines.join("\n") : undefined, failed };
|
|
1805
1887
|
}
|
|
1806
1888
|
return undefined;
|
|
@@ -1987,6 +2069,20 @@ function workflowResultFields(toolName, rawResult, rawContent) {
|
|
|
1987
2069
|
return undefined;
|
|
1988
2070
|
}
|
|
1989
2071
|
function backgroundLaunchResultFields(toolName, rawResult, rawContent) {
|
|
2072
|
+
if (isShellToolName(toolName)) {
|
|
2073
|
+
const record = findShellResultRecord(rawResult, rawContent);
|
|
2074
|
+
const taskId = typeof record?.backgroundTaskId === "string"
|
|
2075
|
+
? record.backgroundTaskId.trim()
|
|
2076
|
+
: "";
|
|
2077
|
+
if (record && taskId) {
|
|
2078
|
+
return {
|
|
2079
|
+
output: buildShellDisplayOutput(record),
|
|
2080
|
+
taskId,
|
|
2081
|
+
failed: false,
|
|
2082
|
+
keepRunning: true,
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
1990
2086
|
return (monitorResultFields(toolName, rawResult, rawContent) ??
|
|
1991
2087
|
workflowResultFields(toolName, rawResult, rawContent));
|
|
1992
2088
|
}
|
|
@@ -2300,9 +2396,16 @@ export function buildToolResultFields(isError, rawContent, base, rawResult, _con
|
|
|
2300
2396
|
}
|
|
2301
2397
|
}
|
|
2302
2398
|
if (!isError && toolName === "Write") {
|
|
2303
|
-
const
|
|
2304
|
-
if (
|
|
2305
|
-
fields.content =
|
|
2399
|
+
const presentation = writeResultPresentation(rawContent);
|
|
2400
|
+
if (presentation?.kind === "diff") {
|
|
2401
|
+
fields.content = presentation.content;
|
|
2402
|
+
return fields;
|
|
2403
|
+
}
|
|
2404
|
+
if (presentation?.kind === "text") {
|
|
2405
|
+
fields.raw_output = presentation.text;
|
|
2406
|
+
fields.content = [
|
|
2407
|
+
{ type: "content", content: { type: "text", text: presentation.text } },
|
|
2408
|
+
];
|
|
2306
2409
|
return fields;
|
|
2307
2410
|
}
|
|
2308
2411
|
const inputDiff = writeDiffFromInput(base?.raw_input);
|
package/agent-sdk/dist/bridge.js
CHANGED
|
@@ -167,6 +167,7 @@ export function buildPromptUserMessage(command, sessionId) {
|
|
|
167
167
|
}
|
|
168
168
|
return {
|
|
169
169
|
type: "user",
|
|
170
|
+
uuid: command.message_uuid,
|
|
170
171
|
session_id: sessionId,
|
|
171
172
|
parent_tool_use_id: null,
|
|
172
173
|
origin: { kind: "human" },
|
|
@@ -194,7 +195,7 @@ export function emitAgentConfigOptionUpdate(sessionId, agent) {
|
|
|
194
195
|
value: agent,
|
|
195
196
|
});
|
|
196
197
|
}
|
|
197
|
-
const EXPECTED_AGENT_SDK_VERSION = "0.3.
|
|
198
|
+
const EXPECTED_AGENT_SDK_VERSION = "0.3.258";
|
|
198
199
|
const require = createRequire(import.meta.url);
|
|
199
200
|
export function resolveInstalledAgentSdkVersion() {
|
|
200
201
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-rust",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.6",
|
|
4
4
|
"description": "Claude Code Rust - native Rust terminal interface for Claude Code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -33,15 +33,15 @@
|
|
|
33
33
|
"LICENSE"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
36
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.258"
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
|
-
"@srothgan/claude-code-rust-darwin-arm64": "0.14.
|
|
40
|
-
"@srothgan/claude-code-rust-darwin-x64": "0.14.
|
|
41
|
-
"@srothgan/claude-code-rust-linux-x64-gnu": "0.14.
|
|
42
|
-
"@srothgan/claude-code-rust-linux-arm64-gnu": "0.14.
|
|
43
|
-
"@srothgan/claude-code-rust-win32-x64-msvc": "0.14.
|
|
44
|
-
"@srothgan/claude-code-rust-win32-arm64-msvc": "0.14.
|
|
39
|
+
"@srothgan/claude-code-rust-darwin-arm64": "0.14.6",
|
|
40
|
+
"@srothgan/claude-code-rust-darwin-x64": "0.14.6",
|
|
41
|
+
"@srothgan/claude-code-rust-linux-x64-gnu": "0.14.6",
|
|
42
|
+
"@srothgan/claude-code-rust-linux-arm64-gnu": "0.14.6",
|
|
43
|
+
"@srothgan/claude-code-rust-win32-x64-msvc": "0.14.6",
|
|
44
|
+
"@srothgan/claude-code-rust-win32-arm64-msvc": "0.14.6"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
47
|
"node": ">=24"
|