use crate::markdown_config::error::MarkdownConfigResult;
use crate::markdown_config::loader::{ConfigFile, ConfigFileType, ConfigurationLoader};
use crate::markdown_config::types::AgentConfig;
use std::path::PathBuf;
use std::sync::Arc;
use tracing::{debug, info, warn};
pub type RegistrationResult = (usize, usize, Vec<(String, String)>);
pub trait AgentRegistrar: Send + Sync {
fn register_agent(&mut self, agent: AgentConfig) -> Result<(), String>;
}
pub struct AgentConfigIntegration {
loader: Arc<ConfigurationLoader>,
}
impl AgentConfigIntegration {
pub fn new(loader: Arc<ConfigurationLoader>) -> Self {
Self { loader }
}
pub fn discover_agent_configs(&self, paths: &[PathBuf]) -> MarkdownConfigResult<Vec<ConfigFile>> {
let all_files = self.loader.discover(paths)?;
let agent_files: Vec<ConfigFile> = all_files
.into_iter()
.filter(|f| f.config_type == ConfigFileType::Agent)
.collect();
debug!("Discovered {} agent configuration files", agent_files.len());
Ok(agent_files)
}
pub async fn load_agent_configs(
&self,
paths: &[PathBuf],
) -> MarkdownConfigResult<(Vec<AgentConfig>, Vec<(PathBuf, String)>)> {
let files = self.discover_agent_configs(paths)?;
let mut agents = Vec::new();
let mut errors = Vec::new();
for file in files {
match self.loader.load(&file).await {
Ok(config) => {
match config {
crate::markdown_config::loader::LoadedConfig::Agent(agent) => {
debug!("Loaded agent configuration: {}", agent.name);
agents.push(agent);
}
_ => {
warn!("Expected agent configuration but got different type from {}", file.path.display());
errors.push((
file.path,
"Expected agent configuration but got different type".to_string(),
));
}
}
}
Err(e) => {
let error_msg = e.to_string();
warn!("Failed to load agent configuration from {}: {}", file.path.display(), error_msg);
errors.push((file.path, error_msg));
}
}
}
info!("Loaded {} agent configurations", agents.len());
Ok((agents, errors))
}
pub fn register_agents(
&self,
agents: Vec<AgentConfig>,
registrar: &mut dyn AgentRegistrar,
) -> MarkdownConfigResult<RegistrationResult> {
let mut success_count = 0;
let mut error_count = 0;
let mut errors = Vec::new();
for agent in agents {
if let Err(e) = agent.validate() {
error_count += 1;
let error_msg = format!("Invalid agent configuration: {}", e);
warn!("Failed to register agent '{}': {}", agent.name, error_msg);
errors.push((agent.name.clone(), error_msg));
continue;
}
debug!("Registering agent: {}", agent.name);
match registrar.register_agent(agent.clone()) {
Ok(_) => {
success_count += 1;
info!("Registered agent: {}", agent.name);
}
Err(e) => {
error_count += 1;
warn!("Failed to register agent '{}': {}", agent.name, e);
errors.push((agent.name.clone(), e));
}
}
}
debug!(
"Agent registration complete: {} successful, {} failed",
success_count, error_count
);
Ok((success_count, error_count, errors))
}
pub async fn load_and_register_agents(
&self,
paths: &[PathBuf],
registrar: &mut dyn AgentRegistrar,
) -> MarkdownConfigResult<(usize, usize, Vec<(String, String)>)> {
let (agents, load_errors) = self.load_agent_configs(paths).await?;
let (success, errors, mut reg_errors) = self.register_agents(agents, registrar)?;
for (path, msg) in load_errors {
reg_errors.push((path.display().to_string(), msg));
}
Ok((success, errors, reg_errors))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::markdown_config::registry::ConfigRegistry;
use std::fs;
use tempfile::TempDir;
fn create_test_agent_file(dir: &PathBuf, name: &str, content: &str) -> PathBuf {
let path = dir.join(format!("{}.agent.md", name));
fs::write(&path, content).unwrap();
path
}
#[test]
fn test_discover_agent_configs() {
let temp_dir = TempDir::new().unwrap();
let dir_path = temp_dir.path().to_path_buf();
create_test_agent_file(&dir_path, "agent1", "---\nname: agent1\n---\nTest");
create_test_agent_file(&dir_path, "agent2", "---\nname: agent2\n---\nTest");
fs::write(dir_path.join("mode1.mode.md"), "---\nname: mode1\n---\nTest").unwrap();
let registry = Arc::new(ConfigRegistry::new());
let loader = Arc::new(ConfigurationLoader::new(registry));
let integration = AgentConfigIntegration::new(loader);
let discovered = integration.discover_agent_configs(&[dir_path]).unwrap();
assert_eq!(discovered.len(), 2);
assert!(discovered.iter().all(|f| f.config_type == ConfigFileType::Agent));
}
#[tokio::test]
async fn test_load_agent_configs() {
let temp_dir = TempDir::new().unwrap();
let dir_path = temp_dir.path().to_path_buf();
let agent_content = r#"---
name: test-agent
description: A test agent
model: gpt-4
temperature: 0.7
max_tokens: 2000
---
You are a helpful assistant"#;
create_test_agent_file(&dir_path, "test-agent", agent_content);
let registry = Arc::new(ConfigRegistry::new());
let loader = Arc::new(ConfigurationLoader::new(registry));
let integration = AgentConfigIntegration::new(loader);
let (agents, errors) = integration.load_agent_configs(&[dir_path]).await.unwrap();
assert_eq!(agents.len(), 1);
assert_eq!(errors.len(), 0);
assert_eq!(agents[0].name, "test-agent");
assert_eq!(agents[0].model, Some("gpt-4".to_string()));
}
#[tokio::test]
async fn test_load_agent_configs_with_errors() {
let temp_dir = TempDir::new().unwrap();
let dir_path = temp_dir.path().to_path_buf();
let valid_content = r#"---
name: valid-agent
---
Valid agent"#;
create_test_agent_file(&dir_path, "valid-agent", valid_content);
fs::write(dir_path.join("invalid.agent.md"), "# No frontmatter\nJust markdown").unwrap();
let registry = Arc::new(ConfigRegistry::new());
let loader = Arc::new(ConfigurationLoader::new(registry));
let integration = AgentConfigIntegration::new(loader);
let (agents, errors) = integration.load_agent_configs(&[dir_path]).await.unwrap();
assert_eq!(agents.len(), 1);
assert_eq!(errors.len(), 1);
assert_eq!(agents[0].name, "valid-agent");
}
#[test]
fn test_register_agents() {
let registry = Arc::new(ConfigRegistry::new());
let loader = Arc::new(ConfigurationLoader::new(registry));
let integration = AgentConfigIntegration::new(loader);
let agents = vec![
AgentConfig {
name: "agent1".to_string(),
description: Some("Test agent 1".to_string()),
prompt: "You are agent 1".to_string(),
model: Some("gpt-4".to_string()),
temperature: Some(0.7),
max_tokens: Some(2000),
tools: vec![],
},
AgentConfig {
name: "agent2".to_string(),
description: Some("Test agent 2".to_string()),
prompt: "You are agent 2".to_string(),
model: None,
temperature: None,
max_tokens: None,
tools: vec![],
},
];
struct MockRegistrar;
impl AgentRegistrar for MockRegistrar {
fn register_agent(&mut self, _agent: AgentConfig) -> Result<(), String> {
Ok(())
}
}
let mut registrar = MockRegistrar;
let (success, errors, error_list) = integration
.register_agents(agents, &mut registrar)
.unwrap();
assert_eq!(success, 2);
assert_eq!(errors, 0);
assert_eq!(error_list.len(), 0);
}
#[test]
fn test_register_invalid_agent() {
let registry = Arc::new(ConfigRegistry::new());
let loader = Arc::new(ConfigurationLoader::new(registry));
let integration = AgentConfigIntegration::new(loader);
let agents = vec![
AgentConfig {
name: String::new(), description: None,
prompt: "Test".to_string(),
model: None,
temperature: None,
max_tokens: None,
tools: vec![],
},
];
struct MockRegistrar;
impl AgentRegistrar for MockRegistrar {
fn register_agent(&mut self, _agent: AgentConfig) -> Result<(), String> {
Ok(())
}
}
let mut registrar = MockRegistrar;
let (success, errors, error_list) = integration
.register_agents(agents, &mut registrar)
.unwrap();
assert_eq!(success, 0);
assert_eq!(errors, 1);
assert_eq!(error_list.len(), 1);
}
}