agtrace_providers/
traits.rs1use agtrace_types::{AgentEvent, ToolCallPayload, ToolKind, ToolOrigin};
2use serde_json::Value;
3use std::path::{Path, PathBuf};
4
5use crate::{Error, Result};
6
7pub trait LogDiscovery: Send + Sync {
14 fn id(&self) -> &'static str;
16
17 fn probe(&self, path: &Path) -> ProbeResult;
19
20 fn resolve_log_root(&self, project_root: &Path) -> Option<PathBuf>;
23
24 fn scan_sessions(&self, log_root: &Path) -> Result<Vec<SessionIndex>>;
26
27 fn extract_session_id(&self, path: &Path) -> Result<String>;
29
30 fn extract_project_hash(&self, path: &Path) -> Result<Option<agtrace_types::ProjectHash>>;
33
34 fn find_session_files(&self, log_root: &Path, session_id: &str) -> Result<Vec<PathBuf>>;
36}
37
38pub trait SessionParser: Send + Sync {
45 fn parse_file(&self, path: &Path) -> Result<Vec<AgentEvent>>;
47
48 fn parse_record(&self, content: &str) -> Result<Option<AgentEvent>>;
51}
52
53pub trait ToolMapper: Send + Sync {
60 fn classify(&self, tool_name: &str) -> (ToolOrigin, ToolKind);
62
63 fn normalize_call(&self, name: &str, args: Value, call_id: Option<String>) -> ToolCallPayload;
65
66 fn summarize(&self, kind: ToolKind, args: &Value) -> String;
68}
69
70#[derive(Debug, Clone, Copy, PartialEq)]
74pub enum ProbeResult {
75 Confidence(f32),
77 NoMatch,
79}
80
81impl ProbeResult {
82 pub fn match_high() -> Self {
84 ProbeResult::Confidence(1.0)
85 }
86
87 pub fn match_medium() -> Self {
89 ProbeResult::Confidence(0.5)
90 }
91
92 pub fn match_low() -> Self {
94 ProbeResult::Confidence(0.3)
95 }
96
97 pub fn is_match(&self) -> bool {
99 matches!(self, ProbeResult::Confidence(c) if *c > 0.0)
100 }
101
102 pub fn confidence(&self) -> f32 {
104 match self {
105 ProbeResult::Confidence(c) => *c,
106 ProbeResult::NoMatch => 0.0,
107 }
108 }
109}
110
111#[derive(Debug, Clone)]
120pub struct SessionIndex {
121 pub session_id: String,
122 pub timestamp: Option<String>,
123 pub latest_mod_time: Option<String>,
124 pub main_file: PathBuf,
125 pub sidechain_files: Vec<PathBuf>,
126 pub project_root: Option<PathBuf>,
127 pub snippet: Option<String>,
128}
129
130pub struct ProviderAdapter {
137 pub discovery: Box<dyn LogDiscovery>,
138 pub parser: Box<dyn SessionParser>,
139 pub mapper: Box<dyn ToolMapper>,
140}
141
142impl ProviderAdapter {
143 pub fn new(
144 discovery: Box<dyn LogDiscovery>,
145 parser: Box<dyn SessionParser>,
146 mapper: Box<dyn ToolMapper>,
147 ) -> Self {
148 Self {
149 discovery,
150 parser,
151 mapper,
152 }
153 }
154
155 pub fn from_name(provider_name: &str) -> Result<Self> {
157 match provider_name {
158 "claude_code" | "claude" => Ok(Self::claude()),
159 "codex" => Ok(Self::codex()),
160 "gemini" => Ok(Self::gemini()),
161 _ => Err(Error::Provider(format!(
162 "Unknown provider: {}",
163 provider_name
164 ))),
165 }
166 }
167
168 pub fn claude() -> Self {
170 Self::new(
171 Box::new(crate::claude::ClaudeDiscovery),
172 Box::new(crate::claude::ClaudeParser),
173 Box::new(crate::claude::ClaudeToolMapper),
174 )
175 }
176
177 pub fn codex() -> Self {
179 Self::new(
180 Box::new(crate::codex::CodexDiscovery),
181 Box::new(crate::codex::CodexParser),
182 Box::new(crate::codex::CodexToolMapper),
183 )
184 }
185
186 pub fn gemini() -> Self {
188 Self::new(
189 Box::new(crate::gemini::GeminiDiscovery),
190 Box::new(crate::gemini::GeminiParser),
191 Box::new(crate::gemini::GeminiToolMapper),
192 )
193 }
194
195 pub fn id(&self) -> &'static str {
197 self.discovery.id()
198 }
199
200 pub fn process_file(&self, path: &Path) -> Result<Vec<AgentEvent>> {
202 if !self.discovery.probe(path).is_match() {
203 return Err(Error::Provider(format!(
204 "Provider {} cannot handle file: {}",
205 self.id(),
206 path.display()
207 )));
208 }
209 self.parser.parse_file(path)
210 }
211}
212
213pub fn get_latest_mod_time_rfc3339(files: &[&std::path::Path]) -> Option<String> {
220 use chrono::{DateTime, Utc};
221 use std::time::SystemTime;
222
223 let mut latest: Option<SystemTime> = None;
224
225 for path in files {
226 if let Ok(metadata) = std::fs::metadata(path)
227 && let Ok(modified) = metadata.modified()
228 && (latest.is_none() || Some(modified) > latest)
229 {
230 latest = Some(modified);
231 }
232 }
233
234 latest.map(|t| {
235 let dt: DateTime<Utc> = t.into();
236 dt.to_rfc3339()
237 })
238}