use std::collections::BTreeMap;
use thiserror::Error;
use crate::config::{InternalAgentModelConfig, InternalAgentTarget};
use super::{
is_reserved_agent_id, AgentDefinition, AgentId, AgentRuntimeSpec, ModelPolicy, ModelSelection,
ReasoningLevel,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct ConfiguredModel {
pub(crate) model: ModelPolicy,
pub(crate) reasoning: Option<ReasoningLevel>,
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub(crate) enum ConfiguredModelError {
#[error(
"agent '{id}' runs on {runtime}, so [internal_agents.{id}] cannot set its model; remove the entry"
)]
ExternalRuntime { id: AgentId, runtime: String },
#[error(
"agent '{id}': [internal_agents.{id}] selects Claude Code, but only internal agents can switch runtime; name a Rho provider and model"
)]
ClaudeCodeTarget { id: AgentId },
}
pub(crate) fn configured_model(
definition: &AgentDefinition,
entries: &BTreeMap<String, InternalAgentModelConfig>,
) -> Result<Option<ConfiguredModel>, ConfiguredModelError> {
let id = &definition.id;
if is_reserved_agent_id(id) {
return Ok(None);
}
let Some(entry) = entries.get(id.as_str()) else {
return Ok(None);
};
match &definition.runtime {
AgentRuntimeSpec::Rho { .. } => {}
AgentRuntimeSpec::ClaudeCli(_)
| AgentRuntimeSpec::Cursor(_)
| AgentRuntimeSpec::Antigravity(_) => {
return Err(ConfiguredModelError::ExternalRuntime {
id: id.clone(),
runtime: definition.runtime.runtime().to_string(),
})
}
}
match &entry.target {
InternalAgentTarget::Rho(rho) => Ok(Some(ConfiguredModel {
model: ModelPolicy::Select(ModelSelection {
provider: Some(rho.provider.clone()),
model: rho.model.clone(),
auth: Some(rho.auth.clone()),
fast: false,
}),
reasoning: entry.reasoning,
})),
InternalAgentTarget::ClaudeCli { .. } => {
Err(ConfiguredModelError::ClaudeCodeTarget { id: id.clone() })
}
}
}
pub(crate) fn effective_rho_model<'a>(
model: &'a ModelPolicy,
reasoning: Option<ReasoningLevel>,
configured: Option<&'a ConfiguredModel>,
) -> (&'a ModelPolicy, Option<ReasoningLevel>) {
match configured {
Some(chosen) => (&chosen.model, chosen.reasoning.or(reasoning)),
None => (model, reasoning),
}
}