use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::error::{Error, Result};
use crate::types::{
AgentEvent, CheckResult, Config, DiagnoseResult, InspectResult, ProviderConfig,
};
#[derive(Clone)]
pub struct Providers {
config: Arc<Config>,
provider_configs: Vec<(String, PathBuf)>,
}
impl Providers {
pub fn detect() -> Result<Self> {
let config = Config::detect_providers().map_err(Error::Runtime)?;
Ok(Self::with_config(config))
}
pub fn with_config(config: Config) -> Self {
let provider_configs: Vec<(String, PathBuf)> = config
.enabled_providers()
.into_iter()
.map(|(name, cfg)| (name.clone(), cfg.log_root.clone()))
.collect();
Self {
config: Arc::new(config),
provider_configs,
}
}
pub fn builder() -> ProvidersBuilder {
ProvidersBuilder::new()
}
pub fn parse_auto(&self, path: &Path) -> Result<Vec<AgentEvent>> {
let path_str = path
.to_str()
.ok_or_else(|| Error::InvalidInput("Path contains invalid UTF-8".to_string()))?;
let adapter = agtrace_providers::detect_adapter_from_path(path_str)
.map_err(|_| Error::NotFound("No suitable provider detected for file".to_string()))?;
adapter
.parser
.parse_file(path)
.map_err(|e| Error::InvalidInput(format!("Parse error: {}", e)))
}
pub fn parse_file(&self, path: &Path, provider_name: &str) -> Result<Vec<AgentEvent>> {
let adapter = agtrace_providers::create_adapter(provider_name)
.map_err(|_| Error::NotFound(format!("Unknown provider: {}", provider_name)))?;
adapter
.parser
.parse_file(path)
.map_err(|e| Error::InvalidInput(format!("Parse error: {}", e)))
}
pub fn diagnose(&self) -> Result<Vec<DiagnoseResult>> {
let providers: Vec<_> = self
.provider_configs
.iter()
.filter_map(|(name, path)| {
agtrace_providers::create_adapter(name)
.ok()
.map(|adapter| (adapter, path.clone()))
})
.collect();
agtrace_runtime::DoctorService::diagnose_all(&providers).map_err(Error::Runtime)
}
pub fn check_file(&self, path: &Path, provider: Option<&str>) -> Result<CheckResult> {
let path_str = path
.to_str()
.ok_or_else(|| Error::InvalidInput("Path contains invalid UTF-8".to_string()))?;
let (adapter, provider_name) = if let Some(name) = provider {
let adapter = agtrace_providers::create_adapter(name)
.map_err(|_| Error::NotFound(format!("Provider: {}", name)))?;
(adapter, name.to_string())
} else {
let adapter = agtrace_providers::detect_adapter_from_path(path_str)
.map_err(|_| Error::NotFound("No suitable provider detected".to_string()))?;
let name = format!("{} (auto-detected)", adapter.id());
(adapter, name)
};
agtrace_runtime::DoctorService::check_file(path_str, &adapter, &provider_name)
.map_err(Error::Runtime)
}
pub fn inspect_file(path: &Path, lines: usize, json_format: bool) -> Result<InspectResult> {
let path_str = path
.to_str()
.ok_or_else(|| Error::InvalidInput("Path contains invalid UTF-8".to_string()))?;
agtrace_runtime::DoctorService::inspect_file(path_str, lines, json_format)
.map_err(Error::Runtime)
}
pub fn list(&self) -> Vec<(&String, &ProviderConfig)> {
self.config.enabled_providers()
}
pub fn config(&self) -> &Config {
&self.config
}
pub fn provider_configs(&self) -> &[(String, PathBuf)] {
&self.provider_configs
}
}
#[derive(Default)]
pub struct ProvidersBuilder {
config: Option<Config>,
providers: Vec<(String, PathBuf)>,
}
impl ProvidersBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn config_file(mut self, path: impl AsRef<Path>) -> Result<Self> {
let config = Config::load_from(&path.as_ref().to_path_buf()).map_err(Error::Runtime)?;
self.config = Some(config);
Ok(self)
}
pub fn config(mut self, config: Config) -> Self {
self.config = Some(config);
self
}
pub fn provider(mut self, name: &str, log_root: impl Into<PathBuf>) -> Self {
self.providers.push((name.to_string(), log_root.into()));
self
}
pub fn auto_detect(mut self) -> Self {
match Config::detect_providers() {
Ok(config) => {
self.config = Some(config);
}
Err(_) => {
}
}
self
}
pub fn build(self) -> Result<Providers> {
let mut config = self.config.unwrap_or_default();
for (name, log_root) in self.providers {
config.set_provider(
name,
ProviderConfig {
enabled: true,
log_root,
context_window_override: None,
},
);
}
Ok(Providers::with_config(config))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_creates_empty_providers() {
let providers = Providers::builder().build().unwrap();
assert!(providers.list().is_empty());
}
#[test]
fn test_builder_with_manual_provider() {
let providers = Providers::builder()
.provider("claude_code", "/tmp/test")
.build()
.unwrap();
assert_eq!(providers.list().len(), 1);
let (name, config) = &providers.list()[0];
assert_eq!(*name, "claude_code");
assert_eq!(config.log_root, PathBuf::from("/tmp/test"));
}
#[test]
fn test_with_config() {
let mut config = Config::default();
config.set_provider(
"test_provider".to_string(),
ProviderConfig {
enabled: true,
log_root: PathBuf::from("/test/path"),
context_window_override: None,
},
);
let providers = Providers::with_config(config);
assert_eq!(providers.list().len(), 1);
}
}