Ports _internal/transport/subprocess_cli.py. Spawns the claude CLI with
--output-format stream-json --input-format stream-json --verbose, then
exposes a split (TransportReader, TransportWriter, TransportHandle) trio.
The split is the key difference from the Python single-class transport:
the reader half owns stdout exclusively and is moved into a background
task; the writer half is Arc/Mutex over stdin and clones freely. A single
mutex over the whole transport would deadlock the moment the reader
blocked on stdin — which it does after each turn.
Other notes:
- find_cli() mirrors the Python search path (PATH, then ~/.npm-global/bin,
/usr/local/bin, ~/.local/bin, ~/node_modules/.bin, ~/.yarn/bin,
~/.claude/local/claude).
- build_command() faithfully ports _build_command() with the v0.1 option
subset.
- Env handling matches Python: filter CLAUDECODE on inherit, set
CLAUDE_CODE_ENTRYPOINT=sdk-rust, layer user env, stamp
CLAUDE_AGENT_SDK_VERSION last.
- Stdout JSON parsing speculatively accumulates until serde_json succeeds
or max_buffer_size (1 MiB default) overflows — same buffer-and-retry
loop as the Python TextReceiveStream path. Non-JSON chatter
([SandboxDebug] etc.) is skipped between frames.
- TransportHandle::close() gives the subprocess a 5s graceful shutdown
window after stdin EOF before SIGKILL, mirroring the #625 fix in the
Python SDK.
- Drop on TransportHandle starts a best-effort kill so abandoned clients
do not leak claude processes.
Unit tests cover the JSON accumulator (full + partial + complete,
non-JSON skip, overflow, multiline split) and the version parser.
- Errors: single enum mirroring the Python SDK's exception hierarchy
(CliNotFound, CliConnection, Process, JsonDecode, MessageParse). Each
Python class becomes a variant; the Process variant carries exit code +
optional captured stderr.
- Options: ClaudeAgentOptions as a plain struct with a builder. Covers the
full subprocess-arg surface (system_prompt with String / PresetAppend /
File forms, tools / allowed / disallowed, permission_mode, model and
fallback, cwd / cli_path / settings, env, extra_args, effort, output_format,
mcp_servers, fork_session, include_partial_messages, etc.). Deferred
fields documented inline.
- Messages: ContentBlock enum (Text / Thinking / ToolUse / ToolResult /
ServerToolUse / ServerToolResult / Unknown) with serde tag dispatch and
an Unknown fallthrough for forward compatibility. Top-level Message enum
uses a hand-written Deserialize that dispatches on the "type" tag so
unrecognised top-level frames land in Message::Unknown with the raw JSON
preserved (matches the Python SDK's "skip unknown" behaviour, but lets
Rust callers introspect).
Unit tests cover roundtrip for the common shapes (assistant text +
thinking + tool_use, user with both string and structured content, result,
system, unknown top-level).