1use std::path::PathBuf;
6use std::sync::Arc;
7
8use dashmap::DashMap;
9
10use crate::types::{AgentConfig, AgentError, NewSessionMeta, Result};
11
12use super::session::Session;
13
14#[derive(Debug, Default)]
19pub struct SessionManager {
20 sessions: DashMap<String, Arc<Session>>,
22}
23
24impl SessionManager {
25 pub fn new() -> Self {
27 Self {
28 sessions: DashMap::new(),
29 }
30 }
31
32 pub fn create_session(
45 &self,
46 session_id: String,
47 cwd: PathBuf,
48 config: &AgentConfig,
49 meta: Option<&NewSessionMeta>,
50 ) -> Result<Arc<Session>> {
51 let entry = self.sessions.entry(session_id.clone());
53
54 match entry {
55 dashmap::Entry::Occupied(_) => {
56 Err(AgentError::SessionAlreadyExists(session_id))
58 }
59 dashmap::Entry::Vacant(vacant) => {
60 let arc_session = Session::new(session_id, cwd, config, meta)?;
62 vacant.insert(Arc::clone(&arc_session));
63 Ok(arc_session)
64 }
65 }
66 }
67
68 pub fn get_session(&self, session_id: &str) -> Option<Arc<Session>> {
70 self.sessions.get(session_id).map(|r| Arc::clone(&r))
71 }
72
73 pub fn get_session_or_error(&self, session_id: &str) -> Result<Arc<Session>> {
75 self.get_session(session_id)
76 .ok_or_else(|| AgentError::SessionNotFound(session_id.to_string()))
77 }
78
79 pub fn remove_session(&self, session_id: &str) -> Option<Arc<Session>> {
81 self.sessions.remove(session_id).map(|(_, v)| v)
82 }
83
84 pub fn has_session(&self, session_id: &str) -> bool {
86 self.sessions.contains_key(session_id)
87 }
88
89 pub fn session_count(&self) -> usize {
91 self.sessions.len()
92 }
93
94 pub fn session_ids(&self) -> Vec<String> {
96 self.sessions.iter().map(|r| r.key().clone()).collect()
97 }
98
99 pub fn clear(&self) {
101 self.sessions.clear();
102 }
103
104 pub fn with_session<F, R>(&self, session_id: &str, f: F) -> Option<R>
108 where
109 F: FnOnce(&Arc<Session>) -> R,
110 {
111 self.sessions.get(session_id).map(|r| f(&r))
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 fn test_config() -> AgentConfig {
120 AgentConfig {
121 base_url: None,
122 api_key: None,
123 model: None,
124 small_fast_model: None,
125 max_thinking_tokens: None,
126 }
127 }
128
129 #[test]
130 fn test_manager_new() {
131 let manager = SessionManager::new();
132 assert_eq!(manager.session_count(), 0);
133 }
134
135 #[test]
136 fn test_manager_create_session() {
137 let manager = SessionManager::new();
138 let config = test_config();
139
140 let session = manager
141 .create_session(
142 "session-1".to_string(),
143 PathBuf::from("/tmp"),
144 &config,
145 None,
146 )
147 .unwrap();
148
149 assert_eq!(session.session_id, "session-1");
150 assert_eq!(manager.session_count(), 1);
151 assert!(manager.has_session("session-1"));
152 }
153
154 #[test]
155 fn test_manager_get_session() {
156 let manager = SessionManager::new();
157 let config = test_config();
158
159 manager
160 .create_session(
161 "session-1".to_string(),
162 PathBuf::from("/tmp"),
163 &config,
164 None,
165 )
166 .unwrap();
167
168 let session = manager.get_session("session-1");
169 assert!(session.is_some());
170 assert_eq!(session.unwrap().session_id, "session-1");
171
172 let missing = manager.get_session("nonexistent");
173 assert!(missing.is_none());
174 }
175
176 #[test]
177 fn test_manager_get_session_or_error() {
178 let manager = SessionManager::new();
179 let config = test_config();
180
181 manager
182 .create_session(
183 "session-1".to_string(),
184 PathBuf::from("/tmp"),
185 &config,
186 None,
187 )
188 .unwrap();
189
190 let result = manager.get_session_or_error("session-1");
191 assert!(result.is_ok());
192
193 let error = manager.get_session_or_error("nonexistent");
194 assert!(matches!(error, Err(AgentError::SessionNotFound(_))));
195 }
196
197 #[test]
198 fn test_manager_remove_session() {
199 let manager = SessionManager::new();
200 let config = test_config();
201
202 manager
203 .create_session(
204 "session-1".to_string(),
205 PathBuf::from("/tmp"),
206 &config,
207 None,
208 )
209 .unwrap();
210
211 assert!(manager.has_session("session-1"));
212
213 let removed = manager.remove_session("session-1");
214 assert!(removed.is_some());
215 assert!(!manager.has_session("session-1"));
216 assert_eq!(manager.session_count(), 0);
217 }
218
219 #[test]
220 fn test_manager_duplicate_session() {
221 let manager = SessionManager::new();
222 let config = test_config();
223
224 manager
225 .create_session(
226 "session-1".to_string(),
227 PathBuf::from("/tmp"),
228 &config,
229 None,
230 )
231 .unwrap();
232
233 let duplicate = manager.create_session(
234 "session-1".to_string(),
235 PathBuf::from("/tmp"),
236 &config,
237 None,
238 );
239
240 assert!(matches!(
241 duplicate,
242 Err(AgentError::SessionAlreadyExists(_))
243 ));
244 }
245
246 #[test]
247 fn test_manager_session_ids() {
248 let manager = SessionManager::new();
249 let config = test_config();
250
251 manager
252 .create_session(
253 "session-1".to_string(),
254 PathBuf::from("/tmp"),
255 &config,
256 None,
257 )
258 .unwrap();
259 manager
260 .create_session(
261 "session-2".to_string(),
262 PathBuf::from("/tmp"),
263 &config,
264 None,
265 )
266 .unwrap();
267
268 let ids = manager.session_ids();
269 assert_eq!(ids.len(), 2);
270 assert!(ids.contains(&"session-1".to_string()));
271 assert!(ids.contains(&"session-2".to_string()));
272 }
273
274 #[test]
275 fn test_manager_clear() {
276 let manager = SessionManager::new();
277 let config = test_config();
278
279 manager
280 .create_session(
281 "session-1".to_string(),
282 PathBuf::from("/tmp"),
283 &config,
284 None,
285 )
286 .unwrap();
287 manager
288 .create_session(
289 "session-2".to_string(),
290 PathBuf::from("/tmp"),
291 &config,
292 None,
293 )
294 .unwrap();
295
296 assert_eq!(manager.session_count(), 2);
297
298 manager.clear();
299 assert_eq!(manager.session_count(), 0);
300 }
301
302 #[test]
303 fn test_manager_with_session() {
304 let manager = SessionManager::new();
305 let config = test_config();
306
307 manager
308 .create_session(
309 "session-1".to_string(),
310 PathBuf::from("/tmp"),
311 &config,
312 None,
313 )
314 .unwrap();
315
316 let result = manager.with_session("session-1", |session| session.session_id.clone());
317
318 assert_eq!(result, Some("session-1".to_string()));
319
320 let missing = manager.with_session("nonexistent", |_| "found");
321 assert!(missing.is_none());
322 }
323}