commit 6af8273f983026d4507c3e96c460d18352f207fe Author: Kayos Date: Thu May 14 08:03:11 2026 -0700 scaffold the crate Initial layout for an async Rust port of the Python claude-agent-sdk. Sets up Cargo.toml on 2024 edition, MIT LICENSE matching the upstream Python SDK's text with the Sulkta Coop copyright appended, a workspace .gitignore, and a README laying out the v0.1 scope and the Python -> Rust mapping table. No source modules yet — those land in the following commits. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..25c8d8a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "claude-agent-sdk" +version = "0.0.1" +edition = "2024" +license = "MIT" +description = "Async Rust SDK for the Claude Agent CLI (subprocess-based, mirrors the Python claude-agent-sdk)." +repository = "https://gitea.sulkta.com/Sulkta-Coop/claude-agent-sdk-rust" +readme = "README.md" +keywords = ["claude", "anthropic", "agent", "sdk", "async"] +categories = ["api-bindings", "asynchronous"] +rust-version = "1.85" + +[dependencies] +tokio = { version = "1", features = ["full"] } +tokio-stream = "0.1" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +thiserror = "2" +tracing = "0.1" +futures-core = "0.3" +async-stream = "0.3" + +[dev-dependencies] +anyhow = "1" +tokio = { version = "1", features = ["macros", "rt-multi-thread", "test-util"] } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +futures-util = "0.3" + +[[example]] +name = "basic" +path = "examples/basic.rs" + +[[example]] +name = "with_options" +path = "examples/with_options.rs" + +[[example]] +name = "interactive" +path = "examples/interactive.rs" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3ac43e1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2025 Anthropic, PBC +Copyright (c) 2026 Sulkta Cooperative + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1e11ab5 --- /dev/null +++ b/README.md @@ -0,0 +1,144 @@ +# 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 Rust types. 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. + +## 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](#v01-scope-and-known-limitations) below. + +## Install + +```toml +[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 + +```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(()) +} +``` + +## 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 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 protocol** — `interrupt()`, `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`](LICENSE). + +This is an independent port; the upstream Python SDK is © Anthropic, PBC.