Skip to main content

swarm_engine_core/actions/
env_spec.rs

1//! Environment Specifications
2//!
3//! 各 Environment がサポートするアクションの仕様を定義。
4
5use std::collections::HashMap;
6
7/// パラメータ仕様
8#[derive(Debug, Clone)]
9pub struct ParamSpec {
10    /// パラメータ名
11    pub name: String,
12    /// 必須かどうか
13    pub required: bool,
14    /// 説明
15    pub description: String,
16}
17
18impl ParamSpec {
19    /// 必須パラメータを作成
20    pub fn required(name: impl Into<String>, description: impl Into<String>) -> Self {
21        Self {
22            name: name.into(),
23            required: true,
24            description: description.into(),
25        }
26    }
27
28    /// オプショナルパラメータを作成
29    pub fn optional(name: impl Into<String>, description: impl Into<String>) -> Self {
30        Self {
31            name: name.into(),
32            required: false,
33            description: description.into(),
34        }
35    }
36}
37
38/// アクション仕様
39#[derive(Debug, Clone)]
40pub struct ActionSpec {
41    /// 正規名(TOML で定義する名前)
42    pub canonical_name: String,
43    /// エイリアス(Environment が受け付ける他の名前)
44    pub aliases: Vec<String>,
45    /// パラメータ仕様
46    pub params: Vec<ParamSpec>,
47    /// 説明
48    pub description: String,
49}
50
51impl ActionSpec {
52    /// 新しいアクション仕様を作成
53    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
54        Self {
55            canonical_name: name.into(),
56            aliases: Vec::new(),
57            params: Vec::new(),
58            description: description.into(),
59        }
60    }
61
62    /// エイリアスを追加
63    pub fn aliases<I, S>(mut self, aliases: I) -> Self
64    where
65        I: IntoIterator<Item = S>,
66        S: Into<String>,
67    {
68        self.aliases = aliases.into_iter().map(|s| s.into()).collect();
69        self
70    }
71
72    /// 必須パラメータを追加
73    pub fn required_param(mut self, name: impl Into<String>, desc: impl Into<String>) -> Self {
74        self.params.push(ParamSpec::required(name, desc));
75        self
76    }
77
78    /// オプショナルパラメータを追加
79    pub fn optional_param(mut self, name: impl Into<String>, desc: impl Into<String>) -> Self {
80        self.params.push(ParamSpec::optional(name, desc));
81        self
82    }
83
84    /// 指定された名前がこのアクションにマッチするか
85    pub fn matches(&self, name: &str) -> bool {
86        let lower = name.to_lowercase();
87        self.canonical_name.to_lowercase() == lower
88            || self.aliases.iter().any(|a| a.to_lowercase() == lower)
89    }
90}
91
92/// Environment 仕様
93#[derive(Debug, Clone)]
94pub struct EnvironmentSpec {
95    /// Environment タイプ名
96    pub env_type: String,
97    /// サポートするアクション
98    pub actions: Vec<ActionSpec>,
99    /// 説明
100    pub description: String,
101}
102
103impl EnvironmentSpec {
104    /// 新しい Environment 仕様を作成
105    pub fn new(env_type: impl Into<String>, description: impl Into<String>) -> Self {
106        Self {
107            env_type: env_type.into(),
108            actions: Vec::new(),
109            description: description.into(),
110        }
111    }
112
113    /// アクションを追加
114    pub fn action(mut self, spec: ActionSpec) -> Self {
115        self.actions.push(spec);
116        self
117    }
118
119    /// 指定された名前のアクションがサポートされているか
120    pub fn supports_action(&self, name: &str) -> bool {
121        self.actions.iter().any(|a| a.matches(name))
122    }
123
124    /// 指定された名前にマッチするアクション仕様を取得
125    pub fn get_action(&self, name: &str) -> Option<&ActionSpec> {
126        self.actions.iter().find(|a| a.matches(name))
127    }
128}
129
130/// 全 Environment 仕様のレジストリ
131pub struct EnvironmentSpecRegistry {
132    specs: HashMap<String, EnvironmentSpec>,
133}
134
135impl Default for EnvironmentSpecRegistry {
136    fn default() -> Self {
137        Self::new()
138    }
139}
140
141impl EnvironmentSpecRegistry {
142    /// レジストリを作成し、ビルトイン仕様を登録
143    pub fn new() -> Self {
144        let mut registry = Self {
145            specs: HashMap::new(),
146        };
147
148        // Troubleshooting Environment
149        registry.register(
150            EnvironmentSpec::new("troubleshooting", "Service troubleshooting environment")
151                .action(
152                    ActionSpec::new("CheckStatus", "Check service status")
153                        .aliases(["check_status", "status"])
154                        .optional_param("service", "Target service name"),
155                )
156                .action(
157                    ActionSpec::new("ReadLogs", "Read service logs")
158                        .aliases(["read_logs", "logs"])
159                        .required_param("service", "Target service name"),
160                )
161                .action(
162                    ActionSpec::new("AnalyzeMetrics", "Analyze service metrics")
163                        .aliases(["analyze_metrics", "metrics"])
164                        .required_param("service", "Target service name"),
165                )
166                .action(
167                    ActionSpec::new("Diagnose", "Diagnose service problem")
168                        .aliases(["diagnosis"])
169                        .required_param("service", "Target service name"),
170                )
171                .action(
172                    ActionSpec::new("Restart", "Restart service")
173                        .aliases(["reboot"])
174                        .required_param("service", "Target service name"),
175                ),
176        );
177
178        // Search Environment
179        registry.register(
180            EnvironmentSpec::new("search", "File search environment")
181                .action(
182                    ActionSpec::new("SearchFiles", "Search for files")
183                        .aliases(["search_files", "search", "list", "listfiles", "list_files"])
184                        .optional_param("query", "Search query pattern"),
185                )
186                .action(
187                    ActionSpec::new("ReadFile", "Read file content")
188                        .aliases(["read_file", "read", "cat"])
189                        .required_param("file", "File path to read"),
190                )
191                .action(
192                    ActionSpec::new("Analyze", "Analyze file content")
193                        .aliases(["process", "complete"])
194                        .required_param("file", "File path to analyze"),
195                ),
196        );
197
198        // Code Environment
199        registry.register(
200            EnvironmentSpec::new("code", "Code exploration environment")
201                .action(
202                    ActionSpec::new("Grep", "Search for pattern in files")
203                        .aliases(["grep"])
204                        .required_param("pattern", "Search pattern"),
205                )
206                .action(
207                    ActionSpec::new("Read", "Read file content")
208                        .aliases(["read"])
209                        .required_param("path", "File path to read"),
210                )
211                .action(ActionSpec::new("List", "List files in codebase").aliases(["list", "ls"])),
212        );
213
214        // Internal Diagnosis Environment
215        registry.register(
216            EnvironmentSpec::new("internal_diagnosis", "SwarmEngine internal diagnosis")
217                .action(
218                    ActionSpec::new("ParseConfig", "Parse configuration file")
219                        .aliases(["parse_config", "config"])
220                        .optional_param("path", "Config file path"),
221                )
222                .action(
223                    ActionSpec::new("AnalyzeLog", "Analyze log entries")
224                        .aliases(["analyze_log", "log"])
225                        .optional_param("filter", "Log filter pattern"),
226                )
227                .action(
228                    ActionSpec::new("TraceError", "Trace error code")
229                        .aliases(["trace_error", "trace"])
230                        .required_param("code", "Error code (SW-XXXX)"),
231                )
232                .action(
233                    ActionSpec::new("ApplyFix", "Apply configuration fix")
234                        .aliases(["apply_fix", "fix"])
235                        .required_param("key", "Configuration key")
236                        .required_param("value", "New value"),
237                ),
238        );
239
240        // Deep Search Environment
241        registry.register(
242            EnvironmentSpec::new("deep_search", "Deep search with multiple sources")
243                .action(
244                    ActionSpec::new("Search", "Search for information")
245                        .required_param("query", "Search query"),
246                )
247                .action(
248                    ActionSpec::new("ReadDocument", "Read document content")
249                        .required_param("doc_id", "Document ID"),
250                )
251                .action(
252                    ActionSpec::new("EvaluateSource", "Evaluate source reliability")
253                        .required_param("source", "Source to evaluate"),
254                )
255                .action(ActionSpec::new("Synthesize", "Synthesize findings"))
256                .action(
257                    ActionSpec::new("Answer", "Submit final answer")
258                        .required_param("answer", "Final answer"),
259                ),
260        );
261
262        // Maze Environment
263        registry.register(
264            EnvironmentSpec::new("maze", "Grid navigation environment")
265                .action(
266                    ActionSpec::new("Move", "Move to adjacent cell")
267                        .required_param("direction", "Direction (north/south/east/west)"),
268                )
269                .action(ActionSpec::new("Look", "Look around current position"))
270                .action(ActionSpec::new("Wait", "Wait at current position")),
271        );
272
273        // None Environment (no actions)
274        registry.register(EnvironmentSpec::new(
275            "none",
276            "No-op environment for testing",
277        ));
278
279        // Default/Realworld Environment
280        registry.register(
281            EnvironmentSpec::new("default", "Real filesystem environment")
282                .action(
283                    ActionSpec::new("Bash", "Execute bash command")
284                        .required_param("command", "Command to execute"),
285                )
286                .action(ActionSpec::new("Read", "Read file").required_param("path", "File path"))
287                .action(
288                    ActionSpec::new("Write", "Write file")
289                        .required_param("path", "File path")
290                        .required_param("content", "File content"),
291                )
292                .action(
293                    ActionSpec::new("Grep", "Search pattern in files")
294                        .required_param("pattern", "Search pattern"),
295                )
296                .action(
297                    ActionSpec::new("Glob", "Find files by pattern")
298                        .required_param("pattern", "Glob pattern"),
299                ),
300        );
301
302        // Alias for realworld
303        if let Some(default_spec) = registry.specs.get("default").cloned() {
304            let mut realworld_spec = default_spec;
305            realworld_spec.env_type = "realworld".to_string();
306            registry
307                .specs
308                .insert("realworld".to_string(), realworld_spec);
309        }
310
311        registry
312    }
313
314    /// Environment 仕様を登録
315    pub fn register(&mut self, spec: EnvironmentSpec) {
316        self.specs.insert(spec.env_type.clone(), spec);
317    }
318
319    /// Environment 仕様を取得
320    pub fn get(&self, env_type: &str) -> Option<&EnvironmentSpec> {
321        self.specs.get(env_type)
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328
329    #[test]
330    fn test_action_spec_matches() {
331        let spec = ActionSpec::new("CheckStatus", "Check service status")
332            .aliases(["check_status", "status"]);
333
334        assert!(spec.matches("CheckStatus"));
335        assert!(spec.matches("checkstatus")); // case insensitive
336        assert!(spec.matches("check_status"));
337        assert!(spec.matches("status"));
338        assert!(!spec.matches("unknown"));
339    }
340
341    #[test]
342    fn test_environment_spec_supports_action() {
343        let spec = EnvironmentSpec::new("test", "Test environment")
344            .action(ActionSpec::new("Action1", "desc").aliases(["a1"]))
345            .action(ActionSpec::new("Action2", "desc"));
346
347        assert!(spec.supports_action("Action1"));
348        assert!(spec.supports_action("a1"));
349        assert!(spec.supports_action("Action2"));
350        assert!(!spec.supports_action("Action3"));
351    }
352
353    #[test]
354    fn test_registry_builtin_specs() {
355        let registry = EnvironmentSpecRegistry::new();
356
357        assert!(registry.get("troubleshooting").is_some());
358        assert!(registry.get("search").is_some());
359        assert!(registry.get("code").is_some());
360        assert!(registry.get("internal_diagnosis").is_some());
361        assert!(registry.get("deep_search").is_some());
362        assert!(registry.get("maze").is_some());
363        assert!(registry.get("none").is_some());
364        assert!(registry.get("default").is_some());
365        assert!(registry.get("realworld").is_some());
366    }
367
368    #[test]
369    fn test_troubleshooting_actions() {
370        let registry = EnvironmentSpecRegistry::new();
371        let spec = registry.get("troubleshooting").unwrap();
372
373        assert!(spec.supports_action("CheckStatus"));
374        assert!(spec.supports_action("check_status"));
375        assert!(spec.supports_action("ReadLogs"));
376        assert!(spec.supports_action("AnalyzeMetrics"));
377        assert!(spec.supports_action("Diagnose"));
378        assert!(spec.supports_action("Restart"));
379        assert!(!spec.supports_action("Unknown"));
380    }
381}