use std::{collections::HashMap, sync::OnceLock};
fn builtin_base_url(provider: &str) -> (&'static str, &'static str) {
match provider {
"anthropic" => ("https://api.anthropic.com/v1", "x-api-key"),
"openai" => ("https://api.openai.com/v1", "bearer"),
"deepseek" => ("https://api.deepseek.com/v1", "bearer"),
"qwen" => ("https://dashscope.aliyuncs.com/compatible-mode/v1", "bearer"),
"doubao" => ("https://ark.cn-beijing.volces.com/api/v3", "bearer"),
"minimax" => ("https://api.minimaxi.com/v1", "bearer"),
"kimi" | "moonshot" => ("https://api.moonshot.cn/v1", "bearer"),
"zhipu" => ("https://open.bigmodel.cn/api/paas/v4", "bearer"),
"groq" => ("https://api.groq.com/openai/v1", "bearer"),
"grok" | "xai" => ("https://api.x.ai/v1", "bearer"),
"gemini" => ("https://generativelanguage.googleapis.com/v1beta", "bearer"),
"siliconflow" => ("https://api.siliconflow.cn/v1", "bearer"),
"openrouter" => ("https://openrouter.ai/api/v1", "bearer"),
"gaterouter" => ("https://api.gaterouter.ai/openai/v1", "bearer"),
"stepfun" => ("https://api.stepfun.com/v1", "bearer"),
"cerebras" => ("https://api.cerebras.ai/v1", "bearer"),
"cohere" => ("https://api.cohere.com/v2", "bearer"),
"lingyi" => ("https://api.lingyiwanwu.com/v1", "bearer"),
"mistral" => ("https://api.mistral.ai/v1", "bearer"),
"ollama" => ("http://localhost:11434", "none"),
_ => ("", "bearer"),
}
}
fn toml_providers() -> &'static HashMap<String, String> {
static CACHE: OnceLock<HashMap<String, String>> = OnceLock::new();
CACHE.get_or_init(|| {
#[derive(serde::Deserialize)]
struct ProvEntry {
name: String,
#[serde(default)]
base_url: String,
}
#[derive(serde::Deserialize, Default)]
struct Defs {
#[serde(default)]
providers: Vec<ProvEntry>,
}
let raw = crate::config::loader::load_defaults_toml();
let defs: Defs = toml::from_str(&raw).unwrap_or_default();
defs.providers
.into_iter()
.filter(|p| !p.base_url.is_empty())
.map(|p| (p.name, p.base_url))
.collect()
})
}
pub fn resolve_base_url(provider: &str) -> (String, &'static str) {
let (builtin_url, auth) = builtin_base_url(provider);
if let Some(url) = toml_providers().get(provider) {
return (url.clone(), auth);
}
if provider == "moonshot" {
if let Some(url) = toml_providers().get("kimi") {
return (url.clone(), auth);
}
}
if provider == "xai" {
if let Some(url) = toml_providers().get("grok") {
return (url.clone(), auth);
}
}
(builtin_url.to_owned(), auth)
}
pub fn has_version_suffix(url: &str) -> bool {
let trimmed = url.trim_end_matches('/');
trimmed
.rsplit('/')
.next()
.is_some_and(|seg| seg.starts_with('v') && seg.chars().nth(1).is_some_and(|c| c.is_ascii_digit()))
}
pub fn models_url(provider: &str, base_url: &str) -> String {
let trimmed = base_url.trim_end_matches('/');
if provider == "ollama" {
format!("{trimmed}/api/tags")
} else {
format!("{trimmed}/models")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builtin_known_providers() {
let (url, auth) = builtin_base_url("anthropic");
assert_eq!(url, "https://api.anthropic.com/v1");
assert_eq!(auth, "x-api-key");
let (url, _) = builtin_base_url("openai");
assert_eq!(url, "https://api.openai.com/v1");
let (url, _) = builtin_base_url("zhipu");
assert_eq!(url, "https://open.bigmodel.cn/api/paas/v4");
let (url, auth) = builtin_base_url("ollama");
assert_eq!(url, "http://localhost:11434");
assert_eq!(auth, "none");
}
#[test]
fn builtin_unknown_returns_empty() {
let (url, auth) = builtin_base_url("nonexistent");
assert_eq!(url, "");
assert_eq!(auth, "bearer");
}
#[test]
fn has_version_suffix_checks() {
assert!(has_version_suffix("https://api.openai.com/v1"));
assert!(has_version_suffix("https://open.bigmodel.cn/api/paas/v4"));
assert!(has_version_suffix("https://generativelanguage.googleapis.com/v1beta"));
assert!(has_version_suffix("https://api.cohere.com/v2/"));
assert!(!has_version_suffix("https://api.anthropic.com"));
assert!(!has_version_suffix("http://localhost:11434"));
assert!(!has_version_suffix("https://api.example.com/api"));
}
#[test]
fn models_url_appends_directly() {
assert_eq!(
models_url("openai", "https://api.openai.com/v1"),
"https://api.openai.com/v1/models"
);
assert_eq!(
models_url("zhipu", "https://open.bigmodel.cn/api/paas/v4"),
"https://open.bigmodel.cn/api/paas/v4/models"
);
assert_eq!(
models_url("doubao", "https://ark.cn-beijing.volces.com/api/v3"),
"https://ark.cn-beijing.volces.com/api/v3/models"
);
assert_eq!(
models_url("custom", "http://macstudio.local/v1"),
"http://macstudio.local/v1/models"
);
assert_eq!(
models_url("custom", "http://macstudio.local"),
"http://macstudio.local/models"
);
assert_eq!(
models_url("ollama", "http://localhost:11434"),
"http://localhost:11434/api/tags"
);
}
#[test]
fn resolve_falls_back_to_builtin() {
let (url, auth) = resolve_base_url("openai");
assert!(!url.is_empty());
assert_eq!(auth, "bearer");
}
}