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}