use crate::chat::{anthropic::AnthropicProvider, openai_compat};
use crate::model::provider::ChatProvider;
use std::sync::Arc;
#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
pub(crate) struct ProviderSummary {
pub name: String,
pub runtime: String,
pub requires_key: bool,
pub env_var: Option<String>,
}
pub(crate) fn provider_catalog() -> Vec<ProviderSummary> {
[
"anthropic",
"openai",
"kimi",
"ollama",
"lmstudio",
"llamacpp",
"turbofieldfare",
"airllm",
]
.into_iter()
.map(|name| {
let provider = try_select_provider(name).expect("catalog provider must be selectable");
ProviderSummary {
name: provider.name().to_string(),
runtime: provider.runtime_kind().label().to_string(),
requires_key: provider.requires_key(),
env_var: provider
.requires_key()
.then(|| provider.env_var().to_string()),
}
})
.collect()
}
pub(crate) fn try_select_provider(name: &str) -> Result<Arc<dyn ChatProvider>, String> {
match name {
"anthropic" => Ok(Arc::new(AnthropicProvider)),
"openai" => Ok(Arc::new(openai_compat::openai())),
"ollama" => Ok(Arc::new(openai_compat::ollama())),
"lmstudio" => Ok(Arc::new(openai_compat::lm_studio())),
"llamacpp" => Ok(Arc::new(openai_compat::llama_cpp())),
"kimi" => Ok(Arc::new(openai_compat::kimi())),
"turbofieldfare" => Ok(Arc::new(openai_compat::turbofieldfare())),
"airllm" => Ok(Arc::new(openai_compat::airllm())),
other => Err(format!(
"unknown provider '{other}' — use ollama | lmstudio | llamacpp | turbofieldfare | airllm | anthropic | openai | kimi"
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn catalog_matches_every_selectable_provider() {
let names: Vec<String> = provider_catalog()
.into_iter()
.map(|item| item.name)
.collect();
assert_eq!(
names,
[
"anthropic",
"openai",
"kimi",
"ollama",
"lmstudio",
"llamacpp",
"turbofieldfare",
"airllm"
]
);
}
#[test]
fn unknown_provider_name_is_a_named_error_not_a_panic() {
assert!(try_select_provider("nonexistent").is_err());
}
}