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 find_session_files(&self, log_root: &Path, session_id: &str) -> Result<Vec<PathBuf>>;
31}
32
33pub trait SessionParser: Send + Sync {
40 fn parse_file(&self, path: &Path) -> Result<Vec<AgentEvent>>;
42
43 fn parse_record(&self, content: &str) -> Result<Option<AgentEvent>>;
46}
47
48pub trait ToolMapper: Send + Sync {
55 fn classify(&self, tool_name: &str) -> (ToolOrigin, ToolKind);
57
58 fn normalize_call(&self, name: &str, args: Value, call_id: Option<String>) -> ToolCallPayload;
60
61 fn summarize(&self, kind: ToolKind, args: &Value) -> String;
63}
64
65#[derive(Debug, Clone, Copy, PartialEq)]
69pub enum ProbeResult {
70 Confidence(f32),
72 NoMatch,
74}
75
76impl ProbeResult {
77 pub fn match_high() -> Self {
79 ProbeResult::Confidence(1.0)
80 }
81
82 pub fn match_medium() -> Self {
84 ProbeResult::Confidence(0.5)
85 }
86
87 pub fn match_low() -> Self {
89 ProbeResult::Confidence(0.3)
90 }
91
92 pub fn is_match(&self) -> bool {
94 matches!(self, ProbeResult::Confidence(c) if *c > 0.0)
95 }
96
97 pub fn confidence(&self) -> f32 {
99 match self {
100 ProbeResult::Confidence(c) => *c,
101 ProbeResult::NoMatch => 0.0,
102 }
103 }
104}
105
106#[derive(Debug, Clone)]
108pub struct SessionIndex {
109 pub session_id: String,
110 pub timestamp: Option<String>,
111 pub main_file: PathBuf,
112 pub sidechain_files: Vec<PathBuf>,
113 pub project_root: Option<String>,
114 pub snippet: Option<String>,
115}
116
117pub struct ProviderAdapter {
124 pub discovery: Box<dyn LogDiscovery>,
125 pub parser: Box<dyn SessionParser>,
126 pub mapper: Box<dyn ToolMapper>,
127}
128
129impl ProviderAdapter {
130 pub fn new(
131 discovery: Box<dyn LogDiscovery>,
132 parser: Box<dyn SessionParser>,
133 mapper: Box<dyn ToolMapper>,
134 ) -> Self {
135 Self {
136 discovery,
137 parser,
138 mapper,
139 }
140 }
141
142 pub fn from_name(provider_name: &str) -> Result<Self> {
144 match provider_name {
145 "claude_code" | "claude" => Ok(Self::claude()),
146 "codex" => Ok(Self::codex()),
147 "gemini" => Ok(Self::gemini()),
148 _ => anyhow::bail!("Unknown provider: {}", provider_name),
149 }
150 }
151
152 pub fn claude() -> Self {
154 Self::new(
155 Box::new(crate::claude::ClaudeDiscovery),
156 Box::new(crate::claude::ClaudeParser),
157 Box::new(crate::claude::ClaudeToolMapper),
158 )
159 }
160
161 pub fn codex() -> Self {
163 Self::new(
164 Box::new(crate::codex::CodexDiscovery),
165 Box::new(crate::codex::CodexParser),
166 Box::new(crate::codex::CodexToolMapper),
167 )
168 }
169
170 pub fn gemini() -> Self {
172 Self::new(
173 Box::new(crate::gemini::GeminiDiscovery),
174 Box::new(crate::gemini::GeminiParser),
175 Box::new(crate::gemini::GeminiToolMapper),
176 )
177 }
178
179 pub fn id(&self) -> &'static str {
181 self.discovery.id()
182 }
183
184 pub fn process_file(&self, path: &Path) -> Result<Vec<AgentEvent>> {
186 if !self.discovery.probe(path).is_match() {
187 anyhow::bail!(
188 "Provider {} cannot handle file: {}",
189 self.id(),
190 path.display()
191 );
192 }
193 self.parser.parse_file(path)
194 }
195}