rsclaw 0.0.1-alpha.1

rsclaw: High-performance AI agent (BETA). Optimized for M4 Max and 2GB VPS. 100% compatible with openclaw
Documentation
use super::runtime::RuntimeConfig;
use anyhow::Result;

/// Configuration validator with cross-field constraints.
pub struct ConfigValidator;

impl ConfigValidator {
    /// Validate configuration and return list of issues.
    pub fn validate(config: &RuntimeConfig) -> Result<Vec<String>> {
        let mut issues = Vec::new();

        // Validate meta
        if config.meta.name.is_empty() {
            issues.push("meta.name cannot be empty".to_string());
        }

        // Validate gateway
        if config.gateway.port == 0 {
            issues.push("gateway.port must be greater than 0".to_string());
        }

        // Validate agents
        let default_count = config.agents.list.iter().filter(|a| a.default).count();
        if default_count == 0 {
            issues.push("No default agent defined. Set one agent as default.".to_string());
        } else if default_count > 1 {
            issues.push(format!(
                "Multiple default agents found ({}). Only one agent can be default.",
                default_count
            ));
        }

        for agent in &config.agents.list {
            if agent.name.is_empty() {
                issues.push("Agent name cannot be empty".to_string());
            }

            if let Some(memory_limit) = agent.memory_limit_mb {
                if memory_limit > 2048 {
                    issues.push(format!(
                        "Agent '{}' memory_limit_mb ({}) exceeds system limit (2048MB)",
                        agent.name, memory_limit
                    ));
                }
            }
        }

        // Validate models
        for provider in &config.models.providers {
            if provider.name.is_empty() {
                issues.push("Provider name cannot be empty".to_string());
            }

            if provider.provider_type.is_empty() {
                issues.push(format!(
                    "Provider '{}' has empty provider_type",
                    provider.name
                ));
            }

            let valid_types = [
                "openai",
                "openai-completions",
                "openai-chat",
                "anthropic",
                "gemini",
                "ollama",
            ];
            if !valid_types.contains(&provider.provider_type.as_ref()) {
                issues.push(format!(
                    "Provider '{}' has invalid provider_type '{}'. Valid types: {}",
                    provider.name,
                    provider.provider_type,
                    valid_types.join(", ")
                ));
            }

            for model in &provider.models {
                if model.name.is_empty() {
                    issues.push(format!(
                        "Model in provider '{}' has empty name",
                        provider.name
                    ));
                }
            }
        }

        // Validate memory config
        if config.memory.max_agent_memory_mb > 2048 {
            issues.push(format!(
                "memory.max_agent_memory_mb ({}) exceeds system limit (2048MB)",
                config.memory.max_agent_memory_mb
            ));
        }

        if config.memory.token_threshold == 0 {
            issues.push("memory.token_threshold must be greater than 0".to_string());
        }

        // Validate max_concurrent_agents (1-5)
        if config.memory.max_concurrent_agents < 1 || config.memory.max_concurrent_agents > 5 {
            issues.push(format!(
                "memory.max_concurrent_agents ({}) must be between 1 and 5",
                config.memory.max_concurrent_agents
            ));
        }

        // Validate session
        if config.session.timeout_minutes == 0 {
            issues.push("session.timeout_minutes must be greater than 0".to_string());
        }

        // Validate bindings reference valid agents
        let agent_names: Vec<&str> = config.agents.list.iter().map(|a| a.name.as_ref()).collect();
        for binding in &config.bindings {
            if !agent_names.contains(&binding.agent.as_ref()) {
                issues.push(format!(
                    "Binding references non-existent agent '{}'",
                    binding.agent
                ));
            }
        }

        Ok(issues)
    }

    /// Attempt to fix configuration issues.
    pub fn fix(config: &mut RuntimeConfig, issues: &[String]) -> Result<()> {
        for issue in issues {
            tracing::info!("Fixing: {}", issue);

            // Fix max_concurrent_agents out of range
            if issue.contains("max_concurrent_agents") {
                if config.memory.max_concurrent_agents < 1 {
                    config.memory.max_concurrent_agents = 1;
                } else if config.memory.max_concurrent_agents > 5 {
                    config.memory.max_concurrent_agents = 5;
                }
            }

            // Fix memory limit
            if issue.contains("memory_limit_mb") {
                for agent in &mut config.agents.list {
                    if let Some(limit) = agent.memory_limit_mb {
                        if limit > 2048 {
                            agent.memory_limit_mb = Some(2048);
                        }
                    }
                }
            }
        }

        Ok(())
    }
}