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}