Skip to main content

aether_cli/
resolve.rs

1use crate::error::CliError;
2use aether_core::agent_spec::AgentSpec;
3use aether_project::AgentCatalog;
4
5pub fn resolve_agent_spec(catalog: &AgentCatalog, agent_name: Option<&str>) -> Result<AgentSpec, CliError> {
6    match agent_name {
7        Some(name) => catalog.resolve(name).map_err(|e| CliError::AgentError(e.to_string())),
8
9        None => {
10            if let Some(selected) = catalog.default_agent() {
11                catalog.resolve(&selected.name).map_err(|e| CliError::AgentError(e.to_string()))
12            } else {
13                let model = "anthropic:claude-sonnet-4-5".parse().map_err(|e: String| CliError::ModelError(e))?;
14
15                Ok(AgentSpec::default_spec(&model, None, Vec::new()))
16            }
17        }
18    }
19}
20
21#[cfg(test)]
22mod tests {
23    use super::*;
24
25    fn write_file(dir: &std::path::Path, path: &str, content: &str) {
26        let full = dir.join(path);
27        if let Some(parent) = full.parent() {
28            std::fs::create_dir_all(parent).unwrap();
29        }
30        std::fs::write(full, content).unwrap();
31    }
32
33    fn setup_catalog(settings_json: &str) -> (tempfile::TempDir, AgentCatalog) {
34        let dir = tempfile::tempdir().unwrap();
35        write_file(dir.path(), "PROMPT.md", "Be helpful");
36        write_file(dir.path(), ".aether/settings.json", settings_json);
37        let config = aether_project::AetherSettings::load_default(dir.path()).unwrap();
38        let catalog = AgentCatalog::from_settings(dir.path(), config).unwrap();
39        (dir, catalog)
40    }
41
42    #[test]
43    fn resolve_with_explicit_name() {
44        let (_dir, catalog) = setup_catalog(
45            r#"{"agents": [
46                {"name": "first", "description": "First", "model": "anthropic:claude-sonnet-4-5", "userInvocable": true, "prompts": [{"type":"file","path":"PROMPT.md"}]},
47                {"name": "second", "description": "Second", "model": "anthropic:claude-sonnet-4-5", "userInvocable": true, "prompts": [{"type":"file","path":"PROMPT.md"}]}
48            ]}"#,
49        );
50        let spec = resolve_agent_spec(&catalog, Some("second")).unwrap();
51        assert_eq!(spec.name, "second");
52    }
53
54    #[test]
55    fn resolve_auto_selects_first_user_invocable() {
56        let (_dir, catalog) = setup_catalog(
57            r#"{"agents": [
58                {"name": "internal", "description": "Internal", "model": "anthropic:claude-sonnet-4-5", "agentInvocable": true, "prompts": [{"type":"file","path":"PROMPT.md"}]},
59                {"name": "visible", "description": "Visible", "model": "anthropic:claude-sonnet-4-5", "userInvocable": true, "prompts": [{"type":"file","path":"PROMPT.md"}]}
60            ]}"#,
61        );
62        let spec = resolve_agent_spec(&catalog, None).unwrap();
63        assert_eq!(spec.name, "visible");
64    }
65
66    #[test]
67    fn resolve_falls_back_to_default() {
68        let dir = tempfile::tempdir().unwrap();
69        let catalog = AgentCatalog::empty(dir.path().to_path_buf());
70        let spec = resolve_agent_spec(&catalog, None).unwrap();
71        assert_eq!(spec.name, "__default__");
72    }
73
74    #[test]
75    fn resolve_unknown_name_errors() {
76        let (_dir, catalog) = setup_catalog(
77            r#"{"agents": [
78                {"name": "alpha", "description": "Alpha", "model": "anthropic:claude-sonnet-4-5", "userInvocable": true, "prompts": [{"type":"file","path":"PROMPT.md"}]}
79            ]}"#,
80        );
81        let result = resolve_agent_spec(&catalog, Some("nonexistent"));
82        assert!(result.is_err());
83    }
84}