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