1use std::collections::HashMap;
73use std::path::PathBuf;
74use std::sync::Arc;
75use std::time::Duration;
76
77use anyhow::Result;
78use chrono::{DateTime, Utc};
79use dashmap::DashMap;
80use serde::{Deserialize, Serialize};
81use tokio::sync::RwLock;
82use uuid::Uuid;
83
84pub mod headless;
85pub mod lifecycle;
86pub mod process;
87pub mod pty;
88pub mod terminal;
89
90use crate::context::SessionContext;
91use crate::persistence::CommandRecord;
92
93#[derive(Debug, thiserror::Error)]
95pub enum SessionError {
96 #[error("Session not found: {0}")]
97 NotFound(SessionId),
98
99 #[error("Session already exists: {0}")]
100 AlreadyExists(SessionId),
101
102 #[error("PTY error: {0}")]
103 PtyError(String),
104
105 #[error("Process error: {0}")]
106 ProcessError(String),
107
108 #[error("IO error: {0}")]
109 IoError(#[from] std::io::Error),
110
111 #[error("Other error: {0}")]
112 Other(#[from] anyhow::Error),
113}
114
115pub type SessionResult<T> = std::result::Result<T, SessionError>;
117
118#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
120pub struct SessionId(Uuid);
121
122impl Default for SessionId {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128impl SessionId {
129 pub fn new() -> Self {
131 Self(Uuid::new_v4())
132 }
133
134 pub fn new_v4() -> Self {
136 Self(Uuid::new_v4())
137 }
138
139 pub fn parse_str(s: &str) -> Result<Self> {
141 Ok(Self(Uuid::parse_str(s)?))
142 }
143
144 pub fn to_string(&self) -> String {
146 self.0.to_string()
147 }
148
149 pub fn as_uuid(&self) -> &Uuid {
151 &self.0
152 }
153}
154
155impl std::fmt::Display for SessionId {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 write!(f, "{}", self.0)
158 }
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
163pub enum SessionStatus {
164 #[default]
166 Initializing,
167 Running,
169 Paused,
171 Terminating,
173 Terminated,
175 Error,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181#[serde(default)]
182pub struct SessionConfig {
183 pub name: Option<String>,
185 pub working_directory: PathBuf,
187 pub environment: HashMap<String, String>,
189 pub shell: Option<String>,
191 pub shell_command: Option<String>,
193 pub pty_size: (u16, u16),
195 pub output_buffer_size: usize,
197 pub timeout: Option<Duration>,
199 pub compress_output: bool,
201 pub parse_output: bool,
203 pub enable_ai_features: bool,
205 pub context_config: ContextConfig,
207 pub agent_role: Option<String>,
209 pub force_headless: bool,
211 pub allow_headless_fallback: bool,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize)]
217#[serde(default)]
218pub struct ContextConfig {
219 pub max_tokens: usize,
221 pub compression_threshold: f64,
223}
224
225impl Default for SessionConfig {
226 fn default() -> Self {
227 Self {
228 name: None,
229 working_directory: std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/")),
230 environment: HashMap::new(),
231 shell: None,
232 shell_command: None,
233 pty_size: (24, 80),
234 output_buffer_size: 1024 * 1024, timeout: None,
236 compress_output: true,
237 parse_output: true,
238 enable_ai_features: false,
239 context_config: ContextConfig::default(),
240 agent_role: None,
241 force_headless: false,
242 allow_headless_fallback: true,
243 }
244 }
245}
246
247impl Default for ContextConfig {
248 fn default() -> Self {
249 Self {
250 max_tokens: 4096,
251 compression_threshold: 0.8,
252 }
253 }
254}
255
256pub struct AISession {
258 pub id: SessionId,
260 pub config: SessionConfig,
262 pub status: RwLock<SessionStatus>,
264 pub context: Arc<RwLock<SessionContext>>,
266 process: Arc<RwLock<Option<process::ProcessHandle>>>,
268 terminal: Arc<RwLock<Option<terminal::TerminalHandle>>>,
270 pub created_at: DateTime<Utc>,
272 pub last_activity: Arc<RwLock<DateTime<Utc>>>,
274 pub metadata: Arc<RwLock<HashMap<String, serde_json::Value>>>,
276 pub command_history: Arc<RwLock<Vec<CommandRecord>>>,
278 pub command_count: Arc<RwLock<usize>>,
280 pub total_tokens: Arc<RwLock<usize>>,
282}
283
284impl AISession {
285 pub async fn new(config: SessionConfig) -> Result<Self> {
287 let id = SessionId::new();
288 let now = Utc::now();
289
290 Ok(Self {
291 id: id.clone(),
292 config,
293 status: RwLock::new(SessionStatus::Initializing),
294 context: Arc::new(RwLock::new(SessionContext::new(id))),
295 process: Arc::new(RwLock::new(None)),
296 terminal: Arc::new(RwLock::new(None)),
297 created_at: now,
298 last_activity: Arc::new(RwLock::new(now)),
299 metadata: Arc::new(RwLock::new(HashMap::new())),
300 command_history: Arc::new(RwLock::new(Vec::new())),
301 command_count: Arc::new(RwLock::new(0)),
302 total_tokens: Arc::new(RwLock::new(0)),
303 })
304 }
305
306 pub async fn new_with_id(
308 id: SessionId,
309 config: SessionConfig,
310 created_at: DateTime<Utc>,
311 ) -> Result<Self> {
312 let now = Utc::now();
313
314 Ok(Self {
315 id: id.clone(),
316 config,
317 status: RwLock::new(SessionStatus::Initializing),
318 context: Arc::new(RwLock::new(SessionContext::new(id))),
319 process: Arc::new(RwLock::new(None)),
320 terminal: Arc::new(RwLock::new(None)),
321 created_at,
322 last_activity: Arc::new(RwLock::new(now)),
323 metadata: Arc::new(RwLock::new(HashMap::new())),
324 command_history: Arc::new(RwLock::new(Vec::new())),
325 command_count: Arc::new(RwLock::new(0)),
326 total_tokens: Arc::new(RwLock::new(0)),
327 })
328 }
329
330 pub async fn start(&self) -> Result<()> {
332 lifecycle::start_session(self).await
333 }
334
335 pub async fn stop(&self) -> Result<()> {
337 lifecycle::stop_session(self).await
338 }
339
340 pub async fn send_input(&self, input: &str) -> Result<()> {
342 let terminal_guard = self.terminal.read().await;
343 if let Some(terminal) = terminal_guard.as_ref() {
344 terminal.write(input.as_bytes()).await?;
345 *self.last_activity.write().await = Utc::now();
346 Ok(())
347 } else {
348 Err(anyhow::anyhow!("Session not started"))
349 }
350 }
351
352 pub async fn read_output(&self) -> Result<Vec<u8>> {
354 let terminal = self.terminal.read().await;
355 if let Some(terminal) = terminal.as_ref() {
356 let output = terminal.read().await?;
357 *self.last_activity.write().await = Utc::now();
358 Ok(output)
359 } else {
360 Err(anyhow::anyhow!("Session not started"))
361 }
362 }
363
364 pub async fn status(&self) -> SessionStatus {
366 *self.status.read().await
367 }
368
369 pub async fn set_metadata(&self, key: String, value: serde_json::Value) -> Result<()> {
371 self.metadata.write().await.insert(key, value);
372 Ok(())
373 }
374
375 pub async fn get_metadata(&self, key: &str) -> Option<serde_json::Value> {
377 self.metadata.read().await.get(key).cloned()
378 }
379
380 pub async fn execute_command(&self, command: &str) -> Result<String> {
382 let start_time = Utc::now();
383
384 self.send_input(&format!("{}\n", command)).await?;
386
387 tokio::time::sleep(Duration::from_millis(500)).await;
389 let output_bytes = self.read_output().await?;
390 let output = String::from_utf8_lossy(&output_bytes).to_string();
391
392 let end_time = Utc::now();
394 let duration_ms = (end_time - start_time).num_milliseconds() as u64;
395
396 let record = CommandRecord {
397 command: command.to_string(),
398 timestamp: start_time,
399 exit_code: None, output_preview: if output.len() > 200 {
401 format!("{}...", &output[..200])
402 } else {
403 output.clone()
404 },
405 duration_ms,
406 };
407
408 self.command_history.write().await.push(record);
410 *self.command_count.write().await += 1;
411
412 Ok(output)
413 }
414
415 pub async fn add_tokens(&self, token_count: usize) {
417 *self.total_tokens.write().await += token_count;
418 }
419
420 pub async fn get_command_history(&self) -> Vec<CommandRecord> {
422 self.command_history.read().await.clone()
423 }
424
425 pub async fn get_command_count(&self) -> usize {
427 *self.command_count.read().await
428 }
429
430 pub async fn get_total_tokens(&self) -> usize {
432 *self.total_tokens.read().await
433 }
434
435 pub async fn trim_command_history(&self, keep_recent: usize) {
437 let mut history = self.command_history.write().await;
438 if history.len() > keep_recent {
439 let start_index = history.len() - keep_recent;
440 history.drain(0..start_index);
441 }
442 }
443}
444
445pub struct SessionManager {
539 sessions: Arc<DashMap<SessionId, Arc<AISession>>>,
541 default_config: SessionConfig,
543}
544
545impl SessionManager {
546 pub fn new() -> Self {
548 Self {
549 sessions: Arc::new(DashMap::new()),
550 default_config: SessionConfig::default(),
551 }
552 }
553
554 pub async fn create_session(&self) -> Result<Arc<AISession>> {
556 self.create_session_with_config(self.default_config.clone())
557 .await
558 }
559
560 pub async fn create_session_with_config(
562 &self,
563 config: SessionConfig,
564 ) -> Result<Arc<AISession>> {
565 let session = Arc::new(AISession::new(config).await?);
566 self.sessions.insert(session.id.clone(), session.clone());
567 Ok(session)
568 }
569
570 pub async fn restore_session(
572 &self,
573 id: SessionId,
574 config: SessionConfig,
575 created_at: DateTime<Utc>,
576 ) -> Result<Arc<AISession>> {
577 if self.sessions.contains_key(&id) {
579 return Err(SessionError::AlreadyExists(id).into());
580 }
581
582 let session = Arc::new(AISession::new_with_id(id.clone(), config, created_at).await?);
583 self.sessions.insert(id, session.clone());
584 Ok(session)
585 }
586
587 pub fn get_session(&self, id: &SessionId) -> Option<Arc<AISession>> {
589 self.sessions.get(id).map(|entry| entry.clone())
590 }
591
592 pub fn list_sessions(&self) -> Vec<SessionId> {
594 self.sessions
595 .iter()
596 .map(|entry| entry.key().clone())
597 .collect()
598 }
599
600 pub fn list_session_refs(&self) -> Vec<Arc<AISession>> {
602 self.sessions
603 .iter()
604 .map(|entry| entry.value().clone())
605 .collect()
606 }
607
608 pub async fn remove_session(&self, id: &SessionId) -> Result<()> {
610 if let Some((_, session)) = self.sessions.remove(id) {
611 session.stop().await?;
612 }
613 Ok(())
614 }
615
616 pub async fn cleanup_terminated(&self) -> Result<usize> {
618 let mut removed = 0;
619 let terminated_ids: Vec<SessionId> = self
620 .sessions
621 .iter()
622 .filter(|entry| {
623 let session = entry.value();
624 if let Ok(status) = session.status.try_read() {
625 *status == SessionStatus::Terminated
626 } else {
627 false
628 }
629 })
630 .map(|entry| entry.key().clone())
631 .collect();
632
633 for id in terminated_ids {
634 self.sessions.remove(&id);
635 removed += 1;
636 }
637
638 Ok(removed)
639 }
640}
641
642impl Default for SessionManager {
643 fn default() -> Self {
644 Self::new()
645 }
646}
647
648#[cfg(test)]
651mod tests {
652 use super::*;
653
654 #[tokio::test]
655 async fn test_session_id() {
656 let id1 = SessionId::new();
657 let id2 = SessionId::new();
658 assert_ne!(id1, id2);
659 }
660
661 #[tokio::test]
662 async fn test_session_manager() {
663 let manager = SessionManager::new();
664 let session = manager.create_session().await.unwrap();
665
666 assert!(manager.get_session(&session.id).is_some());
667 assert_eq!(manager.list_sessions().len(), 1);
668
669 manager.remove_session(&session.id).await.unwrap();
670 assert!(manager.get_session(&session.id).is_none());
671 }
672}