use genai::adapter::AdapterKind;
const OPENAI_MODELS: &[&str] = &[
"gpt-4o",
"gpt-4o-mini",
"gpt-4.1",
"gpt-4.1-mini",
"gpt-4-turbo",
"gpt-5",
"gpt-5-mini",
"gpt-5-codex",
"o1",
"o1-mini",
"o3",
"o3-mini",
"o4-mini",
];
const ANTHROPIC_MODELS: &[&str] = &[
"claude-opus-4-6",
"claude-sonnet-4-6",
"claude-opus-4-5",
"claude-3-7-sonnet-latest",
"claude-3-5-sonnet-latest",
"claude-3-5-haiku-latest",
"claude-3-haiku-20240307",
];
const GEMINI_MODELS: &[&str] = &[
"gemini-2.5-pro",
"gemini-2.5-flash",
"gemini-2.0-flash",
"gemini-1.5-pro",
"gemini-1.5-flash",
];
const GROQ_MODELS: &[&str] = &[
"llama-3.3-70b-versatile",
"llama-3.1-8b-instant",
"mixtral-8x7b-32768",
"gemma2-9b-it",
];
const DEEPSEEK_MODELS: &[&str] = &["deepseek-chat", "deepseek-reasoner"];
const XAI_MODELS: &[&str] = &["grok-4", "grok-3", "grok-3-mini", "grok-2-vision"];
const COHERE_MODELS: &[&str] = &[
"command-r-plus",
"command-r",
"command",
"command-nightly",
"command-light",
"command-light-nightly",
];
#[must_use]
pub const fn static_models(adapter_kind: AdapterKind) -> &'static [&'static str] {
match adapter_kind {
AdapterKind::OpenAI => OPENAI_MODELS,
AdapterKind::Anthropic => ANTHROPIC_MODELS,
AdapterKind::Gemini => GEMINI_MODELS,
AdapterKind::Groq => GROQ_MODELS,
AdapterKind::DeepSeek => DEEPSEEK_MODELS,
AdapterKind::Xai => XAI_MODELS,
AdapterKind::Cohere => COHERE_MODELS,
_ => &[],
}
}
#[must_use]
pub fn static_fallback(statics: &[&str]) -> Option<Vec<String>> {
if statics.is_empty() {
None
} else {
Some(merge_models(Vec::new(), statics))
}
}
#[must_use]
pub fn merge_models(
dynamic: Vec<String>,
statics: &[&str],
) -> Vec<String> {
let mut merged = dynamic;
for &model in statics {
if !merged.iter().any(|existing| existing == model) {
merged.push(model.to_string());
}
}
merged
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn static_models_known_providers_non_empty() {
for kind in [
AdapterKind::OpenAI,
AdapterKind::Anthropic,
AdapterKind::Gemini,
AdapterKind::Groq,
AdapterKind::Cohere,
AdapterKind::DeepSeek,
AdapterKind::Xai,
] {
assert!(!static_models(kind).is_empty(), "{kind} should have curated models");
}
}
#[test]
fn static_models_uncatalogued_provider_empty() {
assert!(static_models(AdapterKind::Ollama).is_empty());
}
#[test]
fn merge_preserves_dynamic_order_and_appends_missing_statics() {
let dynamic = vec!["gpt-4o".to_string(), "gpt-custom".to_string()];
let statics = &["gpt-4o", "gpt-5"];
let merged = merge_models(dynamic, statics);
assert_eq!(merged, vec!["gpt-4o", "gpt-custom", "gpt-5"]);
}
#[test]
fn merge_dedups_exact_matches_only() {
let dynamic = vec!["claude-3-5-sonnet-latest".to_string()];
let statics = &["claude-3-5-sonnet-latest", "claude-3-5-sonnet-20241022"];
let merged = merge_models(dynamic, statics);
assert_eq!(merged, vec!["claude-3-5-sonnet-latest", "claude-3-5-sonnet-20241022"]);
}
#[test]
fn merge_with_empty_dynamic_returns_statics() {
let merged = merge_models(Vec::new(), &["command-r", "command"]);
assert_eq!(merged, vec!["command-r", "command"]);
}
#[test]
fn static_fallback_some_when_catalog_present() {
assert_eq!(
static_fallback(&["a", "b"]),
Some(vec!["a".to_string(), "b".to_string()])
);
}
#[test]
fn static_fallback_none_when_catalog_empty() {
assert_eq!(static_fallback(&[]), None);
}
}