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 {
pub canonical_name: String,
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)
}
}
#[derive(Debug, Clone)]
pub struct EnvironmentSpec {
pub env_type: String,
pub actions: Vec<ActionSpec>,
pub description: String,
}
impl EnvironmentSpec {
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))
}
}
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(),
};
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"),
),
);
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"),
),
);
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"])),
);
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"),
),
);
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"),
),
);
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")),
);
registry.register(EnvironmentSpec::new(
"none",
"No-op environment for testing",
));
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"),
),
);
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
}
pub fn register(&mut self, spec: EnvironmentSpec) {
self.specs.insert(spec.env_type.clone(), spec);
}
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")); 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"));
}
}