agtrace_providers/
traits.rs1use agtrace_types::{AgentEvent, ToolCallPayload, ToolKind, ToolOrigin};
2use anyhow::Result;
3use serde_json::Value;
4use std::path::{Path, PathBuf};
5
6pub trait LogDiscovery: Send + Sync {
13 fn id(&self) -> &'static str;
15
16 fn probe(&self, path: &Path) -> ProbeResult;
18
19 fn resolve_log_root(&self, project_root: &Path) -> Option<PathBuf>;
22
23 fn scan_sessions(&self, log_root: &Path) -> Result<Vec<SessionIndex>>;
25
26 fn extract_session_id(&self, path: &Path) -> Result<String>;
28
29 fn extract_project_hash(&self, path: &Path) -> Result<Option<agtrace_types::ProjectHash>>;
32
33 fn find_session_files(&self, log_root: &Path, session_id: &str) -> Result<Vec<PathBuf>>;
35}
36
37pub trait SessionParser: Send + Sync {
44 fn parse_file(&self, path: &Path) -> Result<Vec<AgentEvent>>;
46
47 fn parse_record(&self, content: &str) -> Result<Option<AgentEvent>>;
50}
51
52pub trait ToolMapper: Send + Sync {
59 fn classify(&self, tool_name: &str) -> (ToolOrigin, ToolKind);
61
62 fn normalize_call(&self, name: &str, args: Value, call_id: Option<String>) -> ToolCallPayload;
64
65 fn summarize(&self, kind: ToolKind, args: &Value) -> String;
67}
68
69#[derive(Debug, Clone, Copy, PartialEq)]
73pub enum ProbeResult {
74 Confidence(f32),
76 NoMatch,
78}
79
80impl ProbeResult {
81 pub fn match_high() -> Self {
83 ProbeResult::Confidence(1.0)
84 }
85
86 pub fn match_medium() -> Self {
88 ProbeResult::Confidence(0.5)
89 }
90
91 pub fn match_low() -> Self {
93 ProbeResult::Confidence(0.3)
94 }
95
96 pub fn is_match(&self) -> bool {
98 matches!(self, ProbeResult::Confidence(c) if *c > 0.0)
99 }
100
101 pub fn confidence(&self) -> f32 {
103 match self {
104 ProbeResult::Confidence(c) => *c,
105 ProbeResult::NoMatch => 0.0,
106 }
107 }
108}
109
110#[derive(Debug, Clone)]
119pub struct SessionIndex {
120 pub session_id: String,
121 pub timestamp: Option<String>,
122 pub latest_mod_time: Option<String>,
123 pub main_file: PathBuf,
124 pub sidechain_files: Vec<PathBuf>,
125 pub project_root: Option<PathBuf>,
126 pub snippet: Option<String>,
127}
128
129pub struct ProviderAdapter {
136 pub discovery: Box<dyn LogDiscovery>,
137 pub parser: Box<dyn SessionParser>,
138 pub mapper: Box<dyn ToolMapper>,
139}
140
141impl ProviderAdapter {
142 pub fn new(
143 discovery: Box<dyn LogDiscovery>,
144 parser: Box<dyn SessionParser>,
145 mapper: Box<dyn ToolMapper>,
146 ) -> Self {
147 Self {
148 discovery,
149 parser,
150 mapper,
151 }
152 }
153
154 pub fn from_name(provider_name: &str) -> Result<Self> {
156 match provider_name {
157 "claude_code" | "claude" => Ok(Self::claude()),
158 "codex" => Ok(Self::codex()),
159 "gemini" => Ok(Self::gemini()),
160 _ => anyhow::bail!("Unknown provider: {}", provider_name),
161 }
162 }
163
164 pub fn claude() -> Self {
166 Self::new(
167 Box::new(crate::claude::ClaudeDiscovery),
168 Box::new(crate::claude::ClaudeParser),
169 Box::new(crate::claude::ClaudeToolMapper),
170 )
171 }
172
173 pub fn codex() -> Self {
175 Self::new(
176 Box::new(crate::codex::CodexDiscovery),
177 Box::new(crate::codex::CodexParser),
178 Box::new(crate::codex::CodexToolMapper),
179 )
180 }
181
182 pub fn gemini() -> Self {
184 Self::new(
185 Box::new(crate::gemini::GeminiDiscovery),
186 Box::new(crate::gemini::GeminiParser),
187 Box::new(crate::gemini::GeminiToolMapper),
188 )
189 }
190
191 pub fn id(&self) -> &'static str {
193 self.discovery.id()
194 }
195
196 pub fn process_file(&self, path: &Path) -> Result<Vec<AgentEvent>> {
198 if !self.discovery.probe(path).is_match() {
199 anyhow::bail!(
200 "Provider {} cannot handle file: {}",
201 self.id(),
202 path.display()
203 );
204 }
205 self.parser.parse_file(path)
206 }
207}
208
209pub fn get_latest_mod_time_rfc3339(files: &[&std::path::Path]) -> Option<String> {
216 use chrono::{DateTime, Utc};
217 use std::time::SystemTime;
218
219 let mut latest: Option<SystemTime> = None;
220
221 for path in files {
222 if let Ok(metadata) = std::fs::metadata(path)
223 && let Ok(modified) = metadata.modified()
224 && (latest.is_none() || Some(modified) > latest)
225 {
226 latest = Some(modified);
227 }
228 }
229
230 latest.map(|t| {
231 let dt: DateTime<Utc> = t.into();
232 dt.to_rfc3339()
233 })
234}