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