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 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 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}