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