ceylon_runtime/config/
mesh_config.rs

1//! Mesh configuration from TOML files.
2
3use super::AgentConfig;
4use crate::core::error::{Error, Result};
5use serde::{Deserialize, Serialize};
6use std::path::Path;
7
8/// Configuration for a multi-agent mesh.
9///
10/// This struct allows defining multiple agents in a single TOML file.
11///
12/// # Example
13///
14/// ```toml
15/// name = "research_mesh"
16///
17/// [[agents]]
18/// name = "researcher"
19/// model = "ollama::gemma3:latest"
20/// system_prompt = "You are a research assistant."
21/// temperature = 0.7
22///
23/// [[agents]]
24/// name = "summarizer"
25/// model = "ollama::gemma3:latest"
26/// system_prompt = "You are a summarization expert."
27/// temperature = 0.5
28/// ```
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct MeshConfig {
31    /// Name of the mesh
32    pub name: String,
33
34    /// List of agent configurations
35    #[serde(default)]
36    pub agents: Vec<AgentConfig>,
37}
38
39impl Default for MeshConfig {
40    fn default() -> Self {
41        Self {
42            name: "mesh".to_string(),
43            agents: Vec::new(),
44        }
45    }
46}
47
48impl MeshConfig {
49    /// Create a new MeshConfig with a name.
50    pub fn new(name: impl Into<String>) -> Self {
51        Self {
52            name: name.into(),
53            agents: Vec::new(),
54        }
55    }
56
57    /// Parse a MeshConfig from a TOML string.
58    ///
59    /// # Example
60    ///
61    /// ```rust
62    /// use ceylon_runtime::config::MeshConfig;
63    ///
64    /// let toml = r#"
65    /// name = "my_mesh"
66    ///
67    /// [[agents]]
68    /// name = "agent1"
69    /// model = "ollama::llama2"
70    /// "#;
71    ///
72    /// let config = MeshConfig::from_toml(toml).unwrap();
73    /// assert_eq!(config.name, "my_mesh");
74    /// assert_eq!(config.agents.len(), 1);
75    /// ```
76    pub fn from_toml(toml_str: &str) -> Result<Self> {
77        toml::from_str(toml_str)
78            .map_err(|e| Error::ConfigError(format!("Failed to parse TOML: {}", e)))
79    }
80
81    /// Load a MeshConfig from a TOML file.
82    ///
83    /// # Example
84    ///
85    /// ```rust,no_run
86    /// use ceylon_runtime::config::MeshConfig;
87    ///
88    /// let config = MeshConfig::from_file("agents.toml").unwrap();
89    /// ```
90    pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
91        let content = std::fs::read_to_string(path.as_ref()).map_err(|e| {
92            Error::ConfigError(format!(
93                "Failed to read file '{}': {}",
94                path.as_ref().display(),
95                e
96            ))
97        })?;
98        Self::from_toml(&content)
99    }
100
101    /// Add an agent configuration.
102    pub fn add_agent(mut self, agent: AgentConfig) -> Self {
103        self.agents.push(agent);
104        self
105    }
106
107    /// Get agent config by name.
108    pub fn get_agent(&self, name: &str) -> Option<&AgentConfig> {
109        self.agents.iter().find(|a| a.name == name)
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn test_parse_mesh_config() {
119        let toml = r#"
120            name = "test_mesh"
121
122            [[agents]]
123            name = "agent1"
124            model = "ollama::llama2"
125            system_prompt = "First agent"
126
127            [[agents]]
128            name = "agent2"
129            model = "openai::gpt-4"
130            temperature = 0.8
131        "#;
132
133        let config = MeshConfig::from_toml(toml).unwrap();
134        assert_eq!(config.name, "test_mesh");
135        assert_eq!(config.agents.len(), 2);
136        assert_eq!(config.agents[0].name, "agent1");
137        assert_eq!(config.agents[1].name, "agent2");
138        assert_eq!(config.agents[1].temperature, Some(0.8));
139    }
140
141    #[test]
142    fn test_get_agent() {
143        let config = MeshConfig::new("test")
144            .add_agent(AgentConfig::new("agent1", "ollama::llama2"))
145            .add_agent(AgentConfig::new("agent2", "ollama::llama2"));
146
147        assert!(config.get_agent("agent1").is_some());
148        assert!(config.get_agent("agent2").is_some());
149        assert!(config.get_agent("nonexistent").is_none());
150    }
151
152    #[test]
153    fn test_empty_agents() {
154        let toml = r#"
155            name = "empty_mesh"
156        "#;
157
158        let config = MeshConfig::from_toml(toml).unwrap();
159        assert_eq!(config.name, "empty_mesh");
160        assert!(config.agents.is_empty());
161    }
162}