# Agent SDK reference - Rust
API reference for the Rust Agent SDK, including all functions, types, and traits.
---
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
anthropic-agent-sdk = "0.2"
```
With MCP SDK server support:
```toml
[dependencies]
anthropic-agent-sdk = { version = "0.2", features = ["rmcp"] }
```
## Functions
### `query()`
One-shot query function for stateless interactions. For multi-turn conversations, use [`ClaudeSDKClient`](#claudesdkclient) instead.
```rust
pub async fn query(
prompt: impl Into<String>,
options: Option<ClaudeAgentOptions>,
) -> Result<impl Stream<Item = Result<Message>>>
```
#### Parameters
| `prompt` | `impl Into<String>` | The input prompt |
| `options` | `Option<ClaudeAgentOptions>` | Configuration options (defaults to `ClaudeAgentOptions::default()` if `None`) |
#### Returns
Returns a `Stream<Item = Result<Message>>` that yields messages from the conversation.
#### Example
```rust
use anthropic_agent_sdk::query;
use futures::StreamExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let stream = query("What is the capital of France?", None).await?;
let mut stream = Box::pin(stream);
while let Some(message) = stream.next().await {
println!("{:?}", message?);
}
Ok(())
}
```
With options:
```rust
use anthropic_agent_sdk::{query, ClaudeAgentOptions};
use futures::StreamExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let options = ClaudeAgentOptions::builder()
.system_prompt("You are a helpful coding assistant")
.max_turns(1)
.build();
let stream = query("Write a hello world in Python", Some(options)).await?;
let mut stream = Box::pin(stream);
while let Some(message) = stream.next().await {
println!("{:?}", message?);
}
Ok(())
}
```
### `ClaudeSDKClient`
Client for bidirectional communication with Claude Code. Supports interactive conversations, interrupts, hooks, and permission callbacks.
```rust
pub struct ClaudeSDKClient { /* fields omitted */ }
```
#### Constructor
```rust
impl ClaudeSDKClient {
pub async fn new(
options: ClaudeAgentOptions,
cli_path: Option<PathBuf>,
) -> Result<Self>
}
```
| `options` | `ClaudeAgentOptions` | Configuration options |
| `cli_path` | `Option<PathBuf>` | Path to Claude Code CLI (auto-detected if `None`) |
#### Methods
| `send_message(content)` | Send a message to Claude |
| `next_message()` | Get the next message from the stream |
| `receive_response()` | Stream messages until a Result message |
| `interrupt()` | Send an interrupt signal |
| `close()` | Close the client and clean up resources |
| `is_connected()` | Check if the client is connected |
| `get_session_id()` | Get the current session ID |
| `queue_message(content)` | Queue a message to send after current turn |
| `next_buffered()` | Get next message, auto-send queued after Result |
| `queued_count()` | Number of messages in queue |
| `has_queued()` | Check if queue has messages |
| `send_queued()` | Manually send next queued message |
| `clear_queue()` | Clear all queued messages |
| `bind_session(session_id)` | Bind client to session, enable validation |
| `bound_session()` | Get bound session ID |
| `unbind_session()` | Clear session binding |
| `validate_session()` | Validate current session matches bound |
| `session_info()` | Get session information (model, tools, MCP servers) |
| `current_model()` | Get the current model being used |
| `available_tools()` | Get available tools in this session |
| `mcp_server_status()` | Get status of MCP servers |
| `supported_models()` | Get list of known Claude models (static) |
| `supported_commands()` | Get available slash commands |
| `account_info()` | Get account information |
| `set_model(model)` | Store model preference locally |
| `get_runtime_model()` | Get runtime model override |
| `set_permission_mode(mode)` | Store permission mode locally |
| `get_runtime_permission_mode()` | Get runtime permission mode override |
| `set_max_thinking_tokens(tokens)` | Store thinking tokens preference locally |
| `get_runtime_max_thinking_tokens()` | Get runtime thinking tokens override |
| `clear_runtime_overrides()` | Clear all runtime overrides |
| `cancellation_token()` | Get a child cancellation token |
| `cancel()` | Cancel all ongoing operations |
| `is_cancelled()` | Check if cancellation was requested |
| `take_hook_receiver()` | Take the hook event receiver for manual handling |
| `take_permission_receiver()` | Take the permission request receiver |
| `respond_to_hook(hook_id, response)` | Respond to a hook event |
| `respond_to_permission(request_id, result)` | Respond to a permission request |
#### Basic Usage
```rust
use anthropic_agent_sdk::{ClaudeSDKClient, ClaudeAgentOptions, Message};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let options = ClaudeAgentOptions::default();
let mut client = ClaudeSDKClient::new(options, None).await?;
client.send_message("Hello, Claude!").await?;
while let Some(message) = client.next_message().await {
match message? {
Message::Assistant { message, .. } => {
println!("Response: {:?}", message.content);
}
Message::Result { .. } => break,
_ => {}
}
}
client.close().await?;
Ok(())
}
```
#### Using `receive_response()`
```rust
use anthropic_agent_sdk::{ClaudeSDKClient, ClaudeAgentOptions, Message};
use futures::StreamExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let options = ClaudeAgentOptions::default();
let mut client = ClaudeSDKClient::new(options, None).await?;
client.send_message("Hello").await?;
let mut messages = Box::pin(client.receive_response());
while let Some(msg) = messages.next().await {
match msg? {
Message::Assistant { message, .. } => println!("{:?}", message),
Message::Result { .. } => println!("Done!"),
_ => {}
}
}
Ok(())
}
```
#### Interrupt
```rust
use anthropic_agent_sdk::{ClaudeSDKClient, ClaudeAgentOptions};
use tokio::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let options = ClaudeAgentOptions::default();
let mut client = ClaudeSDKClient::new(options, None).await?;
client.send_message("Write a long essay").await?;
// After some time, interrupt
tokio::time::sleep(Duration::from_millis(500)).await;
client.interrupt().await?;
Ok(())
}
```
#### Cancellation
```rust
use anthropic_agent_sdk::{ClaudeSDKClient, ClaudeAgentOptions};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let options = ClaudeAgentOptions::default();
let mut client = ClaudeSDKClient::new(options, None).await?;
let cancel_token = client.cancellation_token();
// Use in a spawned task
let token = cancel_token.clone();
tokio::spawn(async move {
tokio::select! {
_ = token.cancelled() => {
println!("Operation cancelled");
}
_ = async { /* long operation */ } => {
println!("Operation completed");
}
}
});
// Later, cancel all operations
client.cancel();
client.close().await?;
Ok(())
}
```
#### Message Buffering
The CLI only reads stdin between turns (after Result, before next user message). Messages sent during streaming are ignored. The SDK provides built-in buffering to handle multi-turn conversations:
```rust
use anthropic_agent_sdk::{ClaudeSDKClient, ClaudeAgentOptions, Message};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let options = ClaudeAgentOptions::builder()
.max_turns(10)
.build();
let mut client = ClaudeSDKClient::new(options, None).await?;
// Send first message
client.send_message("What is Python?").await?;
// Queue follow-up messages (sent automatically after each Result)
client.queue_message("What is TypeScript?");
client.queue_message("Compare Rust to both.");
let total = 1 + client.queued_count(); // Track expected turns
let mut turn = 0;
// next_buffered() auto-sends queued messages after each Result
while let Some(msg) = client.next_buffered().await {
match msg? {
Message::Assistant { message, .. } => {
println!("Claude: {:?}", message.content);
}
Message::Result { .. } => {
turn += 1;
if turn >= total {
break;
}
}
_ => {}
}
}
client.close().await?;
Ok(())
}
```
**Security**: Each queued message is associated with the session_id at queue time. When sending, the SDK verifies the session hasn't changed - if it has, the message is discarded and the queue is cleared to prevent messages from being sent to an unintended conversation context.
| `queue_message(content)` | `()` | Add message to buffer with current session_id |
| `next_buffered()` | `Option<Result<Message>>` | Get next message, auto-send queued after Result |
| `queued_count()` | `usize` | Number of messages waiting in queue |
| `has_queued()` | `bool` | Check if queue has pending messages |
| `send_queued()` | `Result<bool>` | Manually send next queued message |
| `clear_queue()` | `()` | Clear all queued messages |
#### Session Binding (Secure by Default)
Sessions are **automatically bound** on first Result message for security:
```rust
use anthropic_agent_sdk::{ClaudeSDKClient, ClaudeAgentOptions, Message};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let options = ClaudeAgentOptions::default();
let mut client = ClaudeSDKClient::new(options, None).await?;
client.send_message("Hello").await?;
// First Result auto-binds the session
while let Some(msg) = client.next_message().await {
if let Message::Result { .. } = msg? {
// Session is now auto-bound!
break;
}
}
// All subsequent sends are validated automatically
// Returns ClaudeError::SessionMismatch if session changed
client.send_message("Follow-up").await?;
client.close().await?;
Ok(())
}
```
| `bind_session(session_id)` | `()` | Override auto-bound session |
| `bound_session()` | `Option<SessionId>` | Get bound session ID |
| `unbind_session()` | `()` | Clear binding (for multi-session scenarios) |
| `validate_session()` | `Result<()>` | Validate current matches bound (auto-called by `send_message`) |
**Behavior:**
- Auto-binds on first Result (secure by default)
- Returns `Ok(())` if sessions match or either is None (early in conversation)
- Returns `Err(ClaudeError::SessionMismatch)` if bound ≠ current
#### Introspection
```rust
use anthropic_agent_sdk::{ClaudeSDKClient, ClaudeAgentOptions};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let options = ClaudeAgentOptions::default();
let client = ClaudeSDKClient::new(options, None).await?;
// Session information (available after init message)
if let Some(info) = client.session_info() {
println!("Model: {:?}", info.model);
println!("Tools: {:?}", info.tool_names());
for server in &info.mcp_servers {
println!("MCP: {} ({})", server.name, server.status);
}
}
// Static model list
for model in ClaudeSDKClient::supported_models() {
println!("{}: {:?}", model.id, model.name);
}
// Slash commands
for cmd in client.supported_commands() {
println!("/{} - {}", cmd.name, cmd.description);
}
Ok(())
}
```
## Types
### `ClaudeAgentOptions`
Configuration options for the SDK. Uses the builder pattern via `typed-builder`.
```rust
let options = ClaudeAgentOptions::builder()
.system_prompt("You are a helpful assistant")
.model("sonnet")
.max_turns(10)
.build();
```
| `allowed_tools` | `Vec<ToolName>` | `[]` | Tools Claude is allowed to use |
| `system_prompt` | `Option<SystemPrompt>` | `None` | System prompt configuration |
| `mcp_servers` | `McpServers` | `McpServers::None` | MCP server configurations |
| `permission_mode` | `Option<PermissionMode>` | `None` | Permission mode for tool execution |
| `continue_conversation` | `bool` | `false` | Continue from previous conversation |
| `resume` | `Option<SessionId>` | `None` | Session ID to resume |
| `max_turns` | `Option<u32>` | `None` | Maximum conversation turns |
| `disallowed_tools` | `Vec<ToolName>` | `[]` | Tools Claude cannot use |
| `model` | `Option<String>` | `None` | Claude model to use |
| `permission_prompt_tool_name` | `Option<String>` | `None` | MCP tool name for permission prompts |
| `cwd` | `Option<PathBuf>` | `None` | Working directory |
| `settings` | `Option<PathBuf>` | `None` | Path to settings file |
| `add_dirs` | `Vec<PathBuf>` | `[]` | Additional directories Claude can access |
| `env` | `HashMap<String, String>` | `{}` | Environment variables (dangerous vars blocked, see Security) |
| `extra_args` | `HashMap<String, Option<String>>` | `{}` | Additional CLI arguments (allowlist enforced, see Security) |
| `max_buffer_size` | `Option<usize>` | `None` | Maximum buffer size (default: 1MB) |
| `read_timeout_secs` | `Option<u64>` | `None` | Read timeout in seconds (default: 120) |
| `can_use_tool` | `Option<CanUseToolCallback>` | `None` | Custom permission callback |
| `hooks` | `Option<HashMap<HookEvent, Vec<HookMatcher>>>` | `None` | Hook configurations |
| `user` | `Option<String>` | `None` | User identifier |
| `include_partial_messages` | `bool` | `false` | Include partial message events |
| `fork_session` | `bool` | `false` | Fork session when resuming |
| `session_id` | `Option<String>` | `None` | Custom session ID (must be valid UUID) |
| `agents` | `Option<HashMap<String, AgentDefinition>>` | `None` | Custom agent definitions |
| `setting_sources` | `Option<Vec<SettingSource>>` | `None` | Settings sources to load |
| `max_budget_usd` | `Option<f64>` | `None` | Maximum budget in USD |
| `max_thinking_tokens` | `Option<u32>` | `None` | Maximum tokens for thinking |
| `fallback_model` | `Option<String>` | `None` | Model to use if primary fails |
| `output_format` | `Option<OutputFormat>` | `None` | Structured output format |
| `sandbox` | `Option<SandboxSettings>` | `None` | Sandbox configuration |
| `plugins` | `Option<Vec<SdkPluginConfig>>` | `None` | Plugins to load |
| `betas` | `Option<Vec<SdkBeta>>` | `None` | Beta features to enable |
| `strict_mcp_config` | `bool` | `false` | Enforce strict MCP validation |
| `resume_session_at` | `Option<String>` | `None` | Resume at specific message UUID |
| `allow_dangerously_skip_permissions` | `bool` | `false` | Allow bypassing permissions |
| `path_to_claude_code_executable` | `Option<PathBuf>` | `None` | Custom CLI path |
| `stderr` | `Option<StderrCallback>` | `None` | Callback for stderr output |
| `tools` | `Option<ToolsConfig>` | `None` | Tools configuration |
### `SystemPrompt`
System prompt configuration.
```rust
pub enum SystemPrompt {
String(String),
Preset(SystemPromptPreset),
}
```
#### `SystemPromptPreset`
```rust
pub struct SystemPromptPreset {
pub prompt_type: String, // Always "preset"
pub preset: String, // e.g., "claude_code"
pub append: Option<String>,
}
```
### `ToolsConfig`
Tools configuration - list or preset.
```rust
pub enum ToolsConfig {
List(Vec<ToolName>),
Preset(ToolsPreset),
}
```
```rust
// Use Claude Code's default tools
let config = ToolsConfig::claude_code_preset();
// Or specify explicit tools
let config = ToolsConfig::from_list(vec![
ToolName::from("Read"),
ToolName::from("Write"),
]);
```
### `OutputFormat`
Structured output format configuration.
```rust
pub struct OutputFormat {
pub format_type: String, // Always "json_schema"
pub schema: serde_json::Value,
}
```
```rust
let format = OutputFormat::json_schema(serde_json::json!({
"type": "object",
"properties": {
"answer": { "type": "string" }
}
}));
```
### `SandboxSettings`
Configuration for command sandboxing.
```rust
pub struct SandboxSettings {
pub enabled: Option<bool>,
pub auto_allow_bash_if_sandboxed: Option<bool>,
pub excluded_commands: Option<Vec<String>>,
pub allow_unsandboxed_commands: Option<bool>,
pub network: Option<NetworkSandboxSettings>,
pub ignore_violations: Option<SandboxIgnoreViolations>,
pub enable_weaker_nested_sandbox: Option<bool>,
}
```
| `enabled` | `Option<bool>` | `None` | Enable sandbox mode |
| `auto_allow_bash_if_sandboxed` | `Option<bool>` | `None` | Auto-approve bash when sandboxed |
| `excluded_commands` | `Option<Vec<String>>` | `None` | Commands that bypass sandbox |
| `allow_unsandboxed_commands` | `Option<bool>` | `None` | Allow model to request unsandboxed execution |
| `network` | `Option<NetworkSandboxSettings>` | `None` | Network restrictions |
| `ignore_violations` | `Option<SandboxIgnoreViolations>` | `None` | Violations to ignore |
| `enable_weaker_nested_sandbox` | `Option<bool>` | `None` | Weaker nested sandbox for compatibility |
### `NetworkSandboxSettings`
Network-specific sandbox configuration.
```rust
pub struct NetworkSandboxSettings {
pub allow_local_binding: Option<bool>,
pub allow_unix_sockets: Option<Vec<String>>,
pub allow_all_unix_sockets: Option<bool>,
pub http_proxy_port: Option<u16>,
pub socks_proxy_port: Option<u16>,
}
```
### `SandboxIgnoreViolations`
Violations to ignore in sandbox mode.
```rust
pub struct SandboxIgnoreViolations {
pub file: Option<Vec<String>>,
pub network: Option<Vec<String>>,
}
```
### `SdkPluginConfig`
Plugin configuration.
```rust
pub enum SdkPluginConfig {
Local { path: String },
}
```
### `SdkBeta`
Available beta features.
```rust
pub enum SdkBeta {
Context1M, // 1 million token context window
}
```
| `Context1M` | 1 million token context window | Claude Sonnet 4, Claude Sonnet 4.5 |
### `AgentDefinition`
Configuration for custom subagents.
```rust
pub struct AgentDefinition {
pub description: String,
pub prompt: String,
pub tools: Option<Vec<String>>,
pub model: Option<String>,
}
```
| `description` | Yes | When to use this agent |
| `prompt` | Yes | Agent's system prompt |
| `tools` | No | Allowed tools (inherits all if omitted) |
| `model` | No | Model override (`"sonnet"`, `"opus"`, `"haiku"`) |
### `SettingSource`
Configuration sources to load.
```rust
pub enum SettingSource {
User, // ~/.claude/settings.json
Project, // .claude/settings.json
Local, // .claude/settings.local.json
}
```
When `setting_sources` is omitted, no filesystem settings are loaded.
```rust
// Load project settings only
let options = ClaudeAgentOptions::builder()
.setting_sources(vec![SettingSource::Project])
.build();
// Load all settings
let options = ClaudeAgentOptions::builder()
.setting_sources(vec![
SettingSource::User,
SettingSource::Project,
SettingSource::Local,
])
.build();
```
### `PermissionMode`
Permission modes for tool execution.
```rust
pub enum PermissionMode {
Default, // Prompt for dangerous tools
AcceptEdits, // Auto-accept file edits
Plan, // Plan mode - no execution
BypassPermissions, // Allow all (requires allow_dangerously_skip_permissions)
}
```
### `CanUseToolCallback`
Type alias for permission callback.
```rust
pub type CanUseToolCallback = Arc<dyn PermissionCallback>;
```
### `PermissionResult`
Result of a permission check.
```rust
pub enum PermissionResult {
Allow(PermissionResultAllow),
Deny(PermissionResultDeny),
}
```
#### `PermissionResultAllow`
```rust
pub struct PermissionResultAllow {
pub updated_input: Option<serde_json::Value>,
pub updated_permissions: Option<Vec<PermissionUpdate>>,
}
```
#### `PermissionResultDeny`
```rust
pub struct PermissionResultDeny {
pub message: String,
pub interrupt: bool,
}
```
### `McpServerConfig`
MCP server configuration.
```rust
pub enum McpServerConfig {
Stdio(McpStdioServerConfig),
Sse(McpSseServerConfig),
Http(McpHttpServerConfig),
Sdk(SdkMcpServerConfig),
}
```
#### `McpStdioServerConfig`
```rust
pub struct McpStdioServerConfig {
pub server_type: Option<String>, // "stdio"
pub command: String,
pub args: Option<Vec<String>>,
pub env: Option<HashMap<String, String>>,
}
```
#### `McpSseServerConfig`
```rust
pub struct McpSseServerConfig {
pub server_type: String, // "sse"
pub url: String,
pub headers: Option<HashMap<String, String>>,
}
```
#### `McpHttpServerConfig`
```rust
pub struct McpHttpServerConfig {
pub server_type: String, // "http"
pub url: String,
pub headers: Option<HashMap<String, String>>,
}
```
#### `SdkMcpServerConfig`
```rust
pub struct SdkMcpServerConfig {
pub name: String,
pub version: Option<String>,
}
```
### `McpServers`
MCP servers container.
```rust
pub enum McpServers {
None,
Dict(HashMap<String, McpServerConfig>),
Path(PathBuf),
}
```
```rust
use std::collections::HashMap;
let mut servers = HashMap::new();
servers.insert("my-server".to_string(), McpServerConfig::Stdio(
McpStdioServerConfig {
server_type: Some("stdio".to_string()),
command: "npx".to_string(),
args: Some(vec!["-y".to_string(), "@my/mcp-server".to_string()]),
env: None,
}
));
let options = ClaudeAgentOptions::builder()
.mcp_servers(McpServers::Dict(servers))
.build();
```
## Message Types
### `Message`
Message types returned from Claude Code.
```rust
pub enum Message {
User { ... },
Assistant { ... },
System { ... },
Result { ... },
StreamEvent { ... },
}
```
#### `Message::User`
User message sent to Claude.
| `parent_tool_use_id` | `Option<String>` | Parent tool use ID for nested conversations |
| `message` | `UserMessageContent` | Message content |
| `session_id` | `Option<SessionId>` | Session identifier |
#### `Message::Assistant`
Response from Claude.
| `parent_tool_use_id` | `Option<String>` | Parent tool use ID for nested conversations |
| `message` | `AssistantMessageContent` | Message content |
| `session_id` | `Option<SessionId>` | Session identifier |
#### `Message::System`
System messages (init, status updates).
| `subtype` | `String` | Message subtype (e.g., `"init"`) |
| `data` | `serde_json::Value` | System message data |
#### `Message::Result`
Conversation result with metrics.
| `subtype` | `String` | Result subtype (`"success"`, `"error_max_turns"`, `"error_during_execution"`) |
| `duration_ms` | `u64` | Total duration in milliseconds |
| `duration_api_ms` | `u64` | API call duration in milliseconds |
| `is_error` | `bool` | Whether this is an error result |
| `num_turns` | `u32` | Number of conversation turns |
| `session_id` | `SessionId` | Session identifier |
| `total_cost_usd` | `Option<f64>` | Total cost in USD |
| `usage` | `Option<serde_json::Value>` | Aggregate token usage |
| `result` | `Option<String>` | Result message (for success) |
| `model_usage` | `HashMap<String, ModelUsage>` | Per-model usage statistics |
| `permission_denials` | `Vec<SDKPermissionDenial>` | Denied tool uses |
| `structured_output` | `Option<serde_json::Value>` | Structured output (when `output_format` specified) |
| `errors` | `Vec<String>` | Error messages |
#### `Message::StreamEvent`
Partial message events (requires `include_partial_messages: true`).
| `uuid` | `String` | Event UUID |
| `session_id` | `SessionId` | Session identifier |
| `event` | `serde_json::Value` | Raw stream event data |
| `parent_tool_use_id` | `Option<String>` | Parent tool use ID |
### `ContentBlock`
Content blocks in assistant messages.
```rust
pub enum ContentBlock {
Text { text: String },
Thinking { thinking: String, signature: String },
ToolUse { id: String, name: String, input: serde_json::Value },
ToolResult { tool_use_id: String, content: Option<ContentValue>, is_error: Option<bool> },
}
```
| `Text` | Text content from Claude |
| `Thinking` | Extended thinking block (when `max_thinking_tokens` set) |
| `ToolUse` | Tool invocation request |
| `ToolResult` | Result from tool execution |
### `ContentValue`
Content value for tool results.
```rust
pub enum ContentValue {
String(String),
Blocks(Vec<serde_json::Value>),
}
```
### AskUserQuestion Tool Types
Types for the `AskUserQuestion` tool which allows agents to ask users multiple-choice questions.
#### `QuestionOption`
```rust
pub struct QuestionOption {
pub label: String,
pub description: String,
}
```
| `label` | `String` | Display text for this option |
| `description` | `String` | Explanation of what this option means |
#### `QuestionSpec`
```rust
pub struct QuestionSpec {
pub question: String,
pub header: String,
pub options: Vec<QuestionOption>,
pub multi_select: bool,
}
```
| `question` | `String` | The complete question to ask the user |
| `header` | `String` | Short label displayed as a chip/tag (max 12 chars) |
| `options` | `Vec<QuestionOption>` | Available choices (2-4 options) |
| `multi_select` | `bool` | Whether multiple options can be selected |
#### `AskUserQuestionInput`
```rust
pub struct AskUserQuestionInput {
pub questions: Vec<QuestionSpec>,
pub answers: Option<HashMap<String, String>>,
}
```
| `questions` | `Vec<QuestionSpec>` | Questions to ask (1-4 questions) |
| `answers` | `Option<HashMap<String, String>>` | User answers (populated in tool result) |
#### `AskUserQuestionOutput`
```rust
pub struct AskUserQuestionOutput {
pub answers: HashMap<String, String>,
}
```
| `answers` | `HashMap<String, String>` | User's answers keyed by question header |
#### Example: Parsing Tool Input
```rust
use anthropic_agent_sdk::types::{AskUserQuestionInput, ContentBlock};
// When you receive a ToolUse content block
if let ContentBlock::ToolUse { name, input, .. } = block {
if name == "AskUserQuestion" {
let question_input: AskUserQuestionInput = serde_json::from_value(input)?;
for q in &question_input.questions {
println!("Q: {} (header: {})", q.question, q.header);
for opt in &q.options {
println!(" - {}: {}", opt.label, opt.description);
}
}
}
}
```
### `UserMessageContent`
User message content wrapper.
```rust
pub struct UserMessageContent {
pub role: String, // Always "user"
pub content: Option<UserContent>,
}
```
### `UserContent`
User content variants.
```rust
pub enum UserContent {
String(String),
Blocks(Vec<ContentBlock>),
}
```
### `AssistantMessageContent`
Assistant message content.
```rust
pub struct AssistantMessageContent {
pub model: String,
pub content: Vec<ContentBlock>,
}
```
### `SDKPermissionDenial`
Information about denied tool use.
```rust
pub struct SDKPermissionDenial {
pub tool_name: String,
pub tool_use_id: String,
pub tool_input: serde_json::Value,
}
```
### `ModelUsage`
Per-model usage statistics from Result messages.
```rust
pub struct ModelUsage {
pub input_tokens: u64,
pub output_tokens: u64,
pub cache_read_input_tokens: u64,
pub cache_creation_input_tokens: u64,
pub web_search_requests: u64,
pub cost_usd: f64,
pub context_window: u64,
}
```
| `total_tokens()` | `u64` | Input + output tokens |
| `effective_input_tokens()` | `u64` | Input + cache read + cache creation tokens |
## Working with Tools
Claude uses tools to interact with the filesystem, run commands, and perform actions.
The SDK provides options, hooks, and callbacks to control tool usage.
### Tool Flow
1. Claude decides to use a tool → `ContentBlock::ToolUse` appears in message stream
2. (Optional) `PreToolUse` hook fires → can block or modify input
3. (Optional) `PermissionCallback` fires → can allow/deny
4. CLI executes the tool
5. (Optional) `PostToolUse` hook fires → can observe result
6. `ContentBlock::ToolResult` appears in message stream
### Restricting Tools via Options
Use `allowed_tools` and `disallowed_tools` to control which tools Claude can use:
```rust
// Allow only read operations
let options = ClaudeAgentOptions::builder()
.allowed_tools(vec!["Read".into(), "Glob".into(), "Grep".into()])
.build();
// Allow all except dangerous tools
let options = ClaudeAgentOptions::builder()
.disallowed_tools(vec!["Bash".into(), "Write".into()])
.build();
```
| `allowed_tools` | `Vec<ToolName>` | Whitelist - only these tools can be used |
| `disallowed_tools` | `Vec<ToolName>` | Blacklist - these tools cannot be used |
When both are specified, `allowed_tools` takes precedence.
### Detecting Tool Use
```rust
while let Some(msg) = client.next_message().await? {
if let Message::Assistant { content, .. } = &msg {
for block in content {
match block {
ContentBlock::ToolUse { id, name, input } => {
println!("Tool: {} ({})", name, id);
println!("Input: {}", input);
}
ContentBlock::ToolResult { tool_use_id, content, is_error } => {
println!("Result for {}: {:?}", tool_use_id, content);
if is_error.unwrap_or(false) {
println!("Tool execution failed");
}
}
_ => {}
}
}
}
}
```
### Intercepting Tool Use
Use hooks to intercept, modify, or block tool execution:
```rust
// Block all Bash commands containing "rm"
struct BlockDangerousBash;
#[async_trait]
impl HookCallback for BlockDangerousBash {
async fn call(
&self,
input: serde_json::Value,
_tool_use_id: Option<String>,
_ctx: HookContext,
) -> Result<HookOutput> {
if let Some(cmd) = input.get("command").and_then(|c| c.as_str()) {
if cmd.contains("rm ") {
return Ok(HookOutput {
decision: Some(HookDecision::Block),
reason: Some("Dangerous command blocked".into()),
..Default::default()
});
}
}
Ok(HookOutput::default())
}
}
// Register for Bash tool only
let hook = HookMatcher::builder()
.event(HookEvent::PreToolUse)
.tool_name("Bash")
.build();
hook_manager.register(hook, Arc::new(BlockDangerousBash));
```
### Dynamic Permission Control
Use `PermissionCallback` for runtime decisions:
```rust
struct AuditingPermissionHandler;
#[async_trait]
impl PermissionCallback for AuditingPermissionHandler {
async fn call(
&self,
tool_name: String,
input: serde_json::Value,
_ctx: ToolPermissionContext,
) -> Result<PermissionResult> {
// Log all tool usage
println!("[AUDIT] Tool: {}, Input: {}", tool_name, input);
// Allow read tools, deny write tools
if matches!(tool_name.as_str(), "Read" | "Glob" | "Grep") {
Ok(PermissionResult::Allow(PermissionResultAllow {
updated_input: None,
updated_permissions: None,
}))
} else {
Ok(PermissionResult::Deny(PermissionResultDeny {
message: format!("Tool '{}' not allowed", tool_name),
interrupt: false,
}))
}
}
}
let options = ClaudeAgentOptions::builder()
.can_use_tool(Arc::new(AuditingPermissionHandler))
.build();
```
See [Hook Types](#hook-types) for `PreToolUse`/`PostToolUse` details.
See [Permission Types](#permission-types) for `PermissionCallback` trait.
See [Claude Code CLI Reference](https://docs.anthropic.com/en/docs/claude-code/cli) for tool schemas.
## Hook Types
### `HookEvent`
Hook event types for intercepting agent actions.
```rust
pub enum HookEvent {
PreToolUse, // Before a tool is used
PostToolUse, // After a tool is used
PostToolUseFailure,// After a tool use fails
Notification, // When a notification is received
UserPromptSubmit, // When user submits a prompt
SessionStart, // When a session starts
SessionEnd, // When a session ends
Stop, // When conversation stops
SubagentStart, // When a subagent starts
SubagentStop, // When a subagent stops
PreCompact, // Before compacting the conversation
PermissionRequest, // When a permission is requested
}
```
### `HookCallback`
Trait for hook callbacks.
```rust
#[async_trait]
pub trait HookCallback: Send + Sync {
async fn call(
&self,
input: serde_json::Value,
tool_use_id: Option<String>,
context: HookContext,
) -> Result<HookOutput>;
}
```
#### Implementing with a struct
```rust
use anthropic_agent_sdk::callbacks::HookCallback;
use anthropic_agent_sdk::types::{HookOutput, HookContext};
use async_trait::async_trait;
struct LoggingHook;
#[async_trait]
impl HookCallback for LoggingHook {
async fn call(
&self,
input: serde_json::Value,
tool_use_id: Option<String>,
_context: HookContext,
) -> Result<HookOutput> {
println!("Tool called: {:?}", tool_use_id);
Ok(HookOutput::default())
}
}
```
#### Implementing with a closure
```rust
use anthropic_agent_sdk::hooks::HookManager;
Ok(HookOutput::default())
});
```
### `HookMatcher`
Hook configuration with pattern matching.
```rust
pub struct HookMatcher {
pub matcher: Option<String>,
pub hooks: Vec<Arc<dyn HookCallback>>,
pub timeout: Option<Duration>, // Default: 60 seconds
}
```
| `matcher` | `Option<String>` | Pattern (`None` for all, `"Bash"` for specific, `"Write\|Edit"` for multiple) |
| `hooks` | `Vec<Arc<dyn HookCallback>>` | Hook callbacks to invoke |
| `timeout` | `Option<Duration>` | Timeout per hook (default: 60 seconds) |
### `HookMatcherBuilder`
Builder for creating `HookMatcher` instances.
```rust
use anthropic_agent_sdk::hooks::HookMatcherBuilder;
use std::time::Duration;
let matcher = HookMatcherBuilder::new(Some("Bash"))
.add_hook(my_hook)
.timeout(Duration::from_secs(30))
.build();
```
| `new(pattern)` | Create builder with optional pattern |
| `add_hook(hook)` | Add a hook callback |
| `timeout(duration)` | Set timeout for hooks |
| `build()` | Build the `HookMatcher` |
### `HookContext`
Context passed to hook callbacks.
```rust
pub struct HookContext {
pub session_id: Option<String>,
pub cwd: Option<String>,
pub cancellation_token: Option<CancellationToken>,
}
```
| `session_id` | `Option<String>` | Session identifier |
| `cwd` | `Option<String>` | Current working directory |
| `cancellation_token` | `Option<CancellationToken>` | Token for cancellation support |
| `is_cancelled()` | `bool` | Check if cancellation was requested |
### `HookOutput`
Output from a hook callback.
```rust
pub struct HookOutput {
pub decision: Option<HookDecision>,
pub system_message: Option<String>,
pub hook_specific_output: Option<serde_json::Value>,
}
```
| `decision` | `Option<HookDecision>` | Block or allow the action |
| `system_message` | `Option<String>` | Message to add to system context |
| `hook_specific_output` | `Option<serde_json::Value>` | Hook-specific data |
### `HookDecision`
Decision for hook callbacks.
```rust
pub enum HookDecision {
Block, // Block the action
}
```
Omit `decision` or set to `None` to allow the action.
### Hook Input Types
All hook inputs include `BaseHookInput`:
```rust
pub struct BaseHookInput {
pub session_id: String,
pub transcript_path: String,
pub cwd: String,
pub permission_mode: Option<String>,
}
```
| `PreToolUse` | `PreToolUseHookInput` | `tool_name`, `tool_input` |
| `PostToolUse` | `PostToolUseHookInput` | `tool_name`, `tool_input`, `tool_response`, `tool_use_id?` |
| `PostToolUseFailure` | `PostToolUseFailureHookInput` | `tool_name`, `tool_input`, `tool_use_id?`, `error`, `is_interrupt?` |
| `SessionStart` | `SessionStartHookInput` | `source: SessionStartSource` |
| `SessionEnd` | `SessionEndHookInput` | `reason: SessionEndReason` |
| `SubagentStart` | `SubagentStartHookInput` | `agent_id`, `agent_type` |
| `SubagentStop` | `SubagentStopHookInput` | `agent_id?`, `agent_transcript_path?`, `stop_hook_active` |
| `UserPromptSubmit` | `UserPromptSubmitHookInput` | `prompt` |
| `Notification` | `NotificationHookInput` | `message`, `title?` |
| `PreCompact` | `PreCompactHookInput` | `trigger: CompactTrigger`, `custom_instructions?` |
| `PermissionRequest` | `PermissionRequestHookInput` | `tool_name`, `tool_input`, `permission_suggestions?` |
| `Stop` | `StopHookInput` | `stop_hook_active` |
**Note:** Fields marked with `?` are optional (may be absent in CLI 2.0.75+).
### `SessionStartSource`
```rust
pub enum SessionStartSource {
Startup, // Fresh startup
Resume, // Resumed session
Clear, // After clear
Compact, // After compact
}
```
### `SessionEndReason`
```rust
pub enum SessionEndReason {
Clear, // Session cleared
Logout, // User logged out
PromptInputExit, // User exited prompt input
Other, // Other reason
}
```
### `CompactTrigger`
```rust
pub enum CompactTrigger {
Manual, // Manually triggered
Auto, // Automatically triggered
}
```
### `HookManager`
Manager for registering and invoking hooks.
```rust
use anthropic_agent_sdk::hooks::{HookManager, HookMatcherBuilder};
use anthropic_agent_sdk::types::HookEvent;
let mut manager = HookManager::new();
// Register a hook for PreToolUse events on Bash
Ok(HookOutput::default())
});
manager.register_for_event(
HookEvent::PreToolUse,
HookMatcherBuilder::new(Some("Bash")).add_hook(hook).build(),
);
```
| `new()` | Create a new hook manager |
| `from_hooks_config(config)` | Create from a hooks configuration HashMap |
| `register_for_event(event, matcher)` | Register a matcher for an event |
| `has_hooks_for(event)` | Check if hooks are registered for an event |
| `invoke(event, data, tool_name, ctx)` | Invoke hooks for an event |
| `set_session_context(session_id, cwd)` | Set session context |
| `set_cancellation_token(token)` | Set cancellation token |
| `build_context()` | Build a HookContext with current session info |
| `callback(f)` | Create a hook callback from a closure |
## Permission Types
### `PermissionCallback`
Trait for permission callbacks.
```rust
#[async_trait]
pub trait PermissionCallback: Send + Sync {
async fn call(
&self,
tool_name: String,
input: serde_json::Value,
context: ToolPermissionContext,
) -> Result<PermissionResult>;
}
```
#### Implementing with a struct
```rust
use anthropic_agent_sdk::callbacks::PermissionCallback;
use anthropic_agent_sdk::types::{PermissionResult, PermissionResultAllow, PermissionResultDeny, ToolPermissionContext};
use async_trait::async_trait;
struct AllowReadOnly;
#[async_trait]
impl PermissionCallback for AllowReadOnly {
async fn call(
&self,
tool_name: String,
_input: serde_json::Value,
_context: ToolPermissionContext,
) -> Result<PermissionResult> {
if tool_name == "Read" || tool_name == "Glob" {
Ok(PermissionResult::Allow(PermissionResultAllow {
updated_input: None,
updated_permissions: None,
}))
} else {
Ok(PermissionResult::Deny(PermissionResultDeny {
message: "Only read operations allowed".to_string(),
interrupt: false,
}))
}
}
}
```
#### Implementing with a closure
```rust
use anthropic_agent_sdk::permissions::PermissionManager;
// ctx.is_cancelled() checks if operation was cancelled
Ok(PermissionResult::Allow(PermissionResultAllow::default()))
});
```
### `ToolPermissionContext`
Context passed to permission callbacks.
```rust
pub struct ToolPermissionContext {
pub suggestions: Vec<PermissionUpdate>,
pub cancellation_token: Option<CancellationToken>,
}
```
| `suggestions` | `Vec<PermissionUpdate>` | Permission suggestions from CLI |
| `cancellation_token` | `Option<CancellationToken>` | Token for cancellation support |
| `new(suggestions)` | `Self` | Create with suggestions |
| `with_cancellation(suggestions, token)` | `Self` | Create with cancellation token |
| `is_cancelled()` | `bool` | Check if cancellation was requested |
### `PermissionUpdate`
Permission update operations.
```rust
pub enum PermissionUpdate {
AddRules { rules: Option<Vec<PermissionRuleValue>>, destination: Option<PermissionUpdateDestination> },
ReplaceRules { rules: Option<Vec<PermissionRuleValue>>, destination: Option<PermissionUpdateDestination> },
RemoveRules { rules: Option<Vec<PermissionRuleValue>>, destination: Option<PermissionUpdateDestination> },
SetMode { mode: PermissionMode, destination: Option<PermissionUpdateDestination> },
AddDirectories { directories: Option<Vec<String>>, destination: Option<PermissionUpdateDestination> },
RemoveDirectories { directories: Option<Vec<String>>, destination: Option<PermissionUpdateDestination> },
}
```
### `PermissionUpdateDestination`
Where to save permission updates.
```rust
pub enum PermissionUpdateDestination {
UserSettings, // ~/.claude/settings.json
ProjectSettings, // .claude/settings.json
LocalSettings, // .claude/settings.local.json
Session, // Session only (temporary)
}
```
### `PermissionBehavior`
Permission behavior options.
```rust
pub enum PermissionBehavior {
Allow,
Deny,
Ask,
}
```
### `PermissionRuleValue`
Permission rule definition.
```rust
pub struct PermissionRuleValue {
pub tool_name: String,
pub rule_content: Option<String>,
}
```
### `PermissionRequest`
Permission request from CLI.
```rust
pub struct PermissionRequest {
pub tool_name: ToolName,
pub tool_input: serde_json::Value,
pub context: ToolPermissionContext,
}
```
### `PermissionManager`
Manager for permission callbacks.
```rust
use anthropic_agent_sdk::permissions::PermissionManager;
use anthropic_agent_sdk::types::{PermissionResult, PermissionResultAllow, ToolName};
let mut manager = PermissionManager::new();
// Set allowed tools
manager.set_allowed_tools(Some(vec![
ToolName::from("Read"),
ToolName::from("Glob"),
]));
// Set disallowed tools
manager.set_disallowed_tools(vec![ToolName::from("Bash")]);
// Set custom callback
});
manager.set_callback(callback);
```
| `new()` | Create a new permission manager |
| `set_callback(callback)` | Set permission callback |
| `set_allowed_tools(tools)` | Set allowed tools (`None` = all allowed) |
| `set_disallowed_tools(tools)` | Set disallowed tools |
| `can_use_tool(name, input, ctx)` | Check if tool can be used |
| `callback(f)` | Create a callback from a closure |
## Introspection Types
### `SessionInfo`
Session information from init message.
```rust
pub struct SessionInfo {
pub model: Option<String>,
pub tools: Vec<ToolInfo>,
pub cwd: Option<String>,
pub mcp_servers: Vec<McpServerStatus>,
pub extra: HashMap<String, serde_json::Value>,
}
```
| `from_init_data(data)` | `Self` | Create from init message data |
| `tool_names()` | `Vec<&str>` | Get tool names |
| `has_tool(name)` | `bool` | Check if tool is available |
| `mcp_server(name)` | `Option<&McpServerStatus>` | Get MCP server by name |
| `has_mcp_errors()` | `bool` | Check if any MCP server has errors |
| `permission_mode()` | `Option<&str>` | Get current permission mode |
| `is_plan_mode()` | `bool` | Check if session is in plan mode |
### `ToolInfo`
Tool information.
```rust
pub struct ToolInfo {
pub name: String,
pub description: Option<String>,
pub input_schema: Option<serde_json::Value>,
}
```
### `McpServerStatus`
MCP server connection status.
```rust
pub struct McpServerStatus {
pub name: String,
pub status: String, // "connected", "failed", "needs-auth", "pending"
pub error: Option<String>,
pub tools: Vec<String>,
}
```
| `is_connected()` | `bool` | Check if server is connected |
### `ModelInfo`
Claude model information.
```rust
pub struct ModelInfo {
pub id: String,
pub name: Option<String>,
pub max_tokens: Option<u32>,
pub supports_thinking: bool,
}
```
```rust
// Get known models
for model in ModelInfo::known_models() {
println!("{}: {:?}", model.id, model.name);
}
```
### `SlashCommand`
Custom slash command information.
```rust
pub struct SlashCommand {
pub name: String,
pub description: String,
pub argument_hint: String,
}
```
### `AccountInfo`
OAuth account information.
```rust
pub struct AccountInfo {
pub email: Option<String>,
pub account_id: Option<String>,
pub is_oauth: bool,
pub organization_id: Option<String>,
}
```
## Error Types
### `ClaudeError`
Main error type for the SDK.
```rust
pub enum ClaudeError {
CliNotFound(String),
Connection(String),
Process { message: String, exit_code: i32, stderr: Option<String> },
JsonDecode(serde_json::Error),
JsonEncode(String),
MessageParse { message: String, data: Option<serde_json::Value> },
Transport(String),
ControlProtocol(String),
Hook(String),
Mcp(String),
Io(std::io::Error),
Timeout(String),
InvalidConfig(String),
CliVersionTooOld { found: String, minimum: String },
ControlTimeout { timeout_secs: u64, request_type: String },
NotConnected,
AlreadyConnected,
AuthenticationError(String),
NetworkError(String),
}
```
| `CliNotFound` | Claude Code CLI not found |
| `Connection` | Connection error |
| `Process` | Process execution error with exit code |
| `JsonDecode` | JSON parsing error |
| `JsonEncode` | JSON encoding error |
| `MessageParse` | Message parsing error |
| `Transport` | Transport layer error |
| `ControlProtocol` | Control protocol error |
| `Hook` | Hook execution error |
| `Mcp` | MCP error |
| `Io` | I/O error |
| `Timeout` | Timeout error |
| `InvalidConfig` | Invalid configuration |
| `CliVersionTooOld` | CLI version below minimum |
| `ControlTimeout` | Control request timed out |
| `NotConnected` | Client not connected |
| `AlreadyConnected` | Client already connected |
| `AuthenticationError` | Authentication error |
| `NetworkError` | Network error |
#### Helper Constructors
```rust
ClaudeError::cli_not_found()
ClaudeError::connection("Failed to connect")
ClaudeError::process("Failed", 1, Some("stderr output".to_string()))
ClaudeError::message_parse("Invalid message", Some(data))
ClaudeError::transport("Connection lost")
ClaudeError::hook("Hook failed")
ClaudeError::mcp("MCP server error")
ClaudeError::timeout("Request timed out")
ClaudeError::invalid_config("Missing required field")
ClaudeError::cli_version_too_old("1.0.0", "2.0.0")
ClaudeError::not_connected()
ClaudeError::authentication("Invalid API key")
ClaudeError::network("DNS resolution failed")
```
### `Result<T>`
Type alias for SDK results.
```rust
pub type Result<T> = std::result::Result<T, ClaudeError>;
```
## MCP Configuration
MCP (Model Context Protocol) configuration for external and in-process servers.
### External Server Configuration
Already documented in [Types > McpServerConfig](#mcpserverconfig).
### In-Process MCP Servers (feature: `rmcp`)
The `rmcp` feature enables creating in-process MCP servers with custom tools.
Enable in `Cargo.toml`:
```toml
[dependencies]
anthropic-agent-sdk = { version = "0.2", features = ["rmcp"] }
```
#### Re-exported Macros
| `#[tool_router]` | Mark an impl block as containing tool definitions |
| `#[tool]` | Mark a method as an MCP tool |
| `#[tool_handler]` | Implement ServerHandler for a type |
#### Re-exported Types
| `ServerHandler` | Trait for MCP server implementations |
| `ToolRouter<T>` | Router for tool invocations |
| `Parameters<T>` | Typed parameter wrapper |
| `Json` | JSON response wrapper |
| `CallToolResult` | Tool result type |
| `Tool` | Tool definition |
| `Content` | Content type for responses |
| `ServerCapabilities` | Server capability declaration |
| `ServerInfo` | Server information |
| `McpError` | Error type (alias for `ErrorData`) |
| `schemars` | Re-exported for JsonSchema derivation |
#### `SdkMcpServer`
Marker trait automatically implemented for any `ServerHandler`.
```rust
pub trait SdkMcpServer: ServerHandler {}
```
#### Example: Creating an MCP Server
```rust
use anthropic_agent_sdk::mcp::{
tool, tool_router, tool_handler,
Parameters, CallToolResult, Content, ToolRouter, ServerHandler,
};
use schemars::JsonSchema;
use serde::Deserialize;
#[derive(Deserialize, JsonSchema)]
struct GreetParams {
name: String,
}
#[derive(Clone)]
struct Greeter {
tool_router: ToolRouter<Self>,
}
#[tool_router]
impl Greeter {
fn new() -> Self {
Self { tool_router: Self::tool_router() }
}
#[tool(description = "Greet someone by name")]
async fn greet(&self, params: Parameters<GreetParams>) -> Result<CallToolResult, String> {
Ok(CallToolResult::success(vec![Content::text(
format!("Hello, {}!", params.name)
)]))
}
}
#[tool_handler]
impl ServerHandler for Greeter {
fn get_info(&self) -> rmcp::model::ServerInfo {
rmcp::model::ServerInfo::new("greeter", "1.0.0")
}
}
```
## Type-Safe Identifiers
Newtype wrappers for type safety. All implement `Display`, `AsRef<str>`, `Deref<Target=str>`, and `Borrow<str>`.
### `SessionId`
Session identifier.
```rust
pub struct SessionId(String);
```
| `new(id)` | `Self` | Create from string |
| `as_str()` | `&str` | Get as string slice |
### `ToolName`
Tool name identifier.
```rust
pub struct ToolName(String);
```
| `new(name)` | `Self` | Create from string |
| `as_str()` | `&str` | Get as string slice |
### `RequestId`
Control protocol request identifier.
```rust
pub struct RequestId(String);
```
| `new(id)` | `Self` | Create from string |
| `as_str()` | `&str` | Get as string slice |
## Utility Functions
### Safe String Handling (`utils` module)
Safe UTF-8 string truncation utilities that prevent panics when handling multi-byte characters.
```rust
use anthropic_agent_sdk::utils::{safe_truncate, truncate_for_display, safe_window};
```
#### `safe_truncate()`
Safely truncate a string at a UTF-8 character boundary.
```rust
pub fn safe_truncate(s: &str, max_bytes: usize) -> &str
```
| `s` | `&str` | The string to truncate |
| `max_bytes` | `usize` | Maximum number of bytes in the result |
Returns a string slice that is at most `max_bytes` long and valid UTF-8.
```rust
// Emoji 🔍 is 4 bytes - truncating at byte 10 would cut it in half
let text = "Status: 🔍 Active";
let result = safe_truncate(text, 10);
assert_eq!(result, "Status: "); // Stops before the emoji
```
#### `truncate_for_display()`
Truncate a string with ellipsis for display.
```rust
pub fn truncate_for_display(s: &str, max_bytes: usize) -> String
```
| `s` | `&str` | The string to truncate |
| `max_bytes` | `usize` | Maximum number of bytes before adding ellipsis |
Returns a String truncated with "..." appended if truncation occurred.
```rust
let text = "This is a long message";
let result = truncate_for_display(text, 10);
assert_eq!(result, "This is a ...");
```
#### `safe_window()`
Extract a safe substring window ending at a byte position.
```rust
pub fn safe_window(s: &str, end_byte: usize, window_size: usize) -> &str
```
| `s` | `&str` | The source string |
| `end_byte` | `usize` | The byte position to end at (clamped to string length) |
| `window_size` | `usize` | Maximum window size in bytes |
Returns a string slice of at most `window_size` bytes ending at `end_byte`, respecting UTF-8 boundaries on both ends.
```rust
let code = "export class 🔍 Scanner";
let window = safe_window(code, 20, 10);
// Returns a safe slice without cutting multi-byte characters
```
### `check_claude_version()`
Validate Claude Code CLI version.
```rust
pub async fn check_claude_version(cli_path: &Path) -> Result<String>
```
Returns the version string if the CLI is at least `MIN_CLI_VERSION`, otherwise returns `ClaudeError::CliVersionTooOld`.
### `MIN_CLI_VERSION`
Minimum required CLI version.
```rust
pub const MIN_CLI_VERSION: &str = "1.0.0";
```
---
## Security
The SDK implements strict security measures to prevent injection attacks:
### Environment Variables
Dangerous environment variables are **blocked** (not silently filtered):
```rust
// These will cause an error
options.env.insert("LD_PRELOAD".to_string(), "/tmp/evil.so".to_string());
options.env.insert("NODE_OPTIONS".to_string(), "--require=/tmp/bad.js".to_string());
let client = ClaudeSDKClient::new(options, None).await;
// Error: "Dangerous environment variables detected: [LD_PRELOAD, NODE_OPTIONS]..."
```
Blocked variables: `LD_PRELOAD`, `LD_LIBRARY_PATH`, `DYLD_INSERT_LIBRARIES`, `DYLD_LIBRARY_PATH`, `PATH`, `NODE_OPTIONS`, `PYTHONPATH`, `PERL5LIB`, `RUBYLIB`
### CLI Arguments
Only allowlisted flags are permitted in `extra_args`:
```rust
// This will cause an error
options.extra_args.insert("dangerous-flag".to_string(), None);
let client = ClaudeSDKClient::new(options, None).await;
// Error: "Disallowed CLI flags in extra_args: [dangerous-flag]..."
```
Allowed flags: `timeout`, `retries`, `log-level`, `cache-dir`
### Session Binding
Sessions are automatically bound on first Result message. See [Session Binding](#session-binding-secure-by-default).
For full security documentation, see [SECURITY.md](SECURITY.md).
---
## OAuth Authentication
The SDK provides OAuth 2.0 authentication with PKCE for Claude Max/Pro subscribers. This allows users to authenticate without API keys.
### `OAuthClient`
OAuth client for Claude authentication.
```rust
use anthropic_agent_sdk::auth::{OAuthClient, TokenStorage};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create OAuth client with default configuration
let client = OAuthClient::new()?;
// Authenticate - tries cached token first, then OAuth flow
let token = client.authenticate().await?;
println!("Access token: {}...", &token.access_token[..20]);
Ok(())
}
```
#### Constructor
```rust
impl OAuthClient {
pub fn new() -> AuthResult<Self>
pub fn builder() -> OAuthClientBuilder
}
```
#### Methods
| `authenticate()` | `AuthResult<TokenInfo>` | Try cached token, refresh, or start OAuth flow |
| `start_oauth_flow()` | `AuthResult<TokenInfo>` | Start full OAuth authorization flow |
| `config()` | `&OAuthConfig` | Get OAuth configuration |
| `storage()` | `&TokenStorage` | Get token storage |
| `logout()` | `AuthResult<()>` | Delete cached token |
| `is_authenticated()` | `bool` | Check if valid token exists |
| `current_token()` | `Option<TokenInfo>` | Get current token without refresh |
### `OAuthClientBuilder`
Builder for custom OAuth configuration.
```rust
let client = OAuthClient::builder()
.auto_open_browser(false) // Disable auto browser open
.storage(TokenStorage::with_path("custom/path/token.json".into()))
.build();
```
| `config(config)` | Set custom OAuth configuration |
| `storage(storage)` | Set custom token storage |
| `auto_open_browser(bool)` | Enable/disable automatic browser opening (default: true) |
| `build()` | Build the OAuth client |
### `OAuthConfig`
OAuth endpoint configuration.
```rust
pub struct OAuthConfig {
pub client_id: String,
pub auth_url: String,
pub token_url: String,
pub redirect_uri: String,
pub scopes: String,
}
```
Default configuration uses Claude Code's official OAuth endpoints.
### `TokenInfo`
OAuth token information.
```rust
pub struct TokenInfo {
pub access_token: String,
pub refresh_token: Option<String>,
pub token_type: String,
pub scope: Option<String>,
pub expires_at: Option<u64>,
}
```
| `is_expired()` | `bool` | Check if token is expired (with 60s buffer) |
| `authorization_header()` | `String` | Get "Bearer {token}" header value |
| `remaining_validity()` | `Option<Duration>` | Get remaining validity duration |
### `TokenStorage`
Persistent token storage.
```rust
use anthropic_agent_sdk::auth::TokenStorage;
// Default path: platform-specific (macOS: ~/Library/Application Support/claude-sdk/)
let storage = TokenStorage::new();
// Custom path
let storage = TokenStorage::with_path("/custom/path/token.json".into());
// Operations
let token = storage.load()?; // Load token
let token = storage.load_valid()?; // Load only if not expired
storage.save(&token)?; // Save token
storage.delete()?; // Delete token
```
| `new()` | `Self` | Create with default path |
| `with_path(path)` | `Self` | Create with custom path |
| `path()` | `&PathBuf` | Get storage path |
| `load()` | `Result<TokenInfo>` | Load token from storage |
| `load_valid()` | `Result<TokenInfo>` | Load token only if not expired |
| `save(token)` | `Result<()>` | Save token to storage |
| `delete()` | `Result<()>` | Delete stored token |
| `has_valid_token()` | `bool` | Check if valid token exists |
### `OAuthError`
Errors that can occur during OAuth operations.
```rust
pub enum OAuthError {
Http(String),
TokenExchange(String),
InvalidResponse(String),
Cancelled,
Storage(TokenError),
Io(std::io::Error),
Json(serde_json::Error),
BrowserOpen(String),
Reqwest(reqwest::Error),
}
```
### `TokenError`
Errors that can occur during token operations.
```rust
pub enum TokenError {
Expired,
NotFound,
Io(std::io::Error),
Json(serde_json::Error),
}
```
### OAuth Flow
1. Check for cached valid token
2. If expired, attempt refresh using refresh token
3. If no valid token, start browser-based OAuth flow with PKCE
4. User authorizes in browser, copies authorization code
5. Exchange code for access token
6. Cache token for future use
### Example: Full OAuth Flow
```rust
use anthropic_agent_sdk::auth::OAuthClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = OAuthClient::new()?;
// Check if already authenticated
if client.is_authenticated() {
let token = client.current_token().unwrap();
if let Some(remaining) = token.remaining_validity() {
println!("Token valid for: {:?}", remaining);
}
} else {
// Start OAuth flow
let token = client.authenticate().await?;
println!("Authenticated! Scopes: {:?}", token.scope);
}
// Log out when done
// client.logout()?;
Ok(())
}
```
---
## See also
- [SECURITY.md](SECURITY.md) - Full security documentation and threat model
- [Claude Code CLI reference](https://docs.anthropic.com/en/docs/claude-code/cli-reference) - Command-line interface and tool input/output schemas
- [TypeScript SDK reference](https://docs.anthropic.com/en/docs/agent-sdk/typescript) - TypeScript SDK documentation
- [Python SDK reference](https://docs.anthropic.com/en/docs/agent-sdk/python) - Python SDK documentation
- [Common workflows](https://docs.anthropic.com/en/docs/claude-code/common-workflows) - Step-by-step guides
- [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) - Protocol specification