Skip to main content

claude_agents_sdk/
client.rs

1//! Bidirectional Claude client for streaming interactions.
2//!
3//! This module provides [`ClaudeClient`], a full-featured client for
4//! bidirectional communication with Claude. It supports:
5//!
6//! - Multiple queries in a single session
7//! - Tool permission callbacks
8//! - Hook callbacks
9//! - Runtime model and permission changes
10//! - File checkpointing and rewinding
11
12use std::pin::Pin;
13use tokio::sync::mpsc;
14use tokio_stream::{Stream, StreamExt};
15
16use crate::_internal::client::InternalClient;
17use crate::errors::{ClaudeSDKError, Result};
18use crate::types::*;
19
20/// Bidirectional client for streaming Claude interactions.
21///
22/// `ClaudeClient` provides a full-featured interface for interactive
23/// conversations with Claude. Unlike the simple [`query`](crate::query)
24/// function, this client maintains a persistent connection and supports
25/// multiple queries, callbacks, and runtime configuration changes.
26///
27/// # Examples
28///
29/// ## Basic Usage
30///
31/// ```rust,no_run
32/// use claude_agents_sdk::ClaudeClient;
33/// use tokio_stream::StreamExt;
34///
35/// #[tokio::main]
36/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
37///     let mut client = ClaudeClient::new(None);
38///     client.connect().await?;
39///
40///     // Send first query
41///     client.query("What is Rust?").await?;
42///
43///     // Process responses
44///     while let Some(msg) = client.receive_messages().next().await {
45///         println!("{:?}", msg?);
46///     }
47///
48///     client.disconnect().await?;
49///     Ok(())
50/// }
51/// ```
52///
53/// ## With Tool Permission Callback
54///
55/// ```rust,no_run
56/// use claude_agents_sdk::{ClaudeClient, ClaudeAgentOptions, PermissionResult};
57/// use std::sync::Arc;
58///
59/// #[tokio::main]
60/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
61///     let options = ClaudeAgentOptions::new()
62///         .with_can_use_tool(|tool_name, input, _ctx| async move {
63///             println!("Tool request: {} with {:?}", tool_name, input);
64///             PermissionResult::allow()
65///         });
66///
67///     let mut client = ClaudeClient::new(Some(options));
68///     client.connect().await?;
69///
70///     // Queries will now invoke the callback for tool permissions
71///
72///     Ok(())
73/// }
74/// ```
75pub struct ClaudeClient {
76    /// Internal client implementation.
77    internal: InternalClient,
78    /// Message receiver from the internal client.
79    message_rx: Option<mpsc::Receiver<Result<Message>>>,
80}
81
82impl ClaudeClient {
83    /// Create a new Claude client.
84    ///
85    /// # Arguments
86    ///
87    /// * `options` - Optional configuration for the client
88    ///
89    /// # Examples
90    ///
91    /// ```rust,no_run
92    /// use claude_agents_sdk::{ClaudeClient, ClaudeAgentOptions, PermissionMode};
93    ///
94    /// // Default configuration
95    /// let client = ClaudeClient::new(None);
96    ///
97    /// // With custom options
98    /// let options = ClaudeAgentOptions::new()
99    ///     .with_model("claude-3-opus")
100    ///     .with_permission_mode(PermissionMode::AcceptEdits);
101    /// let client = ClaudeClient::new(Some(options));
102    /// ```
103    pub fn new(options: Option<ClaudeAgentOptions>) -> Self {
104        Self {
105            internal: InternalClient::new(options.unwrap_or_default()),
106            message_rx: None,
107        }
108    }
109
110    /// Connect to the Claude CLI.
111    ///
112    /// This establishes a connection to the CLI process and initializes
113    /// the streaming session. Must be called before sending queries.
114    ///
115    /// # Errors
116    ///
117    /// Returns an error if:
118    /// - The CLI is not found
119    /// - The CLI version is incompatible
120    /// - Connection fails
121    ///
122    /// # Examples
123    ///
124    /// ```rust,no_run
125    /// use claude_agents_sdk::ClaudeClient;
126    ///
127    /// #[tokio::main]
128    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
129    ///     let mut client = ClaudeClient::new(None);
130    ///     client.connect().await?;
131    ///     // Client is now ready for queries
132    ///     Ok(())
133    /// }
134    /// ```
135    pub async fn connect(&mut self) -> Result<()> {
136        self.internal.connect().await?;
137        self.message_rx = self.internal.take_message_rx();
138        Ok(())
139    }
140
141    /// Send a query to Claude.
142    ///
143    /// Sends a new prompt to Claude. Responses can be received using
144    /// [`receive_messages`](Self::receive_messages) or
145    /// [`receive_response`](Self::receive_response).
146    ///
147    /// # Arguments
148    ///
149    /// * `prompt` - The prompt to send
150    ///
151    /// # Errors
152    ///
153    /// Returns an error if the client is not connected.
154    ///
155    /// # Examples
156    ///
157    /// ```rust,no_run
158    /// use claude_agents_sdk::ClaudeClient;
159    ///
160    /// #[tokio::main]
161    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
162    ///     let mut client = ClaudeClient::new(None);
163    ///     client.connect().await?;
164    ///
165    ///     client.query("Hello!").await?;
166    ///     client.query("Follow-up question").await?;
167    ///
168    ///     Ok(())
169    /// }
170    /// ```
171    pub async fn query(&mut self, prompt: &str) -> Result<()> {
172        self.internal.send_message(prompt).await
173    }
174
175    /// Get a stream of messages from the current query.
176    ///
177    /// Returns a stream that yields messages as they are received from
178    /// Claude. The stream ends when a result message is received.
179    ///
180    /// # Examples
181    ///
182    /// ```rust,no_run
183    /// use claude_agents_sdk::{ClaudeClient, Message};
184    /// use tokio_stream::StreamExt;
185    ///
186    /// #[tokio::main]
187    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
188    ///     let mut client = ClaudeClient::new(None);
189    ///     client.connect().await?;
190    ///     client.query("Tell me a joke").await?;
191    ///
192    ///     while let Some(msg) = client.receive_messages().next().await {
193    ///         match msg? {
194    ///             Message::Assistant(asst) => println!("{}", asst.text()),
195    ///             Message::Result(_) => break,
196    ///             _ => {}
197    ///         }
198    ///     }
199    ///
200    ///     Ok(())
201    /// }
202    /// ```
203    pub fn receive_messages(&mut self) -> impl Stream<Item = Result<Message>> + '_ {
204        futures::stream::poll_fn(move |cx| {
205            if let Some(ref mut rx) = self.message_rx {
206                Pin::new(rx).poll_recv(cx)
207            } else {
208                std::task::Poll::Ready(None)
209            }
210        })
211    }
212
213    /// Receive the complete response for the current query.
214    ///
215    /// Collects all messages until a result message is received and returns
216    /// the combined response text along with the result metadata.
217    ///
218    /// # Returns
219    ///
220    /// A tuple of (response_text, result_message).
221    ///
222    /// # Examples
223    ///
224    /// ```rust,no_run
225    /// use claude_agents_sdk::ClaudeClient;
226    ///
227    /// #[tokio::main]
228    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
229    ///     let mut client = ClaudeClient::new(None);
230    ///     client.connect().await?;
231    ///     client.query("What is 2 + 2?").await?;
232    ///
233    ///     let (response, result) = client.receive_response().await?;
234    ///     println!("Response: {}", response);
235    ///     println!("Turns: {}", result.num_turns);
236    ///
237    ///     Ok(())
238    /// }
239    /// ```
240    pub async fn receive_response(&mut self) -> Result<(String, ResultMessage)> {
241        let mut response_parts: Vec<String> = Vec::new();
242
243        while let Some(msg) = self.receive_messages().next().await {
244            match msg? {
245                Message::Assistant(asst) => {
246                    let text = asst.text();
247                    if !text.is_empty() {
248                        response_parts.push(text);
249                    }
250                }
251                Message::Result(result) => {
252                    return Ok((response_parts.concat(), result));
253                }
254                _ => {}
255            }
256        }
257
258        Err(ClaudeSDKError::internal("Connection closed without result"))
259    }
260
261    /// Interrupt the current operation.
262    ///
263    /// Sends an interrupt signal to Claude, stopping the current response.
264    ///
265    /// # Examples
266    ///
267    /// ```rust,no_run
268    /// use claude_agents_sdk::ClaudeClient;
269    /// use tokio::time::{timeout, Duration};
270    ///
271    /// #[tokio::main]
272    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
273    ///     let mut client = ClaudeClient::new(None);
274    ///     client.connect().await?;
275    ///     client.query("Write a very long story").await?;
276    ///
277    ///     // Interrupt after 5 seconds
278    ///     tokio::time::sleep(Duration::from_secs(5)).await;
279    ///     client.interrupt().await?;
280    ///
281    ///     Ok(())
282    /// }
283    /// ```
284    pub async fn interrupt(&self) -> Result<()> {
285        self.internal.interrupt().await
286    }
287
288    /// Change the permission mode for the session.
289    ///
290    /// # Arguments
291    ///
292    /// * `mode` - The new permission mode
293    ///
294    /// # Examples
295    ///
296    /// ```rust,no_run
297    /// use claude_agents_sdk::{ClaudeClient, PermissionMode};
298    ///
299    /// #[tokio::main]
300    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
301    ///     let mut client = ClaudeClient::new(None);
302    ///     client.connect().await?;
303    ///
304    ///     // Switch to accept edits mode
305    ///     client.set_permission_mode(PermissionMode::AcceptEdits).await?;
306    ///
307    ///     Ok(())
308    /// }
309    /// ```
310    pub async fn set_permission_mode(&self, mode: PermissionMode) -> Result<()> {
311        self.internal.set_permission_mode(mode).await
312    }
313
314    /// Change the model for the session.
315    ///
316    /// # Arguments
317    ///
318    /// * `model` - The new model identifier
319    ///
320    /// # Examples
321    ///
322    /// ```rust,no_run
323    /// use claude_agents_sdk::ClaudeClient;
324    ///
325    /// #[tokio::main]
326    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
327    ///     let mut client = ClaudeClient::new(None);
328    ///     client.connect().await?;
329    ///
330    ///     // Switch to a different model
331    ///     client.set_model("claude-3-opus").await?;
332    ///
333    ///     Ok(())
334    /// }
335    /// ```
336    pub async fn set_model(&self, model: impl Into<String>) -> Result<()> {
337        self.internal.set_model(model).await
338    }
339
340    /// Rewind files to a specific user message.
341    ///
342    /// This is only available when file checkpointing is enabled.
343    ///
344    /// # Arguments
345    ///
346    /// * `user_message_id` - The UUID of the user message to rewind to
347    ///
348    /// # Examples
349    ///
350    /// ```rust,no_run
351    /// use claude_agents_sdk::{ClaudeClient, ClaudeAgentOptions};
352    ///
353    /// #[tokio::main]
354    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
355    ///     let mut options = ClaudeAgentOptions::new();
356    ///     options.enable_file_checkpointing = true;
357    ///
358    ///     let mut client = ClaudeClient::new(Some(options));
359    ///     client.connect().await?;
360    ///
361    ///     // Later, rewind to a previous state
362    ///     client.rewind_files("user-message-uuid").await?;
363    ///
364    ///     Ok(())
365    /// }
366    /// ```
367    pub async fn rewind_files(&self, user_message_id: impl Into<String>) -> Result<()> {
368        self.internal.rewind_files(user_message_id).await
369    }
370
371    /// Get server initialization info.
372    ///
373    /// Returns the initialization response from the CLI, which includes
374    /// available commands, output styles, and server capabilities.
375    ///
376    /// # Returns
377    ///
378    /// `Some(Value)` with server info if connected and initialized, `None` otherwise.
379    ///
380    /// # Examples
381    ///
382    /// ```rust,no_run
383    /// use claude_agents_sdk::ClaudeClient;
384    ///
385    /// #[tokio::main]
386    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
387    ///     let mut client = ClaudeClient::new(None);
388    ///     client.connect().await?;
389    ///
390    ///     if let Some(info) = client.get_server_info().await {
391    ///         println!("Commands: {:?}", info.get("commands"));
392    ///         println!("Output style: {:?}", info.get("output_style"));
393    ///     }
394    ///
395    ///     Ok(())
396    /// }
397    /// ```
398    pub async fn get_server_info(&self) -> Option<serde_json::Value> {
399        self.internal.get_server_info().await
400    }
401
402    /// Get current MCP server connection status (streaming mode only).
403    pub async fn get_mcp_status(&self) -> Result<serde_json::Value> {
404        self.internal.get_mcp_status().await
405    }
406
407    /// Disconnect from the Claude CLI.
408    ///
409    /// Gracefully closes the connection to the CLI process.
410    ///
411    /// # Examples
412    ///
413    /// ```rust,no_run
414    /// use claude_agents_sdk::ClaudeClient;
415    ///
416    /// #[tokio::main]
417    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
418    ///     let mut client = ClaudeClient::new(None);
419    ///     client.connect().await?;
420    ///
421    ///     // Use the client...
422    ///
423    ///     client.disconnect().await?;
424    ///     Ok(())
425    /// }
426    /// ```
427    pub async fn disconnect(&mut self) -> Result<()> {
428        self.message_rx = None;
429        self.internal.disconnect().await
430    }
431
432    /// Check if the client is connected.
433    pub fn is_connected(&self) -> bool {
434        self.internal.is_connected()
435    }
436}
437
438/// Builder for creating a [`ClaudeClient`] with configuration.
439///
440/// Provides a fluent API for configuring the client before connecting.
441///
442/// # Examples
443///
444/// ```rust,no_run
445/// use claude_agents_sdk::{ClaudeClientBuilder, PermissionMode, PermissionResult};
446///
447/// #[tokio::main]
448/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
449///     let mut client = ClaudeClientBuilder::new()
450///         .model("claude-3-sonnet")
451///         .permission_mode(PermissionMode::AcceptEdits)
452///         .max_turns(10)
453///         .can_use_tool(|tool, input, _| async move {
454///             println!("Tool: {} Input: {:?}", tool, input);
455///             PermissionResult::allow()
456///         })
457///         .build();
458///
459///     client.connect().await?;
460///     Ok(())
461/// }
462/// ```
463pub struct ClaudeClientBuilder {
464    options: ClaudeAgentOptions,
465}
466
467impl ClaudeClientBuilder {
468    /// Create a new builder with default options.
469    pub fn new() -> Self {
470        Self {
471            options: ClaudeAgentOptions::new(),
472        }
473    }
474
475    /// Set the model to use.
476    pub fn model(mut self, model: impl Into<String>) -> Self {
477        self.options.model = Some(model.into());
478        self
479    }
480
481    /// Set the system prompt.
482    pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
483        self.options.system_prompt = Some(SystemPromptConfig::Text(prompt.into()));
484        self
485    }
486
487    /// Set the permission mode.
488    pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
489        self.options.permission_mode = Some(mode);
490        self
491    }
492
493    /// Set the maximum number of turns.
494    pub fn max_turns(mut self, turns: u32) -> Self {
495        self.options.max_turns = Some(turns);
496        self
497    }
498
499    /// Set the maximum budget in USD.
500    pub fn max_budget_usd(mut self, budget: f64) -> Self {
501        self.options.max_budget_usd = Some(budget);
502        self
503    }
504
505    /// Set the working directory.
506    pub fn cwd(mut self, path: impl Into<std::path::PathBuf>) -> Self {
507        self.options.cwd = Some(path.into());
508        self
509    }
510
511    /// Set the tool permission callback.
512    pub fn can_use_tool<F, Fut>(mut self, callback: F) -> Self
513    where
514        F: Fn(String, serde_json::Value, ToolPermissionContext) -> Fut + Send + Sync + 'static,
515        Fut: std::future::Future<Output = PermissionResult> + Send + 'static,
516    {
517        self.options = self.options.with_can_use_tool(callback);
518        self
519    }
520
521    /// Enable partial message streaming.
522    pub fn include_partial_messages(mut self) -> Self {
523        self.options.include_partial_messages = true;
524        self
525    }
526
527    /// Enable file checkpointing.
528    pub fn enable_file_checkpointing(mut self) -> Self {
529        self.options.enable_file_checkpointing = true;
530        self
531    }
532
533    /// Set allowed tools.
534    pub fn allowed_tools(mut self, tools: Vec<String>) -> Self {
535        self.options.allowed_tools = tools;
536        self
537    }
538
539    /// Set disallowed tools.
540    pub fn disallowed_tools(mut self, tools: Vec<String>) -> Self {
541        self.options.disallowed_tools = tools;
542        self
543    }
544
545    /// Build the client.
546    pub fn build(self) -> ClaudeClient {
547        ClaudeClient::new(Some(self.options))
548    }
549}
550
551impl Default for ClaudeClientBuilder {
552    fn default() -> Self {
553        Self::new()
554    }
555}
556
557/// A guard that automatically disconnects a [`ClaudeClient`] when dropped.
558///
559/// This provides RAII-style resource management for the client connection,
560/// similar to Python's async context manager (`async with`).
561///
562/// # Examples
563///
564/// ```rust,no_run
565/// use claude_agents_sdk::{ClaudeClient, ClientGuard};
566/// use tokio_stream::StreamExt;
567///
568/// #[tokio::main]
569/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
570///     let mut client = ClaudeClient::new(None);
571///     client.connect().await?;
572///
573///     // Create guard - client will be disconnected when guard is dropped
574///     let mut guard = ClientGuard::new(client);
575///
576///     guard.client_mut().query("Hello!").await?;
577///     let (response, _) = guard.client_mut().receive_response().await?;
578///     println!("{}", response);
579///
580///     // Client automatically disconnected when guard goes out of scope
581///     Ok(())
582/// }
583/// ```
584///
585/// Or using the convenience method:
586///
587/// ```rust,no_run
588/// use claude_agents_sdk::ClaudeClient;
589///
590/// #[tokio::main]
591/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
592///     let mut client = ClaudeClient::new(None);
593///     client.connect().await?;
594///
595///     let guard = client.into_guard();
596///     // Use guard.client() for all operations
597///     // Automatically disconnects on drop
598///
599///     Ok(())
600/// }
601/// ```
602pub struct ClientGuard {
603    client: Option<ClaudeClient>,
604    runtime: Option<tokio::runtime::Handle>,
605}
606
607impl ClientGuard {
608    /// Create a new guard for the client.
609    ///
610    /// # Note
611    /// If called outside of a Tokio runtime context, the guard will still work
612    /// but automatic disconnect on drop will be skipped (with a warning logged).
613    pub fn new(client: ClaudeClient) -> Self {
614        Self {
615            client: Some(client),
616            runtime: tokio::runtime::Handle::try_current().ok(),
617        }
618    }
619
620    /// Get a reference to the client.
621    pub fn client(&self) -> &ClaudeClient {
622        self.client.as_ref().expect("Client already taken")
623    }
624
625    /// Get a mutable reference to the client.
626    pub fn client_mut(&mut self) -> &mut ClaudeClient {
627        self.client.as_mut().expect("Client already taken")
628    }
629
630    /// Take ownership of the client, preventing automatic disconnect.
631    pub fn into_inner(mut self) -> ClaudeClient {
632        self.client.take().expect("Client already taken")
633    }
634}
635
636impl Drop for ClientGuard {
637    fn drop(&mut self) {
638        if let Some(mut client) = self.client.take() {
639            // Spawn a task to disconnect - we can't do async in drop
640            if let Some(runtime) = &self.runtime {
641                runtime.spawn(async move {
642                    let _ = client.disconnect().await;
643                });
644            } else {
645                // No runtime available - skip async disconnect
646                // The underlying transport will be dropped anyway
647                tracing::warn!(
648                    "ClientGuard dropped without Tokio runtime - skipping async disconnect"
649                );
650            }
651        }
652    }
653}
654
655impl ClaudeClient {
656    /// Convert this client into a guard that automatically disconnects on drop.
657    ///
658    /// This is the Rust equivalent of Python's `async with ClaudeSDKClient() as client:`
659    /// pattern.
660    pub fn into_guard(self) -> ClientGuard {
661        ClientGuard::new(self)
662    }
663}
664
665#[cfg(test)]
666mod tests {
667    use super::*;
668
669    #[test]
670    fn test_client_builder() {
671        let client = ClaudeClientBuilder::new()
672            .model("claude-3-sonnet")
673            .max_turns(5)
674            .build();
675
676        assert!(!client.is_connected());
677    }
678}