1use aether_core::agent_spec::AgentSpec;
2use aether_project::{AetherSettings, AgentCatalog, SettingsError};
3use llm::{ProviderConnectionOverrides, ReasoningEffort};
4use std::path::Path;
5use thiserror::Error;
6
7const FALLBACK_MODEL: &str = "anthropic:claude-sonnet-4-5";
8
9#[derive(Clone, Debug, Default)]
10pub(crate) enum InitialSessionSelection {
11 #[default]
12 Default,
13 Agent(String),
14 Model {
15 model: String,
16 reasoning_effort: Option<ReasoningEffort>,
17 },
18}
19
20impl InitialSessionSelection {
21 pub(crate) fn agent(name: String) -> Self {
22 Self::Agent(name)
23 }
24
25 pub(crate) fn model(model: String, reasoning_effort: Option<ReasoningEffort>) -> Self {
26 Self::Model { model, reasoning_effort }
27 }
28}
29
30pub(crate) struct ResolvedAgentSelection {
31 pub(crate) spec: AgentSpec,
32 pub(crate) catalog: AgentCatalog,
33}
34
35#[derive(Debug, Error)]
36pub(crate) enum AgentSelectionError {
37 #[error(transparent)]
38 Settings(#[from] SettingsError),
39 #[error("{0}")]
40 Agent(SettingsError),
41 #[error("Model error: {0}")]
42 Model(String),
43}
44
45pub(crate) fn resolve_agent_from_settings(
46 cwd: &Path,
47 settings: AetherSettings,
48 provider_connections: ProviderConnectionOverrides,
49 selection: &InitialSessionSelection,
50) -> Result<ResolvedAgentSelection, AgentSelectionError> {
51 let catalog = AgentCatalog::from_settings_or_empty(cwd, settings)?.with_provider_connections(provider_connections);
52 resolve_agent_from_catalog(catalog, selection)
53}
54
55pub(crate) fn resolve_agent_from_catalog(
56 catalog: AgentCatalog,
57 selection: &InitialSessionSelection,
58) -> Result<ResolvedAgentSelection, AgentSelectionError> {
59 let spec = match selection {
60 InitialSessionSelection::Agent(name) => catalog.resolve(name).map_err(AgentSelectionError::Agent)?,
61 InitialSessionSelection::Model { model, reasoning_effort } => {
62 catalog.default_spec(&model.parse().map_err(AgentSelectionError::Model)?, *reasoning_effort)
63 }
64 InitialSessionSelection::Default => match catalog.default_agent() {
65 Some(spec) => spec.clone(),
66 None => catalog.default_spec(&FALLBACK_MODEL.parse().map_err(AgentSelectionError::Model)?, None),
67 },
68 };
69
70 Ok(ResolvedAgentSelection { spec, catalog })
71}
72
73pub fn resolve_agent_spec(
74 catalog: &AgentCatalog,
75 agent_name: Option<&str>,
76) -> Result<AgentSpec, crate::error::CliError> {
77 let selection =
78 agent_name.map_or(InitialSessionSelection::Default, |name| InitialSessionSelection::Agent(name.to_string()));
79 resolve_agent_from_catalog(catalog.clone(), &selection).map(|resolved| resolved.spec).map_err(|error| match error {
80 AgentSelectionError::Settings(error) | AgentSelectionError::Agent(error) => {
81 crate::error::CliError::AgentError(error.to_string())
82 }
83 AgentSelectionError::Model(error) => crate::error::CliError::ModelError(error),
84 })
85}