agtrace_runtime/client/
sessions.rs

1use crate::ops::{
2    ExportService, IndexProgress, IndexService, ListSessionsRequest, PackResult, PackService,
3    SessionService,
4};
5use crate::storage::{RawFileContent, get_raw_files};
6use crate::{Error, Result};
7use agtrace_engine::export::ExportStrategy;
8use agtrace_index::{Database, SessionSummary};
9use agtrace_providers::ProviderAdapter;
10use agtrace_types::AgentEvent;
11use std::path::PathBuf;
12use std::sync::{Arc, Mutex};
13
14#[derive(Debug, Clone)]
15pub struct SessionFilter {
16    pub scope: agtrace_types::ProjectScope,
17    pub limit: usize,
18    pub provider: Option<String>,
19    pub since: Option<String>,
20    pub until: Option<String>,
21}
22
23impl SessionFilter {
24    /// Create a filter for all projects
25    pub fn all() -> Self {
26        Self {
27            scope: agtrace_types::ProjectScope::All,
28            limit: 100,
29            provider: None,
30            since: None,
31            until: None,
32        }
33    }
34
35    /// Create a filter for a specific project
36    pub fn project(project_hash: agtrace_types::ProjectHash) -> Self {
37        Self {
38            scope: agtrace_types::ProjectScope::Specific(project_hash),
39            limit: 100,
40            provider: None,
41            since: None,
42            until: None,
43        }
44    }
45
46    pub fn limit(mut self, limit: usize) -> Self {
47        self.limit = limit;
48        self
49    }
50
51    pub fn provider(mut self, provider: String) -> Self {
52        self.provider = Some(provider);
53        self
54    }
55
56    pub fn since(mut self, since: String) -> Self {
57        self.since = Some(since);
58        self
59    }
60
61    pub fn until(mut self, until: String) -> Self {
62        self.until = Some(until);
63        self
64    }
65}
66
67pub struct SessionOps {
68    db: Arc<Mutex<Database>>,
69    provider_configs: Arc<Vec<(String, PathBuf)>>,
70}
71
72impl SessionOps {
73    pub fn new(db: Arc<Mutex<Database>>, provider_configs: Arc<Vec<(String, PathBuf)>>) -> Self {
74        Self {
75            db,
76            provider_configs,
77        }
78    }
79
80    pub fn list(&self, filter: SessionFilter) -> Result<Vec<SessionSummary>> {
81        self.ensure_index_is_fresh()?;
82        self.list_without_refresh(filter)
83    }
84
85    pub fn list_without_refresh(&self, filter: SessionFilter) -> Result<Vec<SessionSummary>> {
86        let db = self.db.lock().unwrap();
87        let service = SessionService::new(&db);
88        let request = ListSessionsRequest {
89            scope: filter.scope,
90            limit: filter.limit,
91            provider: filter.provider,
92            since: filter.since,
93            until: filter.until,
94        };
95        service.list_sessions(request)
96    }
97
98    fn ensure_index_is_fresh(&self) -> Result<()> {
99        let db = self.db.lock().unwrap();
100
101        let providers: Vec<(ProviderAdapter, PathBuf)> = self
102            .provider_configs
103            .iter()
104            .filter_map(|(name, path)| {
105                agtrace_providers::create_adapter(name)
106                    .ok()
107                    .map(|p| (p, path.clone()))
108            })
109            .collect();
110
111        let service = IndexService::new(&db, providers);
112
113        // Scan all projects without filtering
114        let scope = agtrace_types::ProjectScope::All;
115
116        service.run(scope, false, |_progress: IndexProgress| {})?;
117
118        Ok(())
119    }
120
121    pub fn find(&self, session_id: &str) -> Result<SessionHandle> {
122        if let Some(resolved_id) = self.resolve_session_id(session_id)? {
123            return Ok(SessionHandle {
124                id: resolved_id,
125                db: self.db.clone(),
126            });
127        }
128
129        self.ensure_index_is_fresh()?;
130
131        if let Some(resolved_id) = self.resolve_session_id(session_id)? {
132            return Ok(SessionHandle {
133                id: resolved_id,
134                db: self.db.clone(),
135            });
136        }
137
138        Err(Error::InvalidOperation(format!(
139            "Session not found: {}",
140            session_id
141        )))
142    }
143
144    fn resolve_session_id(&self, session_id: &str) -> Result<Option<String>> {
145        let db = self.db.lock().unwrap();
146
147        if let Some(session) = db.get_session_by_id(session_id)? {
148            return Ok(Some(session.id));
149        }
150
151        Ok(db.find_session_by_prefix(session_id)?)
152    }
153
154    pub fn pack_context(
155        &self,
156        project_hash: Option<&agtrace_types::ProjectHash>,
157        limit: usize,
158    ) -> Result<PackResult> {
159        self.ensure_index_is_fresh()?;
160
161        let db = self.db.lock().unwrap();
162        let service = PackService::new(&db);
163        service.select_sessions(project_hash, limit)
164    }
165}
166
167pub struct SessionHandle {
168    id: String,
169    db: Arc<Mutex<Database>>,
170}
171
172impl SessionHandle {
173    pub fn events(&self) -> Result<Vec<AgentEvent>> {
174        let db = self.db.lock().unwrap();
175        let service = SessionService::new(&db);
176        service.get_session_events(&self.id)
177    }
178
179    pub fn raw_files(&self) -> Result<Vec<RawFileContent>> {
180        let db = self.db.lock().unwrap();
181        get_raw_files(&db, &self.id)
182    }
183
184    pub fn export(&self, strategy: ExportStrategy) -> Result<Vec<AgentEvent>> {
185        let db = self.db.lock().unwrap();
186        let service = ExportService::new(&db);
187        service.export_session(&self.id, strategy)
188    }
189
190    pub fn id(&self) -> &str {
191        &self.id
192    }
193}