# claude-agent-sdk (Rust) Async Rust SDK for the Claude Agent CLI — a faithful port of the official Python [`claude-agent-sdk`](https://github.com/anthropics/claude-agent-sdk-python). This crate wraps the `claude` CLI as a subprocess and exposes its newline-delimited JSON stream as ergonomic, strongly-typed Rust values. The Python SDK's two entry points map directly: - `query()` for fire-and-forget one-shot prompts. - `ClaudeSDKClient` → `claude_agent_sdk::Client` for bidirectional, multi-turn sessions. It is an independent, community-maintained port and is not affiliated with or endorsed by Anthropic. ## 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. See [scope and known limitations](#scope-and-known-limitations) below. ## Requirements - Rust 1.85 or newer (the crate uses the 2024 edition). - The `claude` CLI available on `PATH` (or supplied explicitly via `ClaudeAgentOptions::with_cli_path()`), and an authenticated Claude session. **The SDK does not bundle the CLI binary** — it shells out to whatever `claude` it finds. ## Install The crate is distributed as a git dependency: ```toml [dependencies] claude-agent-sdk = { git = "https://git.sulkta.com/Sulkta-OSS/claude-agent-sdk-rust" } tokio = { version = "1", features = ["full"] } tokio-stream = "0.1" ``` Pin to a tag or commit (`rev = "..."` / `tag = "..."`) for reproducible builds. ## Quick start ```rust 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(()) } ``` `query()` returns a stream that ends when the CLI emits its terminal `result` message, automatically tearing down the subprocess. ## Multi-turn sessions ```rust 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 the next Result, then disconnect. while let Some(msg) = messages.next().await { if let Message::Result(_) = msg? { break; } } client.disconnect().await?; Ok(()) } ``` `Client` keeps a single CLI subprocess alive across turns, so follow-up prompts don't pay the cold-start cost again. ## Configuration Both entry points are configured with `ClaudeAgentOptions`, a builder you chain `.with_*()` methods on: ```rust use claude_agent_sdk::{ClaudeAgentOptions, PermissionMode}; let opts = ClaudeAgentOptions::new() .with_model("claude-sonnet-4-5") .with_permission_mode(PermissionMode::AcceptEdits) .with_allowed_tool("Read") .with_allowed_tool("Bash") .with_cwd("/tmp/my-project") .with_max_turns(5); ``` See the `ClaudeAgentOptions` API docs for the full list of supported fields. ## Examples Runnable examples live in [`examples/`](examples/). With the `claude` CLI on `PATH` and authenticated: ```sh cargo run --example basic # minimal one-shot query() cargo run --example with_options # query() with a configured options builder cargo run --example interactive # multi-turn Client session ``` ## Building and testing ```sh cargo build cargo test # unit + end-to-end tests. A fake CLI fixture stands in # for the real `claude` binary, so the suite needs no # network access or authenticated session to run. cargo clippy --all-targets cargo fmt --check ``` ## 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 { .. }` | ## Scope and known limitations This release covers the core path of the Python SDK: - Subprocess transport with newline-delimited JSON framing. - All message and content-block types parsed faithfully. - `query()` + `Client` for one-shot and bidirectional use. - The `ClaudeAgentOptions` flag surface relevant to subprocess arguments. The following are deferred — see the upstream Python README for the current shape of these features: - **Control protocol over JSON-RPC** — `interrupt()`, `set_permission_mode()`, `set_model()`, `get_mcp_status()`, etc. 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`)** — needs a Rust trait/derive-macro shape that isn't drafted yet. - **`HookMatcher`** — the 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 caller using a newer CLI sees fewer features, not breakage. ## Contributing Contributions are welcome. A good change: - keeps the wire protocol faithful to the upstream Python SDK (cite the behavior you're matching where it isn't obvious); - comes with a test — the end-to-end suite drives a fake CLI over the real framing, so most behavior can be covered without a live `claude` binary; - passes `cargo fmt --check`, `cargo clippy --all-targets`, and `cargo test`. Open an issue to discuss larger features (especially anything from the deferred list above) before sending a big patch. ## License AGPL-3.0-or-later. See [`LICENSE`](LICENSE). This is an independent port; the upstream Python SDK is © Anthropic, PBC. This port is licensed under the GNU Affero General Public License v3.0 or later.