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 use aether_project::{AetherSettings, AetherSettingsSource, SettingsFileSource};
25
26 fn write_file(dir: &std::path::Path, path: &str, content: &str) {
27 let full = dir.join(path);
28 if let Some(parent) = full.parent() {
29 std::fs::create_dir_all(parent).unwrap();
30 }
31 std::fs::write(full, content).unwrap();
32 }
33
34 fn setup_catalog(settings_json: &str) -> (tempfile::TempDir, AgentCatalog) {
35 let dir = tempfile::tempdir().unwrap();
36 write_file(dir.path(), "PROMPT.md", "Be helpful");
37 write_file(dir.path(), ".aether/settings.json", settings_json);
38 let config = AetherSettings::load(
39 dir.path(),
40 [AetherSettingsSource::File(SettingsFileSource::new(".aether/settings.json", dir.path()))],
41 )
42 .unwrap();
43 let catalog = AgentCatalog::from_settings(dir.path(), config).unwrap();
44 (dir, catalog)
45 }
46
47 #[test]
48 fn resolve_with_explicit_name() {
49 let (_dir, catalog) = setup_catalog(
50 r#"{"agents": [
51 {"name": "first", "description": "First", "model": "anthropic:claude-sonnet-4-5", "userInvocable": true, "prompts": [{"type":"file","path":"PROMPT.md"}]},
52 {"name": "second", "description": "Second", "model": "anthropic:claude-sonnet-4-5", "userInvocable": true, "prompts": [{"type":"file","path":"PROMPT.md"}]}
53 ]}"#,
54 );
55 let spec = resolve_agent_spec(&catalog, Some("second")).unwrap();
56 assert_eq!(spec.name, "second");
57 }
58
59 #[test]
60 fn resolve_auto_selects_first_user_invocable() {
61 let (_dir, catalog) = setup_catalog(
62 r#"{"agents": [
63 {"name": "internal", "description": "Internal", "model": "anthropic:claude-sonnet-4-5", "agentInvocable": true, "prompts": [{"type":"file","path":"PROMPT.md"}]},
64 {"name": "visible", "description": "Visible", "model": "anthropic:claude-sonnet-4-5", "userInvocable": true, "prompts": [{"type":"file","path":"PROMPT.md"}]}
65 ]}"#,
66 );
67 let spec = resolve_agent_spec(&catalog, None).unwrap();
68 assert_eq!(spec.name, "visible");
69 }
70
71 #[test]
72 fn resolve_falls_back_to_default() {
73 let dir = tempfile::tempdir().unwrap();
74 let catalog = AgentCatalog::empty(dir.path().to_path_buf());
75 let spec = resolve_agent_spec(&catalog, None).unwrap();
76 assert_eq!(spec.name, "__default__");
77 }
78
79 #[test]
80 fn resolve_unknown_name_errors() {
81 let (_dir, catalog) = setup_catalog(
82 r#"{"agents": [
83 {"name": "alpha", "description": "Alpha", "model": "anthropic:claude-sonnet-4-5", "userInvocable": true, "prompts": [{"type":"file","path":"PROMPT.md"}]}
84 ]}"#,
85 );
86 let result = resolve_agent_spec(&catalog, Some("nonexistent"));
87 assert!(result.is_err());
88 }
89}