Skip to main content

aether_cli/
resolve.rs

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