claude_agent_sdk_rust/lib.rs
1//! Claude Agent SDK for Rust
2//!
3//! Build production-ready AI agents with Claude Code.
4//!
5//! # Quick Start
6//!
7//! ```no_run
8//! use claude_agent_sdk_rust::{query, ClaudeAgentOptions, Message};
9//! use futures::StreamExt;
10//!
11//! #[tokio::main]
12//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
13//! let stream = query("What is 2 + 2?", None).await?;
14//! let mut messages = Box::pin(stream);
15//!
16//! while let Some(msg) = messages.next().await {
17//! match msg? {
18//! Message::Assistant(assistant) => {
19//! println!("Claude: {:?}", assistant);
20//! }
21//! Message::Result(result) => {
22//! println!("Cost: ${:.4}", result.total_cost_usd.unwrap_or(0.0));
23//! }
24//! _ => {}
25//! }
26//! }
27//!
28//! Ok(())
29//! }
30//! ```
31
32pub mod callbacks;
33pub mod client;
34pub mod error;
35pub mod parser;
36pub mod query;
37pub mod transport;
38pub mod types;
39
40// Re-export commonly used types
41pub use callbacks::{HookCallback, PermissionCallback};
42pub use client::ClaudeSDKClient;
43pub use error::{ClaudeSDKError, Result};
44pub use types::{
45 AgentDefinition, AssistantMessage, ClaudeAgentOptions, ContentBlock, Effort, HookContext,
46 HookEvent, HookInput, HookOutput, Message, MessageContent, PermissionMode, PermissionResult,
47 ResultMessage, SandboxSettings, SdkPluginConfig, SettingSource, SystemPrompt,
48 SystemPromptPreset, TextBlock, ThinkingConfig, ToolPermissionContext, ToolUseBlock,
49 ToolsOption, UserMessage,
50};
51
52use futures::Stream;
53use query::Query;
54use transport::{check_claude_version, find_claude_cli, subprocess::SubprocessTransport};
55
56/// Query Claude with a simple prompt.
57///
58/// This is the simplest way to interact with Claude. It creates a one-shot
59/// query and returns a stream of messages.
60///
61/// Internally uses streaming mode (matching Python/TypeScript SDK behavior)
62/// to send the prompt via stdin after initialization. This allows agents
63/// and large configs to be sent via the initialize request.
64///
65/// # Arguments
66///
67/// * `prompt` - The prompt to send to Claude
68/// * `options` - Optional configuration (uses defaults if None)
69///
70/// # Returns
71///
72/// A stream of `Message` objects representing the conversation.
73///
74/// # Example
75///
76/// ```no_run
77/// use claude_agent_sdk_rust::{query, Message};
78/// use futures::StreamExt;
79///
80/// #[tokio::main]
81/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
82/// let stream = query("Hello, Claude!", None).await?;
83/// let mut messages = Box::pin(stream);
84///
85/// while let Some(msg) = messages.next().await {
86/// println!("{:?}", msg?);
87/// }
88///
89/// Ok(())
90/// }
91/// ```
92pub async fn query(
93 prompt: impl Into<String>,
94 options: Option<ClaudeAgentOptions>,
95) -> Result<impl Stream<Item = Result<Message>>> {
96 let prompt_str = prompt.into();
97 let options = options.unwrap_or_default();
98
99 // Find CLI
100 let cli_path = find_claude_cli(options.cli_path.as_ref())?;
101
102 // Check version (optional, doesn't fail)
103 if std::env::var("CLAUDE_AGENT_SDK_SKIP_VERSION_CHECK").is_err() {
104 let _ = check_claude_version(&cli_path).await;
105 }
106
107 // Always use streaming mode internally (matching Python/TypeScript SDK)
108 let mut transport = SubprocessTransport::new_streaming(cli_path, &options);
109
110 // Spawn process (empty prompt for streaming mode)
111 transport.spawn(&options, "").await?;
112
113 // Create Query, start it, initialize
114 let query_obj = Query::new(transport);
115 let query_handle = query_obj.start();
116
117 // Initialize streaming mode
118 query_handle.initialize(None).await?;
119
120 // Send user message via stdin after initialize, then close stdin
121 // to signal no more input (matching Python SDK's end_input() call)
122 query_handle.send_user_message(&prompt_str).await?;
123 query_handle.close_stdin().await;
124
125 // Return stream of parsed messages
126 let message_stream = async_stream::stream! {
127 use futures::stream::StreamExt as _;
128
129 let mut stream = Box::pin(query_handle.read_messages());
130
131 while let Some(result) = stream.next().await {
132 match result {
133 Ok(value) => {
134 match parser::parse_message(value) {
135 Ok(message) => yield Ok(message),
136 Err(ClaudeSDKError::UnknownMessageType(_)) => {
137 // Skip unknown message types (forward-compatible)
138 continue;
139 }
140 Err(e) => yield Err(e),
141 }
142 }
143 Err(e) => yield Err(e),
144 }
145 }
146 };
147
148 Ok(message_stream)
149}