use super::{McpToolConfig, MemoryConfig};
use crate::core::error::{Error, Result};
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentConfig {
pub name: String,
pub model: String,
#[serde(default = "default_system_prompt")]
pub system_prompt: String,
#[serde(default)]
pub api_key: Option<String>,
#[serde(default)]
pub temperature: Option<f32>,
#[serde(default)]
pub max_tokens: Option<u32>,
#[serde(default)]
pub base_url: Option<String>,
#[serde(default)]
pub top_p: Option<f32>,
#[serde(default)]
pub top_k: Option<u32>,
#[serde(default)]
pub timeout_seconds: Option<u64>,
#[serde(default)]
pub reasoning: Option<bool>,
#[serde(default)]
pub reasoning_effort: Option<String>,
#[serde(default)]
pub memory: Option<MemoryConfig>,
#[serde(default)]
pub mcp_tools: Vec<McpToolConfig>,
}
fn default_system_prompt() -> String {
"You are a helpful AI assistant.".to_string()
}
impl Default for AgentConfig {
fn default() -> Self {
Self {
name: String::new(),
model: String::new(),
system_prompt: default_system_prompt(),
api_key: None,
temperature: None,
max_tokens: None,
base_url: None,
top_p: None,
top_k: None,
timeout_seconds: None,
reasoning: None,
reasoning_effort: None,
memory: None,
mcp_tools: Vec::new(),
}
}
}
impl AgentConfig {
pub fn new(name: impl Into<String>, model: impl Into<String>) -> Self {
Self {
name: name.into(),
model: model.into(),
..Default::default()
}
}
pub fn from_toml(toml_str: &str) -> Result<Self> {
toml::from_str(toml_str)
.map_err(|e| Error::ConfigError(format!("Failed to parse TOML: {}", e)))
}
pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
let content = std::fs::read_to_string(path.as_ref()).map_err(|e| {
Error::ConfigError(format!(
"Failed to read file '{}': {}",
path.as_ref().display(),
e
))
})?;
Self::from_toml(&content)
}
pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
self.system_prompt = prompt.into();
self
}
pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
self.api_key = Some(api_key.into());
self
}
pub fn with_temperature(mut self, temperature: f32) -> Self {
self.temperature = Some(temperature);
self
}
pub fn with_max_tokens(mut self, max_tokens: u32) -> Self {
self.max_tokens = Some(max_tokens);
self
}
pub fn with_memory(mut self, memory: MemoryConfig) -> Self {
self.memory = Some(memory);
self
}
pub fn get_effective_memory_config<'a>(
&'a self,
fallback: Option<&'a MemoryConfig>,
) -> Option<&'a MemoryConfig> {
self.memory.as_ref().or(fallback)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_basic_config() {
let toml = r#"
name = "test_agent"
model = "ollama::llama2"
"#;
let config = AgentConfig::from_toml(toml).unwrap();
assert_eq!(config.name, "test_agent");
assert_eq!(config.model, "ollama::llama2");
assert_eq!(config.system_prompt, "You are a helpful AI assistant.");
}
#[test]
fn test_parse_full_config() {
let toml = r#"
name = "researcher"
model = "openai::gpt-4"
system_prompt = "You are a research assistant."
api_key = "sk-test"
temperature = 0.7
max_tokens = 2048
"#;
let config = AgentConfig::from_toml(toml).unwrap();
assert_eq!(config.name, "researcher");
assert_eq!(config.model, "openai::gpt-4");
assert_eq!(config.system_prompt, "You are a research assistant.");
assert_eq!(config.api_key, Some("sk-test".to_string()));
assert_eq!(config.temperature, Some(0.7));
assert_eq!(config.max_tokens, Some(2048));
}
#[test]
fn test_invalid_toml() {
let toml = "this is not valid toml [[[";
let result = AgentConfig::from_toml(toml);
assert!(result.is_err());
}
#[test]
fn test_builder_methods() {
let config = AgentConfig::new("agent", "ollama::llama2")
.with_system_prompt("Custom prompt")
.with_temperature(0.5)
.with_max_tokens(1024);
assert_eq!(config.name, "agent");
assert_eq!(config.system_prompt, "Custom prompt");
assert_eq!(config.temperature, Some(0.5));
assert_eq!(config.max_tokens, Some(1024));
}
#[test]
fn test_with_memory() {
let memory = MemoryConfig::in_memory().with_max_entries(500);
let config = AgentConfig::new("agent", "ollama::llama2").with_memory(memory);
assert!(config.memory.is_some());
assert_eq!(config.memory.as_ref().unwrap().max_entries, Some(500));
}
#[test]
fn test_get_effective_memory_config_with_agent_memory() {
let mesh_memory = MemoryConfig::in_memory().with_max_entries(1000);
let agent_memory = MemoryConfig::in_memory().with_max_entries(500);
let config = AgentConfig::new("agent", "ollama::llama2").with_memory(agent_memory);
let effective = config.get_effective_memory_config(Some(&mesh_memory));
assert!(effective.is_some());
assert_eq!(effective.unwrap().max_entries, Some(500));
}
#[test]
fn test_get_effective_memory_config_fallback_to_mesh() {
let mesh_memory = MemoryConfig::in_memory().with_max_entries(1000);
let config = AgentConfig::new("agent", "ollama::llama2");
let effective = config.get_effective_memory_config(Some(&mesh_memory));
assert!(effective.is_some());
assert_eq!(effective.unwrap().max_entries, Some(1000));
}
#[test]
fn test_get_effective_memory_config_no_memory() {
let config = AgentConfig::new("agent", "ollama::llama2");
let effective = config.get_effective_memory_config(None);
assert!(effective.is_none());
}
}