ceylon-runtime 0.1.3

A Rust-based agent mesh framework for building local and distributed AI agent systems
Documentation
//! Mesh configuration from TOML files.

use super::{AgentConfig, MemoryConfig};
use crate::core::error::{Error, Result};
use serde::{Deserialize, Serialize};
use std::path::Path;

/// Configuration for a multi-agent mesh.
///
/// This struct allows defining multiple agents in a single TOML file.
///
/// # Example
///
/// ```toml
/// name = "research_mesh"
///
/// [memory]
/// backend = "sqlite"
/// path = "./data/knowledge.db"
///
/// [[agents]]
/// name = "researcher"
/// model = "ollama::gemma3:latest"
/// system_prompt = "You are a research assistant."
/// temperature = 0.7
///
/// [[agents]]
/// name = "summarizer"
/// model = "ollama::gemma3:latest"
/// system_prompt = "You are a summarization expert."
/// temperature = 0.5
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MeshConfig {
    /// Name of the mesh
    pub name: String,

    /// Shared memory configuration for all agents (can be overridden per-agent)
    #[serde(default)]
    pub memory: Option<MemoryConfig>,

    /// List of agent configurations
    #[serde(default)]
    pub agents: Vec<AgentConfig>,
}

impl Default for MeshConfig {
    fn default() -> Self {
        Self {
            name: "mesh".to_string(),
            memory: None,
            agents: Vec::new(),
        }
    }
}

impl MeshConfig {
    /// Create a new MeshConfig with a name.
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            memory: None,
            agents: Vec::new(),
        }
    }

    /// Parse a MeshConfig from a TOML string.
    ///
    /// # Example
    ///
    /// ```rust
    /// use ceylon_runtime::config::MeshConfig;
    ///
    /// let toml = r#"
    /// name = "my_mesh"
    ///
    /// [[agents]]
    /// name = "agent1"
    /// model = "ollama::llama2"
    /// "#;
    ///
    /// let config = MeshConfig::from_toml(toml).unwrap();
    /// assert_eq!(config.name, "my_mesh");
    /// assert_eq!(config.agents.len(), 1);
    /// ```
    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)))
    }

    /// Load a MeshConfig from a TOML file.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use ceylon_runtime::config::MeshConfig;
    ///
    /// let config = MeshConfig::from_file("agents.toml").unwrap();
    /// ```
    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)
    }

    /// Add an agent configuration.
    pub fn add_agent(mut self, agent: AgentConfig) -> Self {
        self.agents.push(agent);
        self
    }

    /// Get agent config by name.
    pub fn get_agent(&self, name: &str) -> Option<&AgentConfig> {
        self.agents.iter().find(|a| a.name == name)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_mesh_config() {
        let toml = r#"
            name = "test_mesh"

            [[agents]]
            name = "agent1"
            model = "ollama::llama2"
            system_prompt = "First agent"

            [[agents]]
            name = "agent2"
            model = "openai::gpt-4"
            temperature = 0.8
        "#;

        let config = MeshConfig::from_toml(toml).unwrap();
        assert_eq!(config.name, "test_mesh");
        assert_eq!(config.agents.len(), 2);
        assert_eq!(config.agents[0].name, "agent1");
        assert_eq!(config.agents[1].name, "agent2");
        assert_eq!(config.agents[1].temperature, Some(0.8));
    }

    #[test]
    fn test_get_agent() {
        let config = MeshConfig::new("test")
            .add_agent(AgentConfig::new("agent1", "ollama::llama2"))
            .add_agent(AgentConfig::new("agent2", "ollama::llama2"));

        assert!(config.get_agent("agent1").is_some());
        assert!(config.get_agent("agent2").is_some());
        assert!(config.get_agent("nonexistent").is_none());
    }

    #[test]
    fn test_empty_agents() {
        let toml = r#"
            name = "empty_mesh"
        "#;

        let config = MeshConfig::from_toml(toml).unwrap();
        assert_eq!(config.name, "empty_mesh");
        assert!(config.agents.is_empty());
    }
}