agtrace_runtime/client/
sessions.rs1use 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: Option<usize>,
18 pub provider: Option<String>,
19 pub order: agtrace_types::SessionOrder,
20 pub since: Option<String>,
21 pub until: Option<String>,
22}
23
24impl SessionFilter {
25 pub fn all() -> Self {
27 Self {
28 scope: agtrace_types::ProjectScope::All,
29 limit: None,
30 provider: None,
31 order: agtrace_types::SessionOrder::default(),
32 since: None,
33 until: None,
34 }
35 }
36
37 pub fn project(project_hash: agtrace_types::ProjectHash) -> Self {
39 Self {
40 scope: agtrace_types::ProjectScope::Specific(project_hash),
41 limit: None,
42 provider: None,
43 order: agtrace_types::SessionOrder::default(),
44 since: None,
45 until: None,
46 }
47 }
48
49 pub fn limit(mut self, limit: usize) -> Self {
50 self.limit = Some(limit);
51 self
52 }
53
54 pub fn provider(mut self, provider: String) -> Self {
55 self.provider = Some(provider);
56 self
57 }
58
59 pub fn order(mut self, order: agtrace_types::SessionOrder) -> Self {
60 self.order = order;
61 self
62 }
63
64 pub fn since(mut self, since: String) -> Self {
65 self.since = Some(since);
66 self
67 }
68
69 pub fn until(mut self, until: String) -> Self {
70 self.until = Some(until);
71 self
72 }
73}
74
75pub struct SessionOps {
76 db: Arc<Mutex<Database>>,
77 provider_configs: Arc<Vec<(String, PathBuf)>>,
78}
79
80impl SessionOps {
81 pub fn new(db: Arc<Mutex<Database>>, provider_configs: Arc<Vec<(String, PathBuf)>>) -> Self {
82 Self {
83 db,
84 provider_configs,
85 }
86 }
87
88 pub fn list(&self, filter: SessionFilter) -> Result<Vec<SessionSummary>> {
89 self.ensure_index_is_fresh()?;
90 self.list_without_refresh(filter)
91 }
92
93 pub fn list_without_refresh(&self, filter: SessionFilter) -> Result<Vec<SessionSummary>> {
94 let db = self.db.lock().unwrap();
95 let service = SessionService::new(&db);
96 let request = ListSessionsRequest {
97 scope: filter.scope,
98 limit: filter.limit,
99 provider: filter.provider,
100 order: filter.order,
101 since: filter.since,
102 until: filter.until,
103 };
104 service.list_sessions(request)
105 }
106
107 fn ensure_index_is_fresh(&self) -> Result<()> {
108 let db = self.db.lock().unwrap();
109
110 let providers: Vec<(ProviderAdapter, PathBuf)> = self
111 .provider_configs
112 .iter()
113 .filter_map(|(name, path)| {
114 agtrace_providers::create_adapter(name)
115 .ok()
116 .map(|p| (p, path.clone()))
117 })
118 .collect();
119
120 let service = IndexService::new(&db, providers);
121
122 let scope = agtrace_types::ProjectScope::All;
124
125 service.run(scope, false, |_progress: IndexProgress| {})?;
126
127 Ok(())
128 }
129
130 pub fn find(&self, session_id: &str) -> Result<SessionHandle> {
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 self.ensure_index_is_fresh()?;
139
140 if let Some(resolved_id) = self.resolve_session_id(session_id)? {
141 return Ok(SessionHandle {
142 id: resolved_id,
143 db: self.db.clone(),
144 });
145 }
146
147 Err(Error::InvalidOperation(format!(
148 "Session not found: {}",
149 session_id
150 )))
151 }
152
153 fn resolve_session_id(&self, session_id: &str) -> Result<Option<String>> {
154 let db = self.db.lock().unwrap();
155
156 if let Some(session) = db.get_session_by_id(session_id)? {
157 return Ok(Some(session.id));
158 }
159
160 Ok(db.find_session_by_prefix(session_id)?)
161 }
162
163 pub fn pack_context(
164 &self,
165 project_hash: Option<&agtrace_types::ProjectHash>,
166 limit: usize,
167 ) -> Result<PackResult> {
168 self.ensure_index_is_fresh()?;
169
170 let db = self.db.lock().unwrap();
171 let service = PackService::new(&db);
172 service.select_sessions(project_hash, limit)
173 }
174}
175
176pub struct SessionHandle {
177 id: String,
178 db: Arc<Mutex<Database>>,
179}
180
181impl SessionHandle {
182 pub fn events(&self) -> Result<Vec<AgentEvent>> {
183 let db = self.db.lock().unwrap();
184 let service = SessionService::new(&db);
185 service.get_session_events(&self.id)
186 }
187
188 pub fn raw_files(&self) -> Result<Vec<RawFileContent>> {
189 let db = self.db.lock().unwrap();
190 get_raw_files(&db, &self.id)
191 }
192
193 pub fn export(&self, strategy: ExportStrategy) -> Result<Vec<AgentEvent>> {
194 let db = self.db.lock().unwrap();
195 let service = ExportService::new(&db);
196 service.export_session(&self.id, strategy)
197 }
198
199 pub fn metadata(&self) -> Result<agtrace_types::SessionMetadata> {
200 let db = self.db.lock().unwrap();
201 let index_summary = db.get_session_by_id(&self.id)?.ok_or_else(|| {
202 Error::InvalidOperation(format!("Session metadata not found: {}", self.id))
203 })?;
204
205 let project_root = db
207 .get_project(index_summary.project_hash.as_str())?
208 .and_then(|p| p.root_path);
209
210 Ok(agtrace_types::SessionMetadata {
211 session_id: index_summary.id.clone(),
212 project_hash: index_summary.project_hash,
213 project_root,
214 provider: index_summary.provider,
215 })
216 }
217
218 pub fn id(&self) -> &str {
219 &self.id
220 }
221}