Rust port of anthropics/claude-agent-sdk-python.
Find a file
Kayos ba189340e5 port the subprocess transport
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.
2026-05-14 08:03:38 -07:00
src port the subprocess transport 2026-05-14 08:03:38 -07:00
.gitignore scaffold the crate 2026-05-14 08:03:11 -07:00
Cargo.toml scaffold the crate 2026-05-14 08:03:11 -07:00
LICENSE scaffold the crate 2026-05-14 08:03:11 -07:00
README.md scaffold the crate 2026-05-14 08:03:11 -07:00

claude-agent-sdk (Rust)

Async Rust SDK for the Claude Agent CLI — a faithful port of the official Python claude-agent-sdk.

This crate wraps the claude CLI as a subprocess and exposes its newline-delimited JSON stream as ergonomic Rust types. The Python SDK's two entry points map directly:

  • query() for fire-and-forget one-shot prompts.
  • ClaudeSDKClientclaude_agent_sdk::Client for bidirectional multi-turn sessions.

Status

v0.0.1 — initial port. Core feature parity for the subprocess wire protocol; advanced features (control protocol, hooks, in-process MCP servers, session_store, sandbox, plugins, agents) are deferred to v0.2. See v0.1 scope below.

Install

[dependencies]
claude-agent-sdk = { git = "https://gitea.sulkta.com/Sulkta-Coop/claude-agent-sdk-rust" }
tokio = { version = "1", features = ["full"] }
tokio-stream = "0.1"

Prerequisites: the claude CLI on PATH (or supply ClaudeAgentOptions::with_cli_path()), and an authenticated Claude session. The SDK does not bundle the CLI binary.

Quick start

use claude_agent_sdk::{query, ClaudeAgentOptions, Message, ContentBlock};
use tokio_stream::StreamExt;

#[tokio::main]
async fn main() -> claude_agent_sdk::Result<()> {
    let opts = ClaudeAgentOptions::new()
        .with_system_prompt("You are a helpful assistant.")
        .with_max_turns(1);

    let mut stream = query("What is 2 + 2?", opts).await?;

    while let Some(msg) = stream.next().await {
        match msg? {
            Message::Assistant(a) => {
                for block in a.message.content {
                    if let ContentBlock::Text(t) = block {
                        println!("Claude: {}", t.text);
                    }
                }
            }
            Message::Result(r) => {
                if let Some(usd) = r.total_cost_usd {
                    println!("Cost: ${:.4}", usd);
                }
                break;
            }
            _ => {}
        }
    }
    Ok(())
}

Multi-turn sessions

use claude_agent_sdk::{Client, ClaudeAgentOptions, Message, ContentBlock};
use tokio_stream::StreamExt;

#[tokio::main]
async fn main() -> claude_agent_sdk::Result<()> {
    let mut client = Client::new(ClaudeAgentOptions::new()).await?;
    client.connect().await?;
    let mut messages = client.messages();

    client.send("What's the capital of France?").await?;
    // ...consume messages until a Result frame, then ask a follow-up...
    client.send("And of Germany?").await?;

    // Drain until next Result, then disconnect.
    while let Some(msg) = messages.next().await {
        if let Message::Result(_) = msg? {
            break;
        }
    }
    client.disconnect().await?;
    Ok(())
}

See the examples/ directory for runnable variants.

Mapping the Python SDK

Python Rust
query(prompt=...) query(prompt, opts).await?
ClaudeSDKClient Client
ClaudeAgentOptions ClaudeAgentOptions
AssistantMessage Message::Assistant(_)
UserMessage Message::User(_)
SystemMessage Message::System(_)
ResultMessage Message::Result(_)
TextBlock / ToolUseBlock ContentBlock::Text/ToolUse(_)
CLINotFoundError Error::CliNotFound
CLIConnectionError Error::CliConnection
ProcessError Error::Process { .. }
CLIJSONDecodeError Error::JsonDecode { .. }
MessageParseError Error::MessageParse { .. }

v0.1 scope and known limitations

The v0.1 port covers the core path. The following are deferred:

  • Control protocolinterrupt(), set_permission_mode(), set_model(), get_mcp_status(), etc. The Python SDK sends a JSON-RPC initialize request before the first user message and handles control requests/responses over the same stdio pair. The Rust Client currently speaks only the bare user / assistant / result frames. Adding the control protocol unlocks the rest of the API.
  • can_use_tool permission callback — requires the control protocol.
  • In-process MCP servers (@tool / create_sdk_mcp_server) — the Python decorator wraps a mcp.server.Server instance. The Rust shape for this (likely a derive macro on a Tool trait + a runtime that multiplexes over the control protocol) is not yet drafted.
  • HookMatcher — wire format is supported on the CLI side but the Rust callback surface is not designed.
  • SessionStore mirroring adapter.
  • Sandbox settings, plugins, agents dataclass.

Each of these degrades gracefully — a v0.1 caller using a newer CLI sees fewer features, not breakage.

License

MIT. See LICENSE.

This is an independent port; the upstream Python SDK is © Anthropic, PBC.