1use std::path::{Path, PathBuf};
2use std::sync::Arc;
3
4use crate::error::{Error, Result};
5use crate::types::*;
6use crate::watch::WatchBuilder;
7
8pub struct Client {
14 inner: Arc<agtrace_runtime::AgTrace>,
15}
16
17impl Client {
18 pub fn connect(path: impl Into<PathBuf>) -> Result<Self> {
20 let path = path.into();
21 let runtime = agtrace_runtime::AgTrace::open(path).map_err(Error::Internal)?;
22 Ok(Self {
23 inner: Arc::new(runtime),
24 })
25 }
26
27 pub fn sessions(&self) -> SessionClient {
29 SessionClient {
30 inner: self.inner.clone(),
31 }
32 }
33
34 pub fn projects(&self) -> ProjectClient {
36 ProjectClient {
37 inner: self.inner.clone(),
38 }
39 }
40
41 pub fn watch(&self) -> WatchClient {
43 WatchClient {
44 inner: self.inner.clone(),
45 }
46 }
47
48 pub fn insights(&self) -> InsightClient {
50 InsightClient {
51 inner: self.inner.clone(),
52 }
53 }
54
55 pub fn system(&self) -> SystemClient {
57 SystemClient {
58 inner: self.inner.clone(),
59 }
60 }
61
62 pub fn watch_service(&self) -> crate::types::WatchService {
65 self.inner.watch_service()
66 }
67
68 #[deprecated(note = "Use client.sessions().get(id) instead")]
70 pub fn session(&self, id: &str) -> SessionHandle {
71 SessionHandle {
72 source: SessionSource::Workspace {
73 inner: self.inner.clone(),
74 id: id.to_string(),
75 },
76 }
77 }
78
79 #[deprecated(note = "Use client.sessions().list(filter) instead")]
80 pub fn list_sessions(&self) -> Result<Vec<SessionSummary>> {
81 let filter = SessionFilter::new().limit(100);
82 self.inner.sessions().list(filter).map_err(Error::Internal)
83 }
84}
85
86pub struct SessionClient {
92 inner: Arc<agtrace_runtime::AgTrace>,
93}
94
95impl SessionClient {
96 pub fn list(&self, filter: SessionFilter) -> Result<Vec<SessionSummary>> {
98 self.inner.sessions().list(filter).map_err(Error::Internal)
99 }
100
101 pub fn list_without_refresh(&self, filter: SessionFilter) -> Result<Vec<SessionSummary>> {
103 self.inner
104 .sessions()
105 .list_without_refresh(filter)
106 .map_err(Error::Internal)
107 }
108
109 pub fn pack_context(
111 &self,
112 project_hash: Option<&crate::types::ProjectHash>,
113 limit: usize,
114 ) -> Result<crate::types::PackResult> {
115 self.inner
116 .sessions()
117 .pack_context(project_hash, limit)
118 .map_err(Error::Internal)
119 }
120
121 pub fn get(&self, id_or_prefix: &str) -> Result<SessionHandle> {
123 self.inner
125 .sessions()
126 .find(id_or_prefix)
127 .map_err(|e| Error::NotFound(format!("Session {}: {}", id_or_prefix, e)))?;
128
129 Ok(SessionHandle {
130 source: SessionSource::Workspace {
131 inner: self.inner.clone(),
132 id: id_or_prefix.to_string(),
133 },
134 })
135 }
136}
137
138pub struct SessionHandle {
144 source: SessionSource,
145}
146
147enum SessionSource {
148 Workspace {
150 inner: Arc<agtrace_runtime::AgTrace>,
151 id: String,
152 },
153 Events {
155 events: Vec<crate::types::AgentEvent>,
156 },
157}
158
159impl SessionHandle {
160 pub fn from_events(events: Vec<AgentEvent>) -> Self {
179 Self {
180 source: SessionSource::Events { events },
181 }
182 }
183
184 pub fn events(&self) -> Result<Vec<AgentEvent>> {
186 match &self.source {
187 SessionSource::Workspace { inner, id } => {
188 let session_handle = inner
189 .sessions()
190 .find(id)
191 .map_err(|e| Error::NotFound(format!("Session {}: {}", id, e)))?;
192
193 session_handle.events().map_err(Error::Internal)
194 }
195 SessionSource::Events { events } => Ok(events.clone()),
196 }
197 }
198
199 pub fn assemble(&self) -> Result<AgentSession> {
201 let events = self.events()?;
202 agtrace_engine::assemble_session(&events)
203 .ok_or_else(|| Error::Internal(anyhow::anyhow!("Failed to assemble session")))
204 }
205
206 pub fn export(&self, strategy: ExportStrategy) -> Result<Vec<AgentEvent>> {
208 let events = self.events()?;
209 Ok(agtrace_engine::export::transform(&events, strategy))
210 }
211
212 pub fn summarize(&self) -> Result<agtrace_engine::SessionSummary> {
214 let session = self.assemble()?;
215 Ok(agtrace_engine::session::summarize(&session))
216 }
217
218 pub fn analyze(&self) -> Result<crate::analysis::SessionAnalyzer> {
220 let session = self.assemble()?;
221 Ok(crate::analysis::SessionAnalyzer::new(session))
222 }
223
224 #[deprecated(note = "Use summarize() instead")]
226 pub fn summary(&self) -> Result<agtrace_engine::SessionSummary> {
227 self.summarize()
228 }
229}
230
231pub struct ProjectClient {
237 inner: Arc<agtrace_runtime::AgTrace>,
238}
239
240impl ProjectClient {
241 pub fn list(&self) -> Result<Vec<ProjectInfo>> {
243 self.inner.projects().list().map_err(Error::Internal)
244 }
245}
246
247pub struct WatchClient {
253 inner: Arc<agtrace_runtime::AgTrace>,
254}
255
256impl WatchClient {
257 pub fn builder(&self) -> WatchBuilder {
259 WatchBuilder::new(self.inner.clone())
260 }
261
262 pub fn all_providers(&self) -> WatchBuilder {
264 WatchBuilder::new(self.inner.clone()).all_providers()
265 }
266
267 pub fn provider(&self, name: &str) -> WatchBuilder {
269 WatchBuilder::new(self.inner.clone()).provider(name)
270 }
271
272 pub fn session(&self, _id: &str) -> WatchBuilder {
274 WatchBuilder::new(self.inner.clone())
276 }
277}
278
279pub struct InsightClient {
285 inner: Arc<agtrace_runtime::AgTrace>,
286}
287
288impl InsightClient {
289 pub fn corpus_stats(
291 &self,
292 project_hash: Option<&agtrace_types::ProjectHash>,
293 limit: usize,
294 ) -> Result<CorpusStats> {
295 self.inner
296 .insights()
297 .corpus_stats(project_hash, limit)
298 .map_err(Error::Internal)
299 }
300
301 pub fn tool_usage(
303 &self,
304 limit: Option<usize>,
305 provider: Option<String>,
306 ) -> Result<agtrace_runtime::StatsResult> {
307 self.inner
308 .insights()
309 .tool_usage(limit, provider)
310 .map_err(Error::Internal)
311 }
312
313 pub fn pack(&self, _limit: usize) -> Result<PackResult> {
315 Err(Error::InvalidInput(
317 "Pack operation not yet implemented in runtime".to_string(),
318 ))
319 }
320
321 pub fn grep(
323 &self,
324 _pattern: &str,
325 _filter: &SessionFilter,
326 _limit: usize,
327 ) -> Result<Vec<AgentEvent>> {
328 Err(Error::InvalidInput(
330 "Grep operation not yet implemented in runtime".to_string(),
331 ))
332 }
333}
334
335pub struct SystemClient {
341 inner: Arc<agtrace_runtime::AgTrace>,
342}
343
344impl SystemClient {
345 pub fn initialize<F>(config: InitConfig, on_progress: Option<F>) -> Result<InitResult>
347 where
348 F: FnMut(InitProgress),
349 {
350 agtrace_runtime::AgTrace::setup(config, on_progress).map_err(Error::Internal)
351 }
352
353 pub fn diagnose(&self) -> Result<Vec<DiagnoseResult>> {
355 self.inner.diagnose().map_err(Error::Internal)
356 }
357
358 pub fn check_file(&self, path: &Path, provider: Option<&str>) -> Result<CheckResult> {
360 let path_str = path
361 .to_str()
362 .ok_or_else(|| Error::InvalidInput("Path contains invalid UTF-8".to_string()))?;
363
364 let (adapter, provider_name) = if let Some(name) = provider {
366 let adapter = agtrace_providers::create_adapter(name)
367 .map_err(|_| Error::NotFound(format!("Provider: {}", name)))?;
368 (adapter, name.to_string())
369 } else {
370 let adapter = agtrace_providers::detect_adapter_from_path(path_str)
371 .map_err(|_| Error::NotFound("No suitable provider detected".to_string()))?;
372 let name = format!("{} (auto-detected)", adapter.id());
373 (adapter, name)
374 };
375
376 agtrace_runtime::AgTrace::check_file(path_str, &adapter, &provider_name)
377 .map_err(Error::Internal)
378 }
379
380 pub fn inspect_file(path: &Path, lines: usize, json_format: bool) -> Result<InspectResult> {
382 let path_str = path
383 .to_str()
384 .ok_or_else(|| Error::InvalidInput("Path contains invalid UTF-8".to_string()))?;
385
386 agtrace_runtime::AgTrace::inspect_file(path_str, lines, json_format)
387 .map_err(Error::Internal)
388 }
389
390 pub fn reindex<F>(
392 &self,
393 scope: agtrace_types::ProjectScope,
394 force: bool,
395 provider_filter: Option<&str>,
396 on_progress: F,
397 ) -> Result<()>
398 where
399 F: FnMut(IndexProgress),
400 {
401 self.inner
402 .projects()
403 .scan(scope, force, provider_filter, on_progress)
404 .map(|_| ()) .map_err(Error::Internal)
406 }
407
408 pub fn vacuum(&self) -> Result<()> {
410 let db = self.inner.database();
411 let db = db.lock().unwrap();
412 db.vacuum().map_err(Error::Internal)
413 }
414
415 pub fn list_providers(&self) -> Result<Vec<ProviderConfig>> {
417 Ok(self.inner.config().providers.values().cloned().collect())
418 }
419
420 pub fn detect_providers() -> Result<Config> {
422 agtrace_runtime::Config::detect_providers().map_err(Error::Internal)
423 }
424
425 pub fn config(&self) -> Config {
427 self.inner.config().clone()
428 }
429}