use std::collections::HashMap;
use once_cell::sync::Lazy;
use serde::Deserialize;
use crate::types::{
AnthropicMessagesCompat, Model, ModelCost, OpenAICompletionsCompat, OpenAIResponsesCompat, ThinkingLevelMap,
};
#[derive(Debug, Deserialize)]
struct RawModel {
id: String,
name: String,
api: String,
provider: String,
#[serde(rename = "baseUrl")]
base_url: String,
reasoning: bool,
#[serde(default)]
#[serde(rename = "thinkingLevelMap")]
thinking_level_map: Option<ThinkingLevelMap>,
input: Vec<String>,
cost: RawCost,
#[serde(rename = "contextWindow")]
context_window: u32,
#[serde(rename = "maxTokens")]
max_tokens: u32,
#[serde(default)]
headers: Option<HashMap<String, String>>,
#[serde(default)]
compat: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
struct RawCost {
input: f64,
output: f64,
#[serde(rename = "cacheRead")]
cache_read: f64,
#[serde(rename = "cacheWrite")]
cache_write: f64,
}
fn parse_models(json: &str) -> Vec<Model> {
let raw: HashMap<String, RawModel> = serde_json::from_str(json).expect("invalid embedded model catalog");
raw.into_values().map(convert_model).collect()
}
fn convert_model(raw: RawModel) -> Model {
let (openai_completions_compat, openai_responses_compat, anthropic_compat) =
parse_compat(&raw.api, raw.compat.as_ref());
Model {
id: raw.id,
name: raw.name,
api: raw.api,
provider: raw.provider,
base_url: raw.base_url,
reasoning: raw.reasoning,
thinking_level_map: raw.thinking_level_map,
input: raw.input,
cost: ModelCost {
input: raw.cost.input,
output: raw.cost.output,
cache_read: raw.cost.cache_read,
cache_write: raw.cost.cache_write,
},
context_window: raw.context_window,
max_tokens: raw.max_tokens,
headers: raw.headers,
openai_completions_compat,
openai_responses_compat,
anthropic_compat,
}
}
fn parse_compat(
api: &str,
compat: Option<&serde_json::Value>,
) -> (
Option<OpenAICompletionsCompat>,
Option<OpenAIResponsesCompat>,
Option<AnthropicMessagesCompat>,
) {
let Some(compat) = compat else {
return (None, None, None);
};
match api {
"openai-completions" => (serde_json::from_value(compat.clone()).ok(), None, None),
"openai-responses" | "azure-openai-responses" | "openai-codex-responses" => {
(None, serde_json::from_value(compat.clone()).ok(), None)
}
"anthropic-messages" => (None, None, serde_json::from_value(compat.clone()).ok()),
_ => (None, None, None),
}
}
macro_rules! define_catalog {
($name:ident, $file:literal) => {
pub static $name: Lazy<Vec<Model>> = Lazy::new(|| {
parse_models(include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/models/",
$file
)))
});
};
}
define_catalog!(AMAZON_BEDROCK_MODELS, "amazon_bedrock.json");
define_catalog!(ANT_LING_MODELS, "ant_ling.json");
define_catalog!(ANTHROPIC_MODELS, "anthropic.json");
define_catalog!(AZURE_OPENAI_RESPONSES_MODELS, "azure_openai_responses.json");
define_catalog!(CEREBRAS_MODELS, "cerebras.json");
define_catalog!(CLOUDFLARE_AI_GATEWAY_MODELS, "cloudflare_ai_gateway.json");
define_catalog!(CLOUDFLARE_WORKERS_AI_MODELS, "cloudflare_workers_ai.json");
define_catalog!(DEEPSEEK_MODELS, "deepseek.json");
define_catalog!(FIREWORKS_MODELS, "fireworks.json");
define_catalog!(GITHUB_COPILOT_MODELS, "github_copilot.json");
define_catalog!(GOOGLE_MODELS, "google.json");
define_catalog!(GOOGLE_VERTEX_MODELS, "google_vertex.json");
define_catalog!(GROQ_MODELS, "groq.json");
define_catalog!(HUGGINGFACE_MODELS, "huggingface.json");
define_catalog!(KIMI_CODING_MODELS, "kimi_coding.json");
define_catalog!(MINIMAX_MODELS, "minimax.json");
define_catalog!(MINIMAX_CN_MODELS, "minimax_cn.json");
define_catalog!(MISTRAL_MODELS, "mistral.json");
define_catalog!(MOONSHOTAI_MODELS, "moonshotai.json");
define_catalog!(MOONSHOTAI_CN_MODELS, "moonshotai_cn.json");
define_catalog!(NVIDIA_MODELS, "nvidia.json");
define_catalog!(OPENAI_MODELS, "openai.json");
define_catalog!(OPENAI_CODEX_MODELS, "openai_codex.json");
define_catalog!(OPENCODE_MODELS, "opencode.json");
define_catalog!(OPENCODE_GO_MODELS, "opencode_go.json");
define_catalog!(OPENROUTER_MODELS, "openrouter.json");
define_catalog!(TOGETHER_MODELS, "together.json");
define_catalog!(VERCEL_AI_GATEWAY_MODELS, "vercel_ai_gateway.json");
define_catalog!(XAI_MODELS, "xai.json");
define_catalog!(XIAOMI_MODELS, "xiaomi.json");
define_catalog!(XIAOMI_TOKEN_PLAN_AMS_MODELS, "xiaomi_token_plan_ams.json");
define_catalog!(XIAOMI_TOKEN_PLAN_CN_MODELS, "xiaomi_token_plan_cn.json");
define_catalog!(XIAOMI_TOKEN_PLAN_SGP_MODELS, "xiaomi_token_plan_sgp.json");
define_catalog!(ZAI_MODELS, "zai.json");
define_catalog!(ZAI_CODING_CN_MODELS, "zai_coding_cn.json");
pub fn all_builtin_models() -> HashMap<&'static str, &'static [Model]> {
HashMap::from([
("amazon-bedrock", AMAZON_BEDROCK_MODELS.as_slice()),
("ant-ling", ANT_LING_MODELS.as_slice()),
("anthropic", ANTHROPIC_MODELS.as_slice()),
("azure-openai-responses", AZURE_OPENAI_RESPONSES_MODELS.as_slice()),
("cerebras", CEREBRAS_MODELS.as_slice()),
("cloudflare-ai-gateway", CLOUDFLARE_AI_GATEWAY_MODELS.as_slice()),
("cloudflare-workers-ai", CLOUDFLARE_WORKERS_AI_MODELS.as_slice()),
("deepseek", DEEPSEEK_MODELS.as_slice()),
("fireworks", FIREWORKS_MODELS.as_slice()),
("github-copilot", GITHUB_COPILOT_MODELS.as_slice()),
("google", GOOGLE_MODELS.as_slice()),
("google-vertex", GOOGLE_VERTEX_MODELS.as_slice()),
("groq", GROQ_MODELS.as_slice()),
("huggingface", HUGGINGFACE_MODELS.as_slice()),
("kimi-coding", KIMI_CODING_MODELS.as_slice()),
("minimax", MINIMAX_MODELS.as_slice()),
("minimax-cn", MINIMAX_CN_MODELS.as_slice()),
("mistral", MISTRAL_MODELS.as_slice()),
("moonshotai", MOONSHOTAI_MODELS.as_slice()),
("moonshotai-cn", MOONSHOTAI_CN_MODELS.as_slice()),
("nvidia", NVIDIA_MODELS.as_slice()),
("openai", OPENAI_MODELS.as_slice()),
("openai-codex", OPENAI_CODEX_MODELS.as_slice()),
("opencode", OPENCODE_MODELS.as_slice()),
("opencode-go", OPENCODE_GO_MODELS.as_slice()),
("openrouter", OPENROUTER_MODELS.as_slice()),
("together", TOGETHER_MODELS.as_slice()),
("vercel-ai-gateway", VERCEL_AI_GATEWAY_MODELS.as_slice()),
("xai", XAI_MODELS.as_slice()),
("xiaomi", XIAOMI_MODELS.as_slice()),
("xiaomi-token-plan-ams", XIAOMI_TOKEN_PLAN_AMS_MODELS.as_slice()),
("xiaomi-token-plan-cn", XIAOMI_TOKEN_PLAN_CN_MODELS.as_slice()),
("xiaomi-token-plan-sgp", XIAOMI_TOKEN_PLAN_SGP_MODELS.as_slice()),
("zai", ZAI_MODELS.as_slice()),
("zai-coding-cn", ZAI_CODING_CN_MODELS.as_slice()),
])
}
pub fn get_builtin_model(provider: &str, id: &str) -> Option<Model> {
all_builtin_models().get(provider)?.iter().find(|m| m.id == id).cloned()
}
pub fn get_builtin_models(provider: &str) -> Vec<Model> {
all_builtin_models()
.get(provider)
.map(|models| models.to_vec())
.unwrap_or_default()
}
pub fn get_builtin_providers() -> Vec<&'static str> {
let mut providers: Vec<_> = all_builtin_models().keys().copied().collect();
providers.sort_unstable();
providers
}