use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::tools::loader::ToolConfig;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentConfig {
pub agent: AgentMetadata,
#[serde(default)]
pub tools: Vec<ToolConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentMetadata {
pub name: String,
#[serde(default)]
pub model: Option<String>,
pub system_prompt: PathBuf,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub version: Option<String>,
}
impl AgentConfig {
pub fn from_file(path: impl AsRef<std::path::Path>) -> anyhow::Result<Self> {
let path = path.as_ref();
let content = std::fs::read_to_string(path)?;
let config: AgentConfig = toml::from_str(&content)?;
Ok(config)
}
pub fn validate(&self, base_dir: impl AsRef<std::path::Path>) -> anyhow::Result<()> {
let base = base_dir.as_ref();
let prompt_path = if self.agent.system_prompt.is_absolute() {
self.agent.system_prompt.clone()
} else {
base.join(&self.agent.system_prompt)
};
if !prompt_path.exists() {
anyhow::bail!("System prompt file not found: {}", prompt_path.display());
}
let mut seen_names = std::collections::HashSet::new();
for tool in &self.tools {
if !seen_names.insert(&tool.name) {
anyhow::bail!("Duplicate tool name: {}", tool.name);
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_parse_agent_config() {
let toml = r#"
[agent]
name = "test_agent"
model = "openai/gpt-5"
system_prompt = "prompts/test.txt"
description = "A test agent"
version = "1.0.0"
[[tools]]
name = "echo"
schema = "tools/echo.json"
implementation = { type = "python", script = "tools/echo.py" }
"#;
let config: AgentConfig = toml::from_str(toml).unwrap();
assert_eq!(config.agent.name, "test_agent");
assert_eq!(config.agent.model.as_deref(), Some("openai/gpt-5"));
assert_eq!(config.tools.len(), 1);
assert_eq!(config.tools[0].name, "echo");
}
#[test]
fn test_validate_missing_prompt() {
let dir = TempDir::new().unwrap();
let toml = r#"
[agent]
name = "test"
system_prompt = "nonexistent.txt"
"#;
let config: AgentConfig = toml::from_str(toml).unwrap();
let result = config.validate(dir.path());
assert!(result.is_err());
}
#[test]
fn test_validate_duplicate_tools() {
let toml = r#"
[agent]
name = "test"
system_prompt = "prompt.txt"
[[tools]]
name = "duplicate"
schema = "tools/a.json"
implementation = { type = "python", script = "a.py" }
[[tools]]
name = "duplicate"
schema = "tools/b.json"
implementation = { type = "python", script = "b.py" }
"#;
let config: AgentConfig = toml::from_str(toml).unwrap();
let dir = TempDir::new().unwrap();
let prompt_path = dir.path().join("prompt.txt");
std::fs::write(&prompt_path, "test").unwrap();
let result = config.validate(dir.path());
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Duplicate tool name"));
}
}