claude_agent_sdk/v2/
session.rs

1//! Session management for V2 API
2//!
3//! This module provides session-based conversation management with a simplified API.
4
5use crate::client::ClaudeClient;
6use crate::errors::{ClaudeError, Result};
7use crate::types::config::ClaudeAgentOptions;
8use crate::types::messages::Message;
9use futures::StreamExt;
10use std::sync::Arc;
11use tokio::sync::Mutex;
12
13use super::types::SessionOptions;
14
15/// A conversation session with Claude
16///
17/// `Session` provides a simplified interface for multi-turn conversations with Claude.
18/// It wraps the underlying `ClaudeClient` and provides easier-to-use methods.
19///
20/// # Example
21///
22/// ```no_run
23/// use claude_agent_sdk::v2::{create_session, SessionOptions};
24///
25/// #[tokio::main]
26/// async fn example() -> Result<(), Box<dyn std::error::Error>> {
27///     let mut session = create_session(SessionOptions::default()).await?;
28///
29///     // Send a message
30///     session.send("What is 2 + 2?").await?;
31///
32///     // Receive all responses
33///     let messages = session.receive().await?;
34///     for msg in messages {
35///         println!("Message: {:?}", msg);
36///     }
37///
38///     // Close the session
39///     session.close().await?;
40///
41///     Ok(())
42/// }
43/// ```
44pub struct Session {
45    /// Unique session identifier
46    pub id: String,
47    /// Session options
48    pub options: SessionOptions,
49    /// Internal client
50    client: Arc<Mutex<ClaudeClient>>,
51}
52
53impl Session {
54    /// Create a new session (internal use)
55    ///
56/// This is called by `create_session()` to initialize a new session.
57    fn new(id: String, options: SessionOptions, client: ClaudeClient) -> Self {
58        Self {
59            id,
60            options,
61            client: Arc::new(Mutex::new(client)),
62        }
63    }
64
65    /// Send a message to Claude
66    ///
67    /// This method sends a user message to Claude and queues it for processing.
68    /// Call `receive()` to get Claude's response.
69    ///
70    /// # Arguments
71    ///
72    /// * `message` - The message text to send
73    ///
74    /// # Example
75    ///
76    /// ```no_run
77    /// # use claude_agent_sdk::v2::Session;
78    /// # async fn example(session: &mut Session) -> Result<(), Box<dyn std::error::Error>> {
79    /// session.send("Hello, Claude!").await?;
80    /// # Ok(())
81    /// # }
82    /// ```
83    ///
84    /// # Errors
85    ///
86    /// Returns an error if:
87    /// - The message is empty
88    /// - Network connection fails
89    /// - Session is closed
90    pub async fn send(&mut self, message: impl Into<String>) -> Result<()> {
91        let message_text = message.into();
92
93        if message_text.trim().is_empty() {
94            return Err(ClaudeError::InvalidInput(
95                "Message cannot be empty".to_string(),
96            ));
97        }
98
99        let mut client = self.client.lock().await;
100        client.query(&message_text).await?;
101
102        Ok(())
103    }
104
105    /// Receive messages from Claude
106    ///
107    /// This method returns all pending messages from Claude since the last `send()` call.
108    /// Messages are returned until a `Result` message is encountered (end of turn).
109    ///
110    /// # Returns
111    ///
112    /// A vector of `V2Message` objects
113    ///
114    /// # Example
115    ///
116    /// ```no_run
117    /// # use claude_agent_sdk::v2::Session;
118    /// # async fn example(session: &mut Session) -> Result<(), Box<dyn std::error::Error>> {
119    /// let messages = session.receive().await?;
120    /// for msg in messages {
121    ///     if let Some(text) = msg.as_text() {
122    ///         println!("Claude: {}", text);
123    ///     }
124    /// }
125    /// # Ok(())
126    /// # }
127    /// ```
128    pub async fn receive(&self) -> Result<Vec<V2Message>> {
129        let client = self.client.lock().await;
130        let mut stream = client.receive_response();
131        let mut messages = Vec::new();
132
133        while let Some(result) = stream.next().await {
134            let msg = result?;
135
136            match msg {
137                Message::Assistant(assist_msg) => {
138                    // Extract text content
139                    let content = assist_msg
140                        .message
141                        .content
142                        .iter()
143                        .filter_map(|block| match block {
144                            crate::types::messages::ContentBlock::Text(text) => {
145                                Some(text.text.clone())
146                            }
147                            _ => None,
148                        })
149                        .collect::<Vec<_>>()
150                        .join("\n");
151
152                    messages.push(V2Message::Assistant { content });
153                }
154                Message::Result(_) => {
155                    // End of turn
156                    break;
157                }
158                _ => {
159                    // Ignore other message types
160                }
161            }
162        }
163
164        Ok(messages)
165    }
166
167    /// Get the model being used for this session
168    pub async fn model(&self) -> Option<String> {
169        // TODO: Extract model from client options
170        // For now, return None as ClaudeClient doesn't expose this
171        None
172    }
173
174    /// Check if the session is still connected
175    pub async fn is_connected(&self) -> bool {
176        // Check if client can still communicate
177        // For now, we assume it's connected if we haven't closed it
178        true
179    }
180
181    /// Close the session
182    ///
183    /// This method closes the connection to Claude and releases any resources.
184    ///
185    /// # Example
186    ///
187    /// ```no_run
188    /// # use claude_agent_sdk::v2::Session;
189    /// # async fn example(mut session: Session) -> Result<(), Box<dyn std::error::Error>> {
190    /// session.close().await?;
191    /// println!("Session closed");
192    /// # Ok(())
193    /// # }
194    /// ```
195    pub async fn close(self) -> Result<()> {
196        // Release the client connection
197        // The client will be dropped when self is dropped
198        Ok(())
199    }
200}
201
202/// Simplified message type for V2 API sessions
203///
204/// This is a streamlined version of the full Message type,
205/// focused on the most common use cases.
206///
207/// # Variants
208///
209/// * `Assistant` - Response from Claude with text content
210#[derive(Debug, Clone)]
211pub enum V2Message {
212    /// Response from Claude
213    Assistant {
214        /// The text content of the response
215        content: String,
216    },
217}
218
219impl V2Message {
220    /// Get the text content (if available)
221    pub fn as_text(&self) -> Option<&str> {
222        match self {
223            V2Message::Assistant { content } => Some(content),
224        }
225    }
226}
227
228/// Create a new session with Claude
229///
230/// This function creates a new session and connects to Claude.
231/// The session will use the provided options for configuration.
232///
233/// # Arguments
234///
235/// * `options` - Session options (use `Default::default()` for defaults)
236///
237/// # Returns
238///
239/// A new `Session` object
240///
241/// # Example
242///
243/// ```no_run
244/// use claude_agent_sdk::v2::{create_session, SessionOptions};
245///
246/// #[tokio::main]
247/// async fn example() -> Result<(), Box<dyn std::error::Error>> {
248///     let session = create_session(SessionOptions::default()).await?;
249///     println!("Session created: {}", session.id);
250///     Ok(())
251/// }
252/// ```
253pub async fn create_session(options: SessionOptions) -> Result<Session> {
254    let opts: ClaudeAgentOptions = options.clone().into();
255    let mut client = ClaudeClient::new(opts);
256
257    client.connect().await?;
258
259    // Generate a session ID
260    let id = uuid::Uuid::new_v4().to_string();
261
262    Ok(Session::new(id, options, client))
263}
264
265/// Resume an existing session
266///
267/// This function resumes a previously created session using its session ID.
268/// Note: Session persistence is not yet fully implemented, so this currently
269/// creates a new session with the same ID.
270///
271/// # Arguments
272///
273/// * `session_id` - The ID of the session to resume
274/// * `options` - Session options
275///
276/// # Returns
277///
278/// A resumed `Session` object
279///
280/// # Example
281///
282/// ```no_run
283/// use claude_agent_sdk::v2::{resume_session, SessionOptions};
284///
285/// #[tokio::main]
286/// async fn example() -> Result<(), Box<dyn std::error::Error>> {
287///     let session = resume_session("existing-session-id", SessionOptions::default()).await?;
288///     println!("Resumed session: {}", session.id);
289///     Ok(())
290/// }
291/// ```
292pub async fn resume_session(
293    session_id: &str,
294    options: SessionOptions,
295) -> Result<Session> {
296    // TODO: Implement proper session resumption from persistent storage
297    // For now, create a new session with the provided ID
298    let opts: ClaudeAgentOptions = options.clone().into();
299    let mut client = ClaudeClient::new(opts);
300
301    client.connect().await?;
302
303    Ok(Session::new(
304        session_id.to_string(),
305        options,
306        client,
307    ))
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    #[test]
315    fn test_v2_message_as_text() {
316        let msg = V2Message::Assistant {
317            content: "Hello!".to_string(),
318        };
319
320        assert_eq!(msg.as_text(), Some("Hello!"));
321    }
322}