Expand description
§Claude Agent SDK for Rust
Rust SDK for interacting with Claude Code CLI, enabling programmatic access to Claude’s capabilities with full bidirectional streaming support and 100% feature parity with the official Python SDK.
§Features
- Simple Query API: One-shot queries with both collecting ([
query]) and streaming (query_stream) modes - Bidirectional Streaming: Real-time streaming communication with
ClaudeClient - Dynamic Control: Interrupt, change permissions, switch models mid-execution
- Hooks System: Intercept and control Claude’s behavior at runtime with 6 hook types
- Custom Tools: In-process MCP servers with ergonomic
tool!macro - Plugin System: Load custom plugins to extend Claude’s capabilities
- Permission Management: Fine-grained control over tool execution
- Cost Control: Budget limits and fallback models for production reliability
- Extended Thinking: Configure maximum thinking tokens for complex reasoning
- Session Management: Resume, fork, and manage conversation sessions
- Multimodal Input: Send images alongside text using base64 or URLs
§Quick Start
§Simple Query
use claude_agent_sdk_rs::{query, Message, ContentBlock};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// One-shot query that collects all messages
let messages = query("What is 2 + 2?", None).await?;
for message in messages {
if let Message::Assistant(msg) = message {
for block in &msg.message.content {
if let ContentBlock::Text(text) = block {
println!("Claude: {}", text.text);
}
}
}
}
Ok(())
}§Streaming Query
use claude_agent_sdk_rs::{query_stream, Message, ContentBlock};
use futures::StreamExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Streaming query for memory-efficient processing
let mut stream = query_stream("Explain Rust ownership", None).await?;
while let Some(result) = stream.next().await {
let message = result?;
if let Message::Assistant(msg) = message {
for block in &msg.message.content {
if let ContentBlock::Text(text) = block {
println!("Claude: {}", text.text);
}
}
}
}
Ok(())
}§Bidirectional Client
use claude_agent_sdk_rs::{ClaudeClient, ClaudeAgentOptions, Message, PermissionMode};
use futures::StreamExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let options = ClaudeAgentOptions::builder()
.permission_mode(PermissionMode::BypassPermissions)
.max_turns(5)
.build();
let mut client = ClaudeClient::new(options);
client.connect().await?;
// Send query
client.query("What is Rust?").await?;
// Receive responses
{
let mut stream = client.receive_response();
while let Some(result) = stream.next().await {
match result? {
Message::Assistant(msg) => {
println!("Got assistant message");
}
Message::Result(_) => break,
_ => {}
}
}
} // stream is dropped here
client.disconnect().await?;
Ok(())
}§Multimodal Input (Images)
The SDK supports sending images alongside text in your prompts using structured content blocks. Both base64-encoded images and URL references are supported.
§Supported Formats
- JPEG (
image/jpeg) - PNG (
image/png) - GIF (
image/gif) - WebP (
image/webp)
§Size Limits
- Maximum base64 data size: 15MB (results in ~20MB decoded)
- Large images may timeout or fail - resize before encoding
§Example: Query with Image
use claude_agent_sdk_rs::{query_with_content, UserContentBlock, Message, ContentBlock};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// For real usage, load and base64-encode an image file
// This example uses a pre-encoded 1x1 red PNG pixel
let base64_data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==";
// Query with text and image
let messages = query_with_content(vec![
UserContentBlock::text("What color is this image?"),
UserContentBlock::image_base64("image/png", base64_data)?,
], None).await?;
for message in messages {
if let Message::Assistant(msg) = message {
for block in &msg.message.content {
if let ContentBlock::Text(text) = block {
println!("Claude: {}", text.text);
}
}
}
}
Ok(())
}§Example: Using Image URLs
use claude_agent_sdk_rs::{query_with_content, UserContentBlock};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let messages = query_with_content(vec![
UserContentBlock::text("Describe this architecture diagram"),
UserContentBlock::image_url("https://example.com/diagram.png"),
], None).await?;
Ok(())
}§Example: Streaming with Images
use claude_agent_sdk_rs::{query_stream_with_content, UserContentBlock, Message, ContentBlock};
use futures::StreamExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Minimal 1x1 PNG for example purposes
let png_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
let mut stream = query_stream_with_content(vec![
UserContentBlock::image_base64("image/png", png_base64)?,
UserContentBlock::text("What's in this image?"),
], None).await?;
while let Some(result) = stream.next().await {
let message = result?;
if let Message::Assistant(msg) = message {
for block in &msg.message.content {
if let ContentBlock::Text(text) = block {
print!("{}", text.text);
}
}
}
}
Ok(())
}§Configuration
The SDK provides extensive configuration through ClaudeAgentOptions:
use claude_agent_sdk_rs::{ClaudeAgentOptions, PermissionMode, SdkPluginConfig};
let options = ClaudeAgentOptions::builder()
.model("claude-opus-4")
.fallback_model("claude-sonnet-4")
.max_budget_usd(10.0)
.max_thinking_tokens(2000)
.max_turns(10)
.permission_mode(PermissionMode::Default)
.plugins(vec![SdkPluginConfig::local("./my-plugin")])
.build();§Examples
The SDK includes 22 comprehensive examples covering all features. See the examples directory for detailed usage patterns.
§Documentation
- README - Getting started
- Plugin Guide - Plugin development
- Examples - 22 working examples
§Tracing Support
This SDK uses the tracing crate for instrumentation. All major operations
are automatically traced using #[instrument] attributes, making it easy to
monitor and debug your application.
§Basic Usage (Console Logging)
use claude_agent_sdk_rs::query;
use tracing_subscriber;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Initialize tracing subscriber for console output
tracing_subscriber::fmt::init();
// Use SDK - traces are automatically logged to console
let messages = query("What is 2 + 2?", None).await?;
Ok(())
}§OpenTelemetry Integration
To export traces to OpenTelemetry-compatible backends (Jaeger, OTel, etc.):
use claude_agent_sdk_rs::query;
use tracing_subscriber::{Registry, prelude::*};
use tracing_opentelemetry::OpenTelemetryLayer;
use opentelemetry_otlp;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Initialize OpenTelemetry with OTLP exporter
let tracer = opentelemetry_otlp::new_pipeline()
.tracing()
.with_exporter(opentelemetry_otlp::new_exporter().tonic())
.install_simple()?;
let otel_layer = OpenTelemetryLayer::new(tracer);
// Combine with fmt layer for console output too
Registry::default()
.with(otel_layer)
.with(tracing_subscriber::fmt::layer())
.init();
// Use SDK - traces are exported to OTLP
let messages = query("What is 2 + 2?", None).await?;
// Shutdown telemetry
opentelemetry_sdk::global::shutdown_tracer_provider();
Ok(())
}§Available Spans
The SDK creates the following spans for comprehensive tracing:
claude.query- Top-level one-shot query operationclaude.query_stream- Streaming query operationclaude.query_with_content- Query with structured content (images)claude.query_stream_with_content- Streaming query with contentclaude.client.connect- Client connection establishmentclaude.client.query- Client query sendclaude.client.disconnect- Client disconnectionclaude.transport.connect- Transport layer connection (subprocess spawn)claude.transport.write- Writing to transport stdinclaude.query_full.start- Background message reader startclaude.query_full.initialize- Query initialization with hooksclaude.internal.execute- Internal client execution
Each span includes relevant fields:
prompt_length- Length of the user prompthas_options- Whether custom options were providedcontent_block_count- Number of content blocks (for content queries)session_id- Session identifier (for client queries)model- Model name being usedhas_can_use_tool- Whether permission callback is sethas_hooks- Whether hooks are configuredcli_path- Path to Claude CLI executabledata_length- Length of data being written
§Environment Variables
Control tracing behavior using standard RUST_LOG environment variable:
# Show all SDK logs
RUST_LOG=claude_agent_sdk_rs=debug cargo run
# Show only errors
RUST_LOG=claude_agent_sdk_rs=error cargo run
# Combine with other crates
RUST_LOG=claude_agent_sdk_rs=info,tokio=warn cargo runRe-exports§
pub use errors::ClaudeError;pub use errors::ImageValidationError;pub use errors::Result;pub use types::mcp::ACP_TOOL_PREFIX;pub use types::mcp::McpServerConfig;pub use types::mcp::McpServers;pub use types::mcp::SdkMcpServer;pub use types::mcp::SdkMcpTool;pub use types::mcp::ToolDefinition;pub use types::mcp::ToolHandler;pub use types::mcp::ToolResult;pub use types::mcp::ToolResultContent as McpToolResultContent;pub use types::mcp::acp_tool_name;pub use types::mcp::create_sdk_mcp_server;pub use types::mcp::is_acp_tool;pub use types::mcp::strip_acp_prefix;pub use client::ClaudeClient;pub use query::query;pub use query::query_stream;pub use query::query_stream_with_content;pub use query::query_with_content;pub use types::config::*;pub use types::hooks::*;pub use types::messages::*;pub use types::permissions::*;pub use types::plugin::*;
Modules§
- client
- ClaudeClient for bidirectional streaming interactions with hook support
- errors
- Error types for the Claude Agent SDK
- query
- Simple query function for one-shot interactions
- types
- Type definitions for the Claude Agent SDK
- version
- Version information for the Claude Agent SDK
Macros§
- tool
- Macro to create a tool