use crate::config::{resolve_magi_override, MagiModelsConfig};
use magi_core::schema::AgentName;
#[derive(Debug, Default, Clone)]
pub struct MagiEnvModels {
pub melchior: Option<String>,
pub balthasar: Option<String>,
pub caspar: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct MagiAdapterSpec {
pub agent: AgentName,
pub model: String,
pub adapter_name: String,
}
pub fn agent_label(agent: AgentName) -> &'static str {
match agent {
AgentName::Melchior => "melchior",
AgentName::Balthasar => "balthasar",
AgentName::Caspar => "caspar",
}
}
pub fn resolve_magi_adapter_specs(
backend_label: &str,
cfg: &MagiModelsConfig,
env: &MagiEnvModels,
) -> Vec<MagiAdapterSpec> {
[
(
AgentName::Melchior,
cfg.melchior_model.as_deref(),
env.melchior.as_deref(),
),
(
AgentName::Balthasar,
cfg.balthasar_model.as_deref(),
env.balthasar.as_deref(),
),
(
AgentName::Caspar,
cfg.caspar_model.as_deref(),
env.caspar.as_deref(),
),
]
.into_iter()
.filter_map(|(agent, toml, env_model)| {
resolve_magi_override(toml, env_model).map(|model| MagiAdapterSpec {
agent,
model,
adapter_name: format!("{backend_label}-{}", agent_label(agent)),
})
})
.collect()
}
pub fn static_override_notice(is_static: bool, has_overrides: bool) -> Option<String> {
if is_static && has_overrides {
Some("[magi] per-agent models ignored: no API backend configured".to_string())
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
fn cfg(m: Option<&str>, b: Option<&str>, c: Option<&str>) -> MagiModelsConfig {
MagiModelsConfig {
melchior_model: m.map(str::to_string),
balthasar_model: b.map(str::to_string),
caspar_model: c.map(str::to_string),
}
}
#[test]
fn test_no_overrides_yields_empty_specs() {
let specs = resolve_magi_adapter_specs(
"anthropic",
&MagiModelsConfig::default(),
&MagiEnvModels::default(),
);
assert!(specs.is_empty());
}
#[test]
fn test_empty_string_override_yields_empty_specs() {
let specs = resolve_magi_adapter_specs(
"anthropic",
&cfg(Some(""), None, None),
&MagiEnvModels {
melchior: Some(" ".into()),
..Default::default()
},
);
assert!(specs.is_empty());
}
#[test]
fn test_single_override_anthropic_naming() {
let specs = resolve_magi_adapter_specs(
"anthropic",
&cfg(Some("qwen3:8b"), None, None),
&MagiEnvModels::default(),
);
assert_eq!(specs.len(), 1);
assert_eq!(specs[0].agent, AgentName::Melchior);
assert_eq!(specs[0].model, "qwen3:8b");
assert_eq!(specs[0].adapter_name, "anthropic-melchior");
}
#[test]
fn test_three_overrides() {
let specs = resolve_magi_adapter_specs(
"anthropic",
&cfg(Some("a"), Some("b"), Some("c")),
&MagiEnvModels::default(),
);
assert_eq!(specs.len(), 3);
let names: Vec<&str> = specs.iter().map(|s| s.adapter_name.as_str()).collect();
assert_eq!(
names,
[
"anthropic-melchior",
"anthropic-balthasar",
"anthropic-caspar"
]
);
}
#[test]
fn test_openai_naming_and_env_precedence() {
let specs = resolve_magi_adapter_specs(
"openai",
&cfg(None, None, Some("toml-deepseek")),
&MagiEnvModels {
caspar: Some("deepseek-r1:32b".into()),
..Default::default()
},
);
assert_eq!(specs.len(), 1);
assert_eq!(specs[0].agent, AgentName::Caspar);
assert_eq!(specs[0].model, "deepseek-r1:32b"); assert_eq!(specs[0].adapter_name, "openai-caspar");
}
#[test]
fn test_static_with_overrides_emits_notice() {
let n = static_override_notice(true, true);
assert!(n.as_deref().unwrap().contains("per-agent models ignored"));
}
#[test]
fn test_no_notice_when_not_static_or_no_overrides() {
assert_eq!(static_override_notice(false, true), None);
assert_eq!(static_override_notice(true, false), None);
assert_eq!(static_override_notice(false, false), None);
}
}