Skip to main content

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}