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