claude_code/client.rs
1//! Session-based client for multi-turn interactions with Claude Code.
2//!
3//! This module provides [`ClaudeSdkClient`], which maintains a persistent session
4//! for multi-turn conversations. Use this when you need to send follow-up queries,
5//! interrupt operations, or manage the session lifecycle manually.
6//!
7//! For one-off queries without session management, see [`query()`](crate::query_fn::query).
8
9use std::collections::HashMap;
10use std::sync::Arc;
11use std::time::Duration;
12
13use futures::{Stream, StreamExt};
14use serde_json::Value;
15use tokio::task::JoinHandle;
16use tracing::warn;
17
18use crate::errors::{CLIConnectionError, Error, Result};
19use crate::query::{Query, build_hooks_config};
20use crate::sdk_mcp::McpSdkServer;
21use crate::transport::subprocess_cli::{Prompt as TransportPrompt, SubprocessCliTransport};
22use crate::transport::{Transport, TransportFactory};
23use crate::types::{ClaudeAgentOptions, McpServerConfig, McpServersOption, Message};
24
25/// Input prompt for a query — either plain text or structured messages.
26///
27/// # Variants
28///
29/// - `Text` — A simple text prompt string.
30/// - `Messages` — A list of structured JSON messages for fine-grained control
31/// over the conversation input. Required when using [`can_use_tool`](crate::ClaudeAgentOptions::can_use_tool)
32/// callbacks.
33#[derive(Debug, Clone, PartialEq)]
34pub enum InputPrompt {
35 /// One plain-text prompt.
36 Text(String),
37 /// Pre-built structured protocol messages.
38 Messages(Vec<Value>),
39}
40
41/// Session-based client for multi-turn Claude Code interactions.
42///
43/// `ClaudeSdkClient` maintains a connection to the Claude Code CLI subprocess,
44/// allowing multiple queries within the same conversation context. The session
45/// preserves conversation history across calls.
46///
47/// # Lifecycle
48///
49/// 1. Create a client with [`new()`](Self::new)
50/// 2. Call [`connect()`](Self::connect) to start the session
51/// 3. Send queries with [`query()`](Self::query) and receive responses with
52/// [`receive_message()`](Self::receive_message) or [`receive_response()`](Self::receive_response)
53/// 4. Call [`disconnect()`](Self::disconnect) when done
54///
55/// # Concurrency
56///
57/// After connection, [`query()`](Self::query), [`interrupt()`](Self::interrupt),
58/// and control methods take `&self`, allowing concurrent operations from different
59/// tasks. Only [`connect()`](Self::connect), [`disconnect()`](Self::disconnect),
60/// and [`receive_message()`](Self::receive_message) require `&mut self`.
61///
62/// # Example
63///
64/// ```rust,no_run
65/// # use claude_code::{ClaudeSdkClient, InputPrompt, Message};
66/// # async fn example() -> claude_code::Result<()> {
67/// let mut client = ClaudeSdkClient::new(None, None);
68/// client.connect(None).await?;
69///
70/// client.query(InputPrompt::Text("Hello!".into()), "session-1").await?;
71/// let messages = client.receive_response().await?;
72///
73/// client.disconnect().await?;
74/// # Ok(())
75/// # }
76/// ```
77pub struct ClaudeSdkClient {
78 options: ClaudeAgentOptions,
79 transport_factory: Option<Box<dyn TransportFactory>>,
80 query: Option<Query>,
81 initial_message_stream_task: Option<JoinHandle<Result<()>>>,
82}
83
84/// Adapter that wraps a single pre-built transport instance as a one-shot factory.
85struct SingleUseTransportFactory(std::sync::Mutex<Option<Box<dyn Transport>>>);
86
87impl TransportFactory for SingleUseTransportFactory {
88 fn create_transport(&self) -> Result<Box<dyn Transport>> {
89 self.0
90 .lock()
91 .map_err(|_| Error::Other("Transport factory lock poisoned".to_string()))?
92 .take()
93 .ok_or_else(|| {
94 Error::Other(
95 "Single-use transport already consumed. Use a TransportFactory for reconnect support."
96 .to_string(),
97 )
98 })
99 }
100}
101
102impl ClaudeSdkClient {
103 /// Creates a new `ClaudeSdkClient` with optional configuration and transport factory.
104 ///
105 /// # Arguments
106 ///
107 /// * `options` — Optional [`ClaudeAgentOptions`] for configuring the session.
108 /// If `None`, defaults are used.
109 /// * `transport_factory` — Optional [`TransportFactory`] for creating transport
110 /// instances on each [`connect()`](Self::connect) call. If `None`, the default
111 /// [`SubprocessCliTransport`] is used. Using a factory enables reconnect after
112 /// disconnect with the same client instance.
113 ///
114 /// # Example
115 ///
116 /// ```rust
117 /// use claude_code::ClaudeSdkClient;
118 ///
119 /// let _client = ClaudeSdkClient::new(None, None);
120 /// ```
121 pub fn new(
122 options: Option<ClaudeAgentOptions>,
123 transport_factory: Option<Box<dyn TransportFactory>>,
124 ) -> Self {
125 Self {
126 options: options.unwrap_or_default(),
127 transport_factory,
128 query: None,
129 initial_message_stream_task: None,
130 }
131 }
132
133 /// Creates a new `ClaudeSdkClient` with a single-use custom transport.
134 ///
135 /// The transport is consumed on the first [`connect()`](Self::connect). Subsequent
136 /// `connect()` calls after [`disconnect()`](Self::disconnect) will return an error.
137 /// For reconnect support with custom transports, use [`new()`](Self::new) with a
138 /// [`TransportFactory`].
139 ///
140 /// # Example
141 ///
142 /// ```rust
143 /// use claude_code::transport::subprocess_cli::{Prompt, SubprocessCliTransport};
144 /// use claude_code::ClaudeSdkClient;
145 ///
146 /// let transport = SubprocessCliTransport::new(Prompt::Messages, Default::default()).unwrap();
147 /// let _client = ClaudeSdkClient::new_with_transport(None, Box::new(transport));
148 /// ```
149 pub fn new_with_transport(
150 options: Option<ClaudeAgentOptions>,
151 transport: Box<dyn Transport>,
152 ) -> Self {
153 Self {
154 options: options.unwrap_or_default(),
155 transport_factory: Some(Box::new(SingleUseTransportFactory(std::sync::Mutex::new(
156 Some(transport),
157 )))),
158 query: None,
159 initial_message_stream_task: None,
160 }
161 }
162
163 async fn handle_initial_message_stream_task(&mut self, abort_running: bool) -> Result<()> {
164 let Some(task) = self.initial_message_stream_task.take() else {
165 return Ok(());
166 };
167
168 if abort_running && !task.is_finished() {
169 task.abort();
170 }
171
172 match task.await {
173 Ok(Ok(())) => Ok(()),
174 Ok(Err(err)) => {
175 if abort_running {
176 warn!("Initial message stream task ended with error during shutdown: {err}");
177 Ok(())
178 } else {
179 Err(err)
180 }
181 }
182 Err(join_err) => {
183 if join_err.is_cancelled() {
184 Ok(())
185 } else {
186 let message = format!("Initial message stream task panicked: {join_err}");
187 if abort_running {
188 warn!("{message}");
189 Ok(())
190 } else {
191 Err(Error::Other(message))
192 }
193 }
194 }
195 }
196 }
197
198 fn initialize_timeout() -> Duration {
199 let timeout_ms = std::env::var("CLAUDE_CODE_STREAM_CLOSE_TIMEOUT")
200 .ok()
201 .and_then(|value| value.parse::<u64>().ok())
202 .unwrap_or(60_000);
203 Duration::from_secs_f64((timeout_ms as f64 / 1000.0).max(60.0))
204 }
205
206 fn extract_sdk_mcp_servers(options: &ClaudeAgentOptions) -> HashMap<String, Arc<McpSdkServer>> {
207 let mut servers = HashMap::new();
208 if let McpServersOption::Servers(configs) = &options.mcp_servers {
209 for (name, config) in configs {
210 if let McpServerConfig::Sdk(sdk_config) = config {
211 servers.insert(name.clone(), sdk_config.instance.clone());
212 }
213 }
214 }
215 servers
216 }
217
218 /// Establishes a connection to the Claude Code CLI and starts the session.
219 ///
220 /// If an existing connection exists, it is disconnected first.
221 ///
222 /// # Arguments
223 ///
224 /// * `prompt` — Optional initial prompt to send upon connection. When using
225 /// `can_use_tool`, this must be [`InputPrompt::Messages`], not `Text`.
226 ///
227 /// # Errors
228 ///
229 /// Returns an error if:
230 /// - The CLI executable is not found
231 /// - `can_use_tool` is set with a `Text` prompt (requires `Messages`)
232 /// - `can_use_tool` is set alongside `permission_prompt_tool_name`
233 /// - The subprocess fails to start
234 ///
235 /// # Example
236 ///
237 /// ```rust,no_run
238 /// use claude_code::{ClaudeSdkClient, InputPrompt};
239 ///
240 /// # async fn example() -> claude_code::Result<()> {
241 /// let mut client = ClaudeSdkClient::new(None, None);
242 /// client.connect(Some(InputPrompt::Text("Hello".to_string()))).await?;
243 /// client.disconnect().await?;
244 /// # Ok(())
245 /// # }
246 /// ```
247 pub async fn connect(&mut self, prompt: Option<InputPrompt>) -> Result<()> {
248 self.handle_initial_message_stream_task(true).await?;
249
250 if self.query.is_some() {
251 self.disconnect().await?;
252 }
253
254 if self.options.can_use_tool.is_some() {
255 if matches!(prompt, Some(InputPrompt::Text(_))) {
256 return Err(Error::Other(
257 "can_use_tool callback requires streaming mode. Please provide prompt as messages."
258 .to_string(),
259 ));
260 }
261 if self.options.permission_prompt_tool_name.is_some() {
262 return Err(Error::Other(
263 "can_use_tool callback cannot be used with permission_prompt_tool_name."
264 .to_string(),
265 ));
266 }
267 }
268
269 let mut configured_options = self.options.clone();
270 if configured_options.can_use_tool.is_some() {
271 configured_options.permission_prompt_tool_name = Some("stdio".to_string());
272 }
273
274 let transport_prompt = match &prompt {
275 Some(InputPrompt::Text(text)) => TransportPrompt::Text(text.clone()),
276 _ => TransportPrompt::Messages,
277 };
278
279 let mut transport: Box<dyn Transport> = if let Some(factory) = &self.transport_factory {
280 factory.create_transport()?
281 } else {
282 Box::new(SubprocessCliTransport::new(
283 transport_prompt,
284 configured_options.clone(),
285 )?)
286 };
287 transport.connect().await?;
288
289 let hooks = configured_options.hooks.clone().unwrap_or_default();
290 let sdk_mcp_servers = Self::extract_sdk_mcp_servers(&configured_options);
291 let (hooks_config, hook_callbacks) = build_hooks_config(&hooks);
292
293 let (reader, writer, close_handle) = transport.into_split()?;
294
295 let mut query = Query::start(
296 reader,
297 writer,
298 close_handle,
299 true,
300 configured_options.can_use_tool.clone(),
301 hook_callbacks,
302 sdk_mcp_servers,
303 configured_options.agents.clone(),
304 Self::initialize_timeout(),
305 );
306 query.initialize(hooks_config).await?;
307
308 if let Some(InputPrompt::Messages(messages)) = prompt {
309 query.send_input_messages(messages).await?;
310 }
311
312 self.query = Some(query);
313 Ok(())
314 }
315
316 /// Establishes a connection and sends initial prompt messages from a stream.
317 ///
318 /// This is a Rust-idiomatic equivalent of Python SDK `connect(AsyncIterable)`.
319 /// The stream is consumed in a background task so this method returns once
320 /// connection is established. Unlike one-off query streaming helpers, this
321 /// keeps stdin open so the session can continue with follow-up
322 /// [`query()`](Self::query) calls.
323 ///
324 /// To synchronously wait for stream completion and surface stream write
325 /// errors, call [`wait_for_initial_messages()`](Self::wait_for_initial_messages).
326 ///
327 /// # Errors
328 ///
329 /// Returns the same errors as [`connect()`](Self::connect), plus errors
330 /// when starting the background stream task.
331 ///
332 /// # Example
333 ///
334 /// ```rust,no_run
335 /// use claude_code::ClaudeSdkClient;
336 /// use futures::stream;
337 /// use serde_json::json;
338 ///
339 /// # async fn example() -> claude_code::Result<()> {
340 /// let mut client = ClaudeSdkClient::new(None, None);
341 /// client.connect_with_messages(stream::iter(vec![
342 /// json!({"type":"user","message":{"role":"user","content":"hello"}}),
343 /// ])).await?;
344 /// client.wait_for_initial_messages().await?;
345 /// client.disconnect().await?;
346 /// # Ok(())
347 /// # }
348 /// ```
349 pub async fn connect_with_messages<S>(&mut self, prompt: S) -> Result<()>
350 where
351 S: Stream<Item = Value> + Send + Unpin + 'static,
352 {
353 self.connect(None).await?;
354
355 let query = self.query.as_ref().ok_or_else(|| {
356 Error::CLIConnection(CLIConnectionError::new(
357 "Not connected. Call connect() first.",
358 ))
359 })?;
360
361 self.initial_message_stream_task = Some(query.spawn_input_from_stream(prompt)?);
362 Ok(())
363 }
364
365 /// Waits for completion of the initial background message stream task.
366 ///
367 /// This is only relevant after calling [`connect_with_messages()`](Self::connect_with_messages).
368 /// If no background stream is active, this returns immediately.
369 ///
370 /// # Example
371 ///
372 /// ```rust,no_run
373 /// use claude_code::ClaudeSdkClient;
374 ///
375 /// # async fn example() -> claude_code::Result<()> {
376 /// let mut client = ClaudeSdkClient::new(None, None);
377 /// client.wait_for_initial_messages().await?;
378 /// # Ok(())
379 /// # }
380 /// ```
381 pub async fn wait_for_initial_messages(&mut self) -> Result<()> {
382 self.handle_initial_message_stream_task(false).await
383 }
384
385 /// Sends a query within the current session.
386 ///
387 /// The session must be connected first via [`connect()`](Self::connect).
388 /// After sending, use [`receive_message()`](Self::receive_message) or
389 /// [`receive_response()`](Self::receive_response) to get the response.
390 ///
391 /// # Arguments
392 ///
393 /// * `prompt` — The prompt to send (text or structured messages).
394 /// * `session_id` — Session identifier for the query.
395 ///
396 /// # Errors
397 ///
398 /// Returns [`CLIConnectionError`] if not connected.
399 ///
400 /// # Example
401 ///
402 /// ```rust,no_run
403 /// use claude_code::{ClaudeSdkClient, InputPrompt};
404 ///
405 /// # async fn example() -> claude_code::Result<()> {
406 /// let mut client = ClaudeSdkClient::new(None, None);
407 /// client.connect(None).await?;
408 /// client.query(InputPrompt::Text("Summarize this repo".into()), "default").await?;
409 /// client.disconnect().await?;
410 /// # Ok(())
411 /// # }
412 /// ```
413 pub async fn query(&self, prompt: InputPrompt, session_id: &str) -> Result<()> {
414 let query = self.query.as_ref().ok_or_else(|| {
415 Error::CLIConnection(CLIConnectionError::new(
416 "Not connected. Call connect() first.",
417 ))
418 })?;
419
420 match prompt {
421 InputPrompt::Text(text) => {
422 query.send_user_message(&text, session_id).await?;
423 }
424 InputPrompt::Messages(messages) => {
425 for mut message in messages {
426 if let Value::Object(ref mut obj) = message
427 && !obj.contains_key("session_id")
428 {
429 obj.insert(
430 "session_id".to_string(),
431 Value::String(session_id.to_string()),
432 );
433 }
434 query.send_raw_message(message).await?;
435 }
436 }
437 }
438
439 Ok(())
440 }
441
442 /// Streams JSON message prompts within the current session.
443 ///
444 /// # Errors
445 ///
446 /// Returns [`CLIConnectionError`] if not connected.
447 ///
448 /// # Example
449 ///
450 /// ```rust,no_run
451 /// use claude_code::ClaudeSdkClient;
452 /// use futures::stream;
453 /// use serde_json::json;
454 ///
455 /// # async fn example() -> claude_code::Result<()> {
456 /// let mut client = ClaudeSdkClient::new(None, None);
457 /// client.connect(None).await?;
458 /// client
459 /// .query_stream(
460 /// stream::iter(vec![json!({"type":"user","message":{"role":"user","content":"hello"}})]),
461 /// "default",
462 /// )
463 /// .await?;
464 /// client.disconnect().await?;
465 /// # Ok(())
466 /// # }
467 /// ```
468 pub async fn query_stream<S>(&self, prompt: S, session_id: &str) -> Result<()>
469 where
470 S: Stream<Item = Value> + Unpin,
471 {
472 let query = self.query.as_ref().ok_or_else(|| {
473 Error::CLIConnection(CLIConnectionError::new(
474 "Not connected. Call connect() first.",
475 ))
476 })?;
477
478 let session_id = session_id.to_string();
479 let mapped = prompt.map(move |mut message| {
480 if let Value::Object(ref mut obj) = message
481 && !obj.contains_key("session_id")
482 {
483 obj.insert("session_id".to_string(), Value::String(session_id.clone()));
484 }
485 message
486 });
487 query.send_input_from_stream(mapped).await
488 }
489
490 /// Receives a single message from the current query.
491 ///
492 /// Returns `None` when no more messages are available.
493 ///
494 /// # Errors
495 ///
496 /// Returns [`CLIConnectionError`] if not connected.
497 ///
498 /// # Example
499 ///
500 /// ```rust,no_run
501 /// use claude_code::ClaudeSdkClient;
502 ///
503 /// # async fn example() -> claude_code::Result<()> {
504 /// let mut client = ClaudeSdkClient::new(None, None);
505 /// client.connect(None).await?;
506 /// let _next = client.receive_message().await?;
507 /// client.disconnect().await?;
508 /// # Ok(())
509 /// # }
510 /// ```
511 pub async fn receive_message(&mut self) -> Result<Option<Message>> {
512 let query = self.query.as_mut().ok_or_else(|| {
513 Error::CLIConnection(CLIConnectionError::new(
514 "Not connected. Call connect() first.",
515 ))
516 })?;
517 query.receive_next_message().await
518 }
519
520 /// Receives all messages for the current query until a [`Message::Result`] is received.
521 ///
522 /// # Errors
523 ///
524 /// Returns [`CLIConnectionError`] if not connected.
525 ///
526 /// # Example
527 ///
528 /// ```rust,no_run
529 /// use claude_code::{ClaudeSdkClient, InputPrompt};
530 ///
531 /// # async fn example() -> claude_code::Result<()> {
532 /// let mut client = ClaudeSdkClient::new(None, None);
533 /// client.connect(None).await?;
534 /// client.query(InputPrompt::Text("Hi".into()), "default").await?;
535 /// let _messages = client.receive_response().await?;
536 /// client.disconnect().await?;
537 /// # Ok(())
538 /// # }
539 /// ```
540 pub async fn receive_response(&mut self) -> Result<Vec<Message>> {
541 let mut messages = Vec::new();
542 while let Some(message) = self.receive_message().await? {
543 let is_result = matches!(message, Message::Result(_));
544 messages.push(message);
545 if is_result {
546 break;
547 }
548 }
549 Ok(messages)
550 }
551
552 /// Interrupts the current operation.
553 ///
554 /// # Errors
555 ///
556 /// Returns [`CLIConnectionError`] if not connected.
557 ///
558 /// # Example
559 ///
560 /// ```rust,no_run
561 /// use claude_code::ClaudeSdkClient;
562 ///
563 /// # async fn example() -> claude_code::Result<()> {
564 /// let mut client = ClaudeSdkClient::new(None, None);
565 /// client.connect(None).await?;
566 /// client.interrupt().await?;
567 /// client.disconnect().await?;
568 /// # Ok(())
569 /// # }
570 /// ```
571 pub async fn interrupt(&self) -> Result<()> {
572 let query = self.query.as_ref().ok_or_else(|| {
573 Error::CLIConnection(CLIConnectionError::new(
574 "Not connected. Call connect() first.",
575 ))
576 })?;
577 query.interrupt().await
578 }
579
580 /// Changes the permission mode for the current session.
581 ///
582 /// # Errors
583 ///
584 /// Returns [`CLIConnectionError`] if not connected.
585 ///
586 /// # Example
587 ///
588 /// ```rust,no_run
589 /// use claude_code::ClaudeSdkClient;
590 ///
591 /// # async fn example() -> claude_code::Result<()> {
592 /// let mut client = ClaudeSdkClient::new(None, None);
593 /// client.connect(None).await?;
594 /// client.set_permission_mode("plan").await?;
595 /// client.disconnect().await?;
596 /// # Ok(())
597 /// # }
598 /// ```
599 pub async fn set_permission_mode(&self, mode: &str) -> Result<()> {
600 let query = self.query.as_ref().ok_or_else(|| {
601 Error::CLIConnection(CLIConnectionError::new(
602 "Not connected. Call connect() first.",
603 ))
604 })?;
605 query.set_permission_mode(mode).await
606 }
607
608 /// Changes the model used for the current session.
609 ///
610 /// # Errors
611 ///
612 /// Returns [`CLIConnectionError`] if not connected.
613 ///
614 /// # Example
615 ///
616 /// ```rust,no_run
617 /// use claude_code::ClaudeSdkClient;
618 ///
619 /// # async fn example() -> claude_code::Result<()> {
620 /// let mut client = ClaudeSdkClient::new(None, None);
621 /// client.connect(None).await?;
622 /// client.set_model(Some("sonnet")).await?;
623 /// client.disconnect().await?;
624 /// # Ok(())
625 /// # }
626 /// ```
627 pub async fn set_model(&self, model: Option<&str>) -> Result<()> {
628 let query = self.query.as_ref().ok_or_else(|| {
629 Error::CLIConnection(CLIConnectionError::new(
630 "Not connected. Call connect() first.",
631 ))
632 })?;
633 query.set_model(model).await
634 }
635
636 /// Rewinds file changes to a specific user message checkpoint.
637 ///
638 /// # Errors
639 ///
640 /// Returns [`CLIConnectionError`] if not connected.
641 ///
642 /// # Example
643 ///
644 /// ```rust,no_run
645 /// use claude_code::ClaudeSdkClient;
646 ///
647 /// # async fn example() -> claude_code::Result<()> {
648 /// let mut client = ClaudeSdkClient::new(None, None);
649 /// client.connect(None).await?;
650 /// client.rewind_files("user-msg-1").await?;
651 /// client.disconnect().await?;
652 /// # Ok(())
653 /// # }
654 /// ```
655 pub async fn rewind_files(&self, user_message_id: &str) -> Result<()> {
656 let query = self.query.as_ref().ok_or_else(|| {
657 Error::CLIConnection(CLIConnectionError::new(
658 "Not connected. Call connect() first.",
659 ))
660 })?;
661 query.rewind_files(user_message_id).await
662 }
663
664 /// Queries the status of connected MCP servers.
665 ///
666 /// # Errors
667 ///
668 /// Returns [`CLIConnectionError`] if not connected.
669 ///
670 /// # Example
671 ///
672 /// ```rust,no_run
673 /// use claude_code::ClaudeSdkClient;
674 ///
675 /// # async fn example() -> claude_code::Result<()> {
676 /// let mut client = ClaudeSdkClient::new(None, None);
677 /// client.connect(None).await?;
678 /// let _status = client.get_mcp_status().await?;
679 /// client.disconnect().await?;
680 /// # Ok(())
681 /// # }
682 /// ```
683 pub async fn get_mcp_status(&self) -> Result<Value> {
684 let query = self.query.as_ref().ok_or_else(|| {
685 Error::CLIConnection(CLIConnectionError::new(
686 "Not connected. Call connect() first.",
687 ))
688 })?;
689 query.get_mcp_status().await
690 }
691
692 /// Returns the server initialization response, if available.
693 ///
694 /// # Errors
695 ///
696 /// Returns [`CLIConnectionError`] if not connected.
697 ///
698 /// # Example
699 ///
700 /// ```rust,no_run
701 /// use claude_code::ClaudeSdkClient;
702 ///
703 /// # async fn example() -> claude_code::Result<()> {
704 /// let mut client = ClaudeSdkClient::new(None, None);
705 /// client.connect(None).await?;
706 /// let _info = client.get_server_info()?;
707 /// client.disconnect().await?;
708 /// # Ok(())
709 /// # }
710 /// ```
711 pub fn get_server_info(&self) -> Result<Option<Value>> {
712 let query = self.query.as_ref().ok_or_else(|| {
713 Error::CLIConnection(CLIConnectionError::new(
714 "Not connected. Call connect() first.",
715 ))
716 })?;
717 Ok(query.initialization_result())
718 }
719
720 /// Disconnects from the Claude Code CLI and closes the session.
721 ///
722 /// After disconnecting, the client can be reconnected with [`connect()`](Self::connect).
723 ///
724 /// # Example
725 ///
726 /// ```rust,no_run
727 /// use claude_code::ClaudeSdkClient;
728 ///
729 /// # async fn example() -> claude_code::Result<()> {
730 /// let mut client = ClaudeSdkClient::new(None, None);
731 /// client.connect(None).await?;
732 /// client.disconnect().await?;
733 /// # Ok(())
734 /// # }
735 /// ```
736 pub async fn disconnect(&mut self) -> Result<()> {
737 self.handle_initial_message_stream_task(true).await?;
738 if let Some(query) = self.query.take() {
739 query.close().await?;
740 }
741 Ok(())
742 }
743}
744
745impl Drop for ClaudeSdkClient {
746 fn drop(&mut self) {
747 if let Some(task) = self.initial_message_stream_task.take() {
748 task.abort();
749 }
750 }
751}