swarm-engine-core 0.1.6

Core types and orchestration for SwarmEngine
Documentation
//! Environment Specifications
//!
//! 各 Environment がサポートするアクションの仕様を定義。

use std::collections::HashMap;

/// パラメータ仕様
#[derive(Debug, Clone)]
pub struct ParamSpec {
    /// パラメータ名
    pub name: String,
    /// 必須かどうか
    pub required: bool,
    /// 説明
    pub description: String,
}

impl ParamSpec {
    /// 必須パラメータを作成
    pub fn required(name: impl Into<String>, description: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            required: true,
            description: description.into(),
        }
    }

    /// オプショナルパラメータを作成
    pub fn optional(name: impl Into<String>, description: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            required: false,
            description: description.into(),
        }
    }
}

/// アクション仕様
#[derive(Debug, Clone)]
pub struct ActionSpec {
    /// 正規名(TOML で定義する名前)
    pub canonical_name: String,
    /// エイリアス(Environment が受け付ける他の名前)
    pub aliases: Vec<String>,
    /// パラメータ仕様
    pub params: Vec<ParamSpec>,
    /// 説明
    pub description: String,
}

impl ActionSpec {
    /// 新しいアクション仕様を作成
    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
        Self {
            canonical_name: name.into(),
            aliases: Vec::new(),
            params: Vec::new(),
            description: description.into(),
        }
    }

    /// エイリアスを追加
    pub fn aliases<I, S>(mut self, aliases: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.aliases = aliases.into_iter().map(|s| s.into()).collect();
        self
    }

    /// 必須パラメータを追加
    pub fn required_param(mut self, name: impl Into<String>, desc: impl Into<String>) -> Self {
        self.params.push(ParamSpec::required(name, desc));
        self
    }

    /// オプショナルパラメータを追加
    pub fn optional_param(mut self, name: impl Into<String>, desc: impl Into<String>) -> Self {
        self.params.push(ParamSpec::optional(name, desc));
        self
    }

    /// 指定された名前がこのアクションにマッチするか
    pub fn matches(&self, name: &str) -> bool {
        let lower = name.to_lowercase();
        self.canonical_name.to_lowercase() == lower
            || self.aliases.iter().any(|a| a.to_lowercase() == lower)
    }
}

/// Environment 仕様
#[derive(Debug, Clone)]
pub struct EnvironmentSpec {
    /// Environment タイプ名
    pub env_type: String,
    /// サポートするアクション
    pub actions: Vec<ActionSpec>,
    /// 説明
    pub description: String,
}

impl EnvironmentSpec {
    /// 新しい Environment 仕様を作成
    pub fn new(env_type: impl Into<String>, description: impl Into<String>) -> Self {
        Self {
            env_type: env_type.into(),
            actions: Vec::new(),
            description: description.into(),
        }
    }

    /// アクションを追加
    pub fn action(mut self, spec: ActionSpec) -> Self {
        self.actions.push(spec);
        self
    }

    /// 指定された名前のアクションがサポートされているか
    pub fn supports_action(&self, name: &str) -> bool {
        self.actions.iter().any(|a| a.matches(name))
    }

    /// 指定された名前にマッチするアクション仕様を取得
    pub fn get_action(&self, name: &str) -> Option<&ActionSpec> {
        self.actions.iter().find(|a| a.matches(name))
    }
}

/// 全 Environment 仕様のレジストリ
pub struct EnvironmentSpecRegistry {
    specs: HashMap<String, EnvironmentSpec>,
}

impl Default for EnvironmentSpecRegistry {
    fn default() -> Self {
        Self::new()
    }
}

impl EnvironmentSpecRegistry {
    /// レジストリを作成し、ビルトイン仕様を登録
    pub fn new() -> Self {
        let mut registry = Self {
            specs: HashMap::new(),
        };

        // Troubleshooting Environment
        registry.register(
            EnvironmentSpec::new("troubleshooting", "Service troubleshooting environment")
                .action(
                    ActionSpec::new("CheckStatus", "Check service status")
                        .aliases(["check_status", "status"])
                        .optional_param("service", "Target service name"),
                )
                .action(
                    ActionSpec::new("ReadLogs", "Read service logs")
                        .aliases(["read_logs", "logs"])
                        .required_param("service", "Target service name"),
                )
                .action(
                    ActionSpec::new("AnalyzeMetrics", "Analyze service metrics")
                        .aliases(["analyze_metrics", "metrics"])
                        .required_param("service", "Target service name"),
                )
                .action(
                    ActionSpec::new("Diagnose", "Diagnose service problem")
                        .aliases(["diagnosis"])
                        .required_param("service", "Target service name"),
                )
                .action(
                    ActionSpec::new("Restart", "Restart service")
                        .aliases(["reboot"])
                        .required_param("service", "Target service name"),
                ),
        );

        // Search Environment
        registry.register(
            EnvironmentSpec::new("search", "File search environment")
                .action(
                    ActionSpec::new("SearchFiles", "Search for files")
                        .aliases(["search_files", "search", "list", "listfiles", "list_files"])
                        .optional_param("query", "Search query pattern"),
                )
                .action(
                    ActionSpec::new("ReadFile", "Read file content")
                        .aliases(["read_file", "read", "cat"])
                        .required_param("file", "File path to read"),
                )
                .action(
                    ActionSpec::new("Analyze", "Analyze file content")
                        .aliases(["process", "complete"])
                        .required_param("file", "File path to analyze"),
                ),
        );

        // Code Environment
        registry.register(
            EnvironmentSpec::new("code", "Code exploration environment")
                .action(
                    ActionSpec::new("Grep", "Search for pattern in files")
                        .aliases(["grep"])
                        .required_param("pattern", "Search pattern"),
                )
                .action(
                    ActionSpec::new("Read", "Read file content")
                        .aliases(["read"])
                        .required_param("path", "File path to read"),
                )
                .action(ActionSpec::new("List", "List files in codebase").aliases(["list", "ls"])),
        );

        // Internal Diagnosis Environment
        registry.register(
            EnvironmentSpec::new("internal_diagnosis", "SwarmEngine internal diagnosis")
                .action(
                    ActionSpec::new("ParseConfig", "Parse configuration file")
                        .aliases(["parse_config", "config"])
                        .optional_param("path", "Config file path"),
                )
                .action(
                    ActionSpec::new("AnalyzeLog", "Analyze log entries")
                        .aliases(["analyze_log", "log"])
                        .optional_param("filter", "Log filter pattern"),
                )
                .action(
                    ActionSpec::new("TraceError", "Trace error code")
                        .aliases(["trace_error", "trace"])
                        .required_param("code", "Error code (SW-XXXX)"),
                )
                .action(
                    ActionSpec::new("ApplyFix", "Apply configuration fix")
                        .aliases(["apply_fix", "fix"])
                        .required_param("key", "Configuration key")
                        .required_param("value", "New value"),
                ),
        );

        // Deep Search Environment
        registry.register(
            EnvironmentSpec::new("deep_search", "Deep search with multiple sources")
                .action(
                    ActionSpec::new("Search", "Search for information")
                        .required_param("query", "Search query"),
                )
                .action(
                    ActionSpec::new("ReadDocument", "Read document content")
                        .required_param("doc_id", "Document ID"),
                )
                .action(
                    ActionSpec::new("EvaluateSource", "Evaluate source reliability")
                        .required_param("source", "Source to evaluate"),
                )
                .action(ActionSpec::new("Synthesize", "Synthesize findings"))
                .action(
                    ActionSpec::new("Answer", "Submit final answer")
                        .required_param("answer", "Final answer"),
                ),
        );

        // Maze Environment
        registry.register(
            EnvironmentSpec::new("maze", "Grid navigation environment")
                .action(
                    ActionSpec::new("Move", "Move to adjacent cell")
                        .required_param("direction", "Direction (north/south/east/west)"),
                )
                .action(ActionSpec::new("Look", "Look around current position"))
                .action(ActionSpec::new("Wait", "Wait at current position")),
        );

        // None Environment (no actions)
        registry.register(EnvironmentSpec::new(
            "none",
            "No-op environment for testing",
        ));

        // Default/Realworld Environment
        registry.register(
            EnvironmentSpec::new("default", "Real filesystem environment")
                .action(
                    ActionSpec::new("Bash", "Execute bash command")
                        .required_param("command", "Command to execute"),
                )
                .action(ActionSpec::new("Read", "Read file").required_param("path", "File path"))
                .action(
                    ActionSpec::new("Write", "Write file")
                        .required_param("path", "File path")
                        .required_param("content", "File content"),
                )
                .action(
                    ActionSpec::new("Grep", "Search pattern in files")
                        .required_param("pattern", "Search pattern"),
                )
                .action(
                    ActionSpec::new("Glob", "Find files by pattern")
                        .required_param("pattern", "Glob pattern"),
                ),
        );

        // Alias for realworld
        if let Some(default_spec) = registry.specs.get("default").cloned() {
            let mut realworld_spec = default_spec;
            realworld_spec.env_type = "realworld".to_string();
            registry
                .specs
                .insert("realworld".to_string(), realworld_spec);
        }

        registry
    }

    /// Environment 仕様を登録
    pub fn register(&mut self, spec: EnvironmentSpec) {
        self.specs.insert(spec.env_type.clone(), spec);
    }

    /// Environment 仕様を取得
    pub fn get(&self, env_type: &str) -> Option<&EnvironmentSpec> {
        self.specs.get(env_type)
    }
}

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

    #[test]
    fn test_action_spec_matches() {
        let spec = ActionSpec::new("CheckStatus", "Check service status")
            .aliases(["check_status", "status"]);

        assert!(spec.matches("CheckStatus"));
        assert!(spec.matches("checkstatus")); // case insensitive
        assert!(spec.matches("check_status"));
        assert!(spec.matches("status"));
        assert!(!spec.matches("unknown"));
    }

    #[test]
    fn test_environment_spec_supports_action() {
        let spec = EnvironmentSpec::new("test", "Test environment")
            .action(ActionSpec::new("Action1", "desc").aliases(["a1"]))
            .action(ActionSpec::new("Action2", "desc"));

        assert!(spec.supports_action("Action1"));
        assert!(spec.supports_action("a1"));
        assert!(spec.supports_action("Action2"));
        assert!(!spec.supports_action("Action3"));
    }

    #[test]
    fn test_registry_builtin_specs() {
        let registry = EnvironmentSpecRegistry::new();

        assert!(registry.get("troubleshooting").is_some());
        assert!(registry.get("search").is_some());
        assert!(registry.get("code").is_some());
        assert!(registry.get("internal_diagnosis").is_some());
        assert!(registry.get("deep_search").is_some());
        assert!(registry.get("maze").is_some());
        assert!(registry.get("none").is_some());
        assert!(registry.get("default").is_some());
        assert!(registry.get("realworld").is_some());
    }

    #[test]
    fn test_troubleshooting_actions() {
        let registry = EnvironmentSpecRegistry::new();
        let spec = registry.get("troubleshooting").unwrap();

        assert!(spec.supports_action("CheckStatus"));
        assert!(spec.supports_action("check_status"));
        assert!(spec.supports_action("ReadLogs"));
        assert!(spec.supports_action("AnalyzeMetrics"));
        assert!(spec.supports_action("Diagnose"));
        assert!(spec.supports_action("Restart"));
        assert!(!spec.supports_action("Unknown"));
    }
}