use std::collections::BTreeMap;
use crate::config::schema::AgentConfig;
#[derive(Debug, Clone, PartialEq)]
pub struct ModelSelection {
pub value: Option<String>,
pub prompt: String,
}
pub fn select_model(prompt: &str) -> ModelSelection {
let trimmed = prompt.trim();
let after_flag = if let Some(rest) = trimmed.strip_prefix("--model") {
rest
} else if let Some(rest) = trimmed.strip_prefix("-m") {
rest
} else {
return ModelSelection {
value: None,
prompt: trimmed.to_owned(),
};
};
let after_separator = if let Some(rest) = after_flag.strip_prefix('=') {
rest
} else {
match after_flag.find(|character: char| !character.is_whitespace()) {
Some(0) | None => {
return ModelSelection {
value: None,
prompt: trimmed.to_owned(),
};
}
Some(run) => &after_flag[run..],
}
};
let value_end = after_separator
.find(char::is_whitespace)
.unwrap_or(after_separator.len());
if value_end == 0 {
return ModelSelection {
value: None,
prompt: trimmed.to_owned(),
};
}
ModelSelection {
value: Some(after_separator[..value_end].to_owned()),
prompt: after_separator[value_end..].trim().to_owned(),
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ChosenModel {
pub provider: Option<String>,
pub model: String,
}
pub fn resolve_model(value: &str, known: &[&str]) -> ChosenModel {
if let Some(slash) = value.find('/')
&& slash > 0
&& known.contains(&&value[..slash])
{
return ChosenModel {
provider: Some(value[..slash].to_owned()),
model: value[slash + 1..].to_owned(),
};
}
ChosenModel {
provider: None,
model: value.to_owned(),
}
}
const LEVELS: [&str; 7] = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
pub fn split_level(value: &str) -> (String, String) {
let Some(colon) = value.rfind(':') else {
return (value.to_owned(), String::new());
};
if colon == 0 {
return (value.to_owned(), String::new());
}
let suffix = value[colon + 1..].to_lowercase();
if LEVELS.contains(&suffix.as_str()) {
(value[..colon].to_owned(), value[colon..].to_owned())
} else {
(value.to_owned(), String::new())
}
}
pub fn expand_alias(value: &str, aliases: &BTreeMap<String, String>) -> String {
let (asked, level) = split_level(value.trim());
let Some(target) = aliases.get(&asked) else {
return value.trim().to_owned();
};
if level.is_empty() {
return target.clone();
}
format!("{}{level}", split_level(target).0)
}
#[cfg(test)]
mod tests;
pub fn known_providers(agent: &AgentConfig) -> Vec<&str> {
let mut known = vec![agent.provider.as_str()];
known.extend(agent.providers.keys().map(String::as_str));
known
}
pub fn configured_model(agent: &AgentConfig) -> Option<ChosenModel> {
let named = agent.model.as_deref()?;
Some(resolve_model(
&expand_alias(named, &agent.aliases),
&known_providers(agent),
))
}