use crate::Platform;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Protocol {
OpenAi,
Anthropic,
OpenAiResponses,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Activation {
OutOfBox,
RequiresOneTimeStep(&'static str),
}
#[derive(Debug, Clone)]
pub struct CatalogModel {
pub public_id: &'static str,
pub cloud: Platform,
pub upstream_id: &'static str,
pub protocol: Protocol,
}
impl CatalogModel {
pub fn provider(&self) -> &'static str {
let id = self.public_id;
if id.starts_with("claude") {
"anthropic"
} else if id.starts_with("gpt") || id == "model-router" {
"openai"
} else if id.starts_with("gemini") || id.starts_with("gemma") {
"google"
} else if id.starts_with("qwen") {
"qwen"
} else if id.starts_with("deepseek") {
"deepseek"
} else if id.starts_with("mistral")
|| id.starts_with("devstral")
|| id.starts_with("magistral")
|| id.starts_with("ministral")
{
"mistral"
} else if id.starts_with("minimax") {
"minimax"
} else if id.starts_with("kimi") {
"moonshotai"
} else if id.starts_with("nemotron") {
"nvidia"
} else if id.starts_with("glm") {
"zai"
} else if id.starts_with("palmyra") {
"writer"
} else {
"unknown"
}
}
pub fn display_name(&self) -> &'static str {
match self.public_id {
"gpt-5.6-sol" => "GPT-5.6 Sol",
"gpt-5.6-terra" => "GPT-5.6 Terra",
"gpt-5.6-luna" => "GPT-5.6 Luna",
"gpt-5.5" => "GPT-5.5",
"gpt-5.4" => "GPT-5.4",
"gpt-oss-20b" => "GPT-OSS 20B",
"gpt-oss-120b" => "GPT-OSS 120B",
"gpt-oss-safeguard-20b" => "GPT-OSS Safeguard 20B",
"gpt-oss-safeguard-120b" => "GPT-OSS Safeguard 120B",
"deepseek-v3.2" => "DeepSeek V3.2",
"qwen3-32b" => "Qwen3 32B",
"qwen3-coder-30b" => "Qwen3 Coder 30B",
"qwen3-coder-next" => "Qwen3 Coder Next",
"qwen3-next-80b" => "Qwen3 Next 80B",
"qwen3-vl-235b" => "Qwen3 VL 235B",
"mistral-large-3" => "Mistral Large 3",
"devstral-2" => "Devstral 2",
"magistral-small" => "Magistral Small",
"ministral-3-14b" => "Ministral 3 14B",
"ministral-3-8b" => "Ministral 3 8B",
"ministral-3-3b" => "Ministral 3 3B",
"minimax-m2" => "MiniMax M2",
"minimax-m2.1" => "MiniMax M2.1",
"minimax-m2.5" => "MiniMax M2.5",
"kimi-k2.5" => "Kimi K2.5",
"nemotron-nano-9b" => "Nemotron Nano 9B",
"nemotron-nano-12b" => "Nemotron Nano 12B",
"nemotron-nano-3-30b" => "Nemotron Nano 3 30B",
"nemotron-super-3-120b" => "Nemotron Super 3 120B",
"gemma-3-4b" => "Gemma 3 4B",
"gemma-3-12b" => "Gemma 3 12B",
"gemma-3-27b" => "Gemma 3 27B",
"glm-4.7" => "GLM 4.7",
"glm-4.7-flash" => "GLM 4.7 Flash",
"glm-5" => "GLM 5",
"palmyra-vision-7b" => "Palmyra Vision 7B",
"claude-opus-5" => "Claude Opus 5",
"claude-sonnet-5" => "Claude Sonnet 5",
"claude-opus-4.8" => "Claude Opus 4.8",
"claude-opus-4.7" => "Claude Opus 4.7",
"claude-opus-4.6" => "Claude Opus 4.6",
"claude-opus-4.5" => "Claude Opus 4.5",
"claude-opus-4.1" => "Claude Opus 4.1",
"claude-sonnet-4.6" => "Claude Sonnet 4.6",
"claude-sonnet-4.5" => "Claude Sonnet 4.5",
"claude-haiku-4.5" => "Claude Haiku 4.5",
"claude-fable-5" => "Claude Fable 5",
"claude-mythos-5" => "Claude Mythos 5",
"claude-sonnet-4" => "Claude Sonnet 4",
"claude-3-haiku" => "Claude 3 Haiku",
"gemini-2.5-pro" => "Gemini 2.5 Pro",
"gemini-2.5-flash" => "Gemini 2.5 Flash",
"gemini-2.5-flash-lite" => "Gemini 2.5 Flash Lite",
"gemini-3.5-flash" => "Gemini 3.5 Flash",
"gemini-3.1-flash-lite" => "Gemini 3.1 Flash Lite",
"gpt-4.1" => "GPT-4.1",
"gpt-4o-mini" => "GPT-4o mini",
"model-router" => "Model Router",
other => other,
}
}
pub fn activation(&self) -> Activation {
if !self.public_id.starts_with("claude") {
return Activation::OutOfBox;
}
match self.cloud {
Platform::Aws => Activation::RequiresOneTimeStep(
"Submit the one-time Anthropic use-case form in the Bedrock console.",
),
Platform::Gcp => Activation::RequiresOneTimeStep(
"Enable Claude in Vertex AI Model Garden and accept Anthropic's terms of service, one-time, in the Google Cloud console.",
),
Platform::Azure => Activation::RequiresOneTimeStep(
"Accept the Marketplace terms and create the Claude deployment in the Microsoft Foundry portal (one-time).",
),
_ => Activation::OutOfBox,
}
}
}
static CATALOG: &[CatalogModel] = &[
CatalogModel { public_id: "gpt-5.6-sol", cloud: Platform::Aws, upstream_id: "openai.gpt-5.6-sol", protocol: Protocol::OpenAiResponses },
CatalogModel { public_id: "gpt-5.6-terra", cloud: Platform::Aws, upstream_id: "openai.gpt-5.6-terra", protocol: Protocol::OpenAiResponses },
CatalogModel { public_id: "gpt-5.6-luna", cloud: Platform::Aws, upstream_id: "openai.gpt-5.6-luna", protocol: Protocol::OpenAiResponses },
CatalogModel { public_id: "gpt-5.5", cloud: Platform::Aws, upstream_id: "openai.gpt-5.5", protocol: Protocol::OpenAiResponses },
CatalogModel { public_id: "gpt-5.4", cloud: Platform::Aws, upstream_id: "openai.gpt-5.4", protocol: Protocol::OpenAiResponses },
CatalogModel { public_id: "gpt-oss-20b", cloud: Platform::Aws, upstream_id: "openai.gpt-oss-20b-1:0", protocol: Protocol::OpenAi },
CatalogModel { public_id: "gpt-oss-120b", cloud: Platform::Aws, upstream_id: "openai.gpt-oss-120b-1:0", protocol: Protocol::OpenAi },
CatalogModel { public_id: "gpt-oss-safeguard-20b", cloud: Platform::Aws, upstream_id: "openai.gpt-oss-safeguard-20b", protocol: Protocol::OpenAi },
CatalogModel { public_id: "gpt-oss-safeguard-120b", cloud: Platform::Aws, upstream_id: "openai.gpt-oss-safeguard-120b", protocol: Protocol::OpenAi },
CatalogModel { public_id: "deepseek-v3.2", cloud: Platform::Aws, upstream_id: "deepseek.v3.2", protocol: Protocol::OpenAi },
CatalogModel { public_id: "qwen3-32b", cloud: Platform::Aws, upstream_id: "qwen.qwen3-32b-v1:0", protocol: Protocol::OpenAi },
CatalogModel { public_id: "qwen3-coder-30b", cloud: Platform::Aws, upstream_id: "qwen.qwen3-coder-30b-a3b-v1:0", protocol: Protocol::OpenAi },
CatalogModel { public_id: "qwen3-coder-next", cloud: Platform::Aws, upstream_id: "qwen.qwen3-coder-next", protocol: Protocol::OpenAi },
CatalogModel { public_id: "qwen3-next-80b", cloud: Platform::Aws, upstream_id: "qwen.qwen3-next-80b-a3b", protocol: Protocol::OpenAi },
CatalogModel { public_id: "qwen3-vl-235b", cloud: Platform::Aws, upstream_id: "qwen.qwen3-vl-235b-a22b", protocol: Protocol::OpenAi },
CatalogModel { public_id: "mistral-large-3", cloud: Platform::Aws, upstream_id: "mistral.mistral-large-3-675b-instruct", protocol: Protocol::OpenAi },
CatalogModel { public_id: "devstral-2", cloud: Platform::Aws, upstream_id: "mistral.devstral-2-123b", protocol: Protocol::OpenAi },
CatalogModel { public_id: "magistral-small", cloud: Platform::Aws, upstream_id: "mistral.magistral-small-2509", protocol: Protocol::OpenAi },
CatalogModel { public_id: "ministral-3-14b", cloud: Platform::Aws, upstream_id: "mistral.ministral-3-14b-instruct", protocol: Protocol::OpenAi },
CatalogModel { public_id: "ministral-3-8b", cloud: Platform::Aws, upstream_id: "mistral.ministral-3-8b-instruct", protocol: Protocol::OpenAi },
CatalogModel { public_id: "ministral-3-3b", cloud: Platform::Aws, upstream_id: "mistral.ministral-3-3b-instruct", protocol: Protocol::OpenAi },
CatalogModel { public_id: "minimax-m2", cloud: Platform::Aws, upstream_id: "minimax.minimax-m2", protocol: Protocol::OpenAi },
CatalogModel { public_id: "minimax-m2.1", cloud: Platform::Aws, upstream_id: "minimax.minimax-m2.1", protocol: Protocol::OpenAi },
CatalogModel { public_id: "minimax-m2.5", cloud: Platform::Aws, upstream_id: "minimax.minimax-m2.5", protocol: Protocol::OpenAi },
CatalogModel { public_id: "kimi-k2.5", cloud: Platform::Aws, upstream_id: "moonshotai.kimi-k2.5", protocol: Protocol::OpenAi },
CatalogModel { public_id: "nemotron-nano-9b", cloud: Platform::Aws, upstream_id: "nvidia.nemotron-nano-9b-v2", protocol: Protocol::OpenAi },
CatalogModel { public_id: "nemotron-nano-12b", cloud: Platform::Aws, upstream_id: "nvidia.nemotron-nano-12b-v2", protocol: Protocol::OpenAi },
CatalogModel { public_id: "nemotron-nano-3-30b", cloud: Platform::Aws, upstream_id: "nvidia.nemotron-nano-3-30b", protocol: Protocol::OpenAi },
CatalogModel { public_id: "nemotron-super-3-120b", cloud: Platform::Aws, upstream_id: "nvidia.nemotron-super-3-120b", protocol: Protocol::OpenAi },
CatalogModel { public_id: "gemma-3-4b", cloud: Platform::Aws, upstream_id: "google.gemma-3-4b-it", protocol: Protocol::OpenAi },
CatalogModel { public_id: "gemma-3-12b", cloud: Platform::Aws, upstream_id: "google.gemma-3-12b-it", protocol: Protocol::OpenAi },
CatalogModel { public_id: "gemma-3-27b", cloud: Platform::Aws, upstream_id: "google.gemma-3-27b-it", protocol: Protocol::OpenAi },
CatalogModel { public_id: "glm-4.7", cloud: Platform::Aws, upstream_id: "zai.glm-4.7", protocol: Protocol::OpenAi },
CatalogModel { public_id: "glm-4.7-flash", cloud: Platform::Aws, upstream_id: "zai.glm-4.7-flash", protocol: Protocol::OpenAi },
CatalogModel { public_id: "glm-5", cloud: Platform::Aws, upstream_id: "zai.glm-5", protocol: Protocol::OpenAi },
CatalogModel { public_id: "palmyra-vision-7b", cloud: Platform::Aws, upstream_id: "writer.palmyra-vision-7b", protocol: Protocol::OpenAi },
CatalogModel { public_id: "claude-opus-5", cloud: Platform::Aws, upstream_id: "anthropic.claude-opus-5", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-sonnet-5", cloud: Platform::Aws, upstream_id: "anthropic.claude-sonnet-5", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-opus-4.8", cloud: Platform::Aws, upstream_id: "anthropic.claude-opus-4-8", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-opus-4.7", cloud: Platform::Aws, upstream_id: "anthropic.claude-opus-4-7", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-opus-4.6", cloud: Platform::Aws, upstream_id: "anthropic.claude-opus-4-6-v1", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-opus-4.5", cloud: Platform::Aws, upstream_id: "anthropic.claude-opus-4-5-20251101-v1:0", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-opus-4.1", cloud: Platform::Aws, upstream_id: "anthropic.claude-opus-4-1-20250805-v1:0", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-sonnet-4.6", cloud: Platform::Aws, upstream_id: "anthropic.claude-sonnet-4-6", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-sonnet-4.5", cloud: Platform::Aws, upstream_id: "anthropic.claude-sonnet-4-5-20250929-v1:0", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-haiku-4.5", cloud: Platform::Aws, upstream_id: "anthropic.claude-haiku-4-5-20251001-v1:0", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-fable-5", cloud: Platform::Aws, upstream_id: "anthropic.claude-fable-5", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-mythos-5", cloud: Platform::Aws, upstream_id: "anthropic.claude-mythos-5", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-sonnet-4", cloud: Platform::Aws, upstream_id: "anthropic.claude-sonnet-4-20250514-v1:0", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-3-haiku", cloud: Platform::Aws, upstream_id: "anthropic.claude-3-haiku-20240307-v1:0", protocol: Protocol::Anthropic },
CatalogModel { public_id: "gemini-2.5-pro", cloud: Platform::Gcp, upstream_id: "google/gemini-2.5-pro", protocol: Protocol::OpenAi },
CatalogModel { public_id: "gemini-2.5-flash", cloud: Platform::Gcp, upstream_id: "google/gemini-2.5-flash", protocol: Protocol::OpenAi },
CatalogModel { public_id: "gemini-2.5-flash-lite", cloud: Platform::Gcp, upstream_id: "google/gemini-2.5-flash-lite", protocol: Protocol::OpenAi },
CatalogModel { public_id: "gemini-3.5-flash", cloud: Platform::Gcp, upstream_id: "google/gemini-3.5-flash", protocol: Protocol::OpenAi },
CatalogModel { public_id: "gemini-3.1-flash-lite", cloud: Platform::Gcp, upstream_id: "google/gemini-3.1-flash-lite", protocol: Protocol::OpenAi },
CatalogModel { public_id: "claude-sonnet-5", cloud: Platform::Gcp, upstream_id: "claude-sonnet-5", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-opus-4.8", cloud: Platform::Gcp, upstream_id: "claude-opus-4-8", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-opus-4.7", cloud: Platform::Gcp, upstream_id: "claude-opus-4-7", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-opus-4.6", cloud: Platform::Gcp, upstream_id: "claude-opus-4-6", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-opus-4.5", cloud: Platform::Gcp, upstream_id: "claude-opus-4-5@20251101", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-sonnet-4.6", cloud: Platform::Gcp, upstream_id: "claude-sonnet-4-6", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-sonnet-4.5", cloud: Platform::Gcp, upstream_id: "claude-sonnet-4-5@20250929", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-haiku-4.5", cloud: Platform::Gcp, upstream_id: "claude-haiku-4-5@20251001", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-fable-5", cloud: Platform::Gcp, upstream_id: "claude-fable-5", protocol: Protocol::Anthropic },
CatalogModel { public_id: "gpt-4.1", cloud: Platform::Azure, upstream_id: "gpt-4.1", protocol: Protocol::OpenAi },
CatalogModel { public_id: "gpt-4o-mini", cloud: Platform::Azure, upstream_id: "gpt-4o-mini", protocol: Protocol::OpenAi },
CatalogModel { public_id: "model-router", cloud: Platform::Azure, upstream_id: "model-router", protocol: Protocol::OpenAi },
CatalogModel { public_id: "claude-sonnet-5", cloud: Platform::Azure, upstream_id: "claude-sonnet-5", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-opus-4.8", cloud: Platform::Azure, upstream_id: "claude-opus-4-8", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-opus-4.7", cloud: Platform::Azure, upstream_id: "claude-opus-4-7", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-opus-4.6", cloud: Platform::Azure, upstream_id: "claude-opus-4-6", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-opus-4.5", cloud: Platform::Azure, upstream_id: "claude-opus-4-5", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-sonnet-4.6", cloud: Platform::Azure, upstream_id: "claude-sonnet-4-6", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-sonnet-4.5", cloud: Platform::Azure, upstream_id: "claude-sonnet-4-5", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-haiku-4.5", cloud: Platform::Azure, upstream_id: "claude-haiku-4-5", protocol: Protocol::Anthropic },
CatalogModel { public_id: "claude-fable-5", cloud: Platform::Azure, upstream_id: "claude-fable-5", protocol: Protocol::Anthropic },
];
static AZURE_DEPLOYMENTS: &[(&str, &str, &str)] = &[
("gpt-4.1", "gpt-4.1", "2025-04-14"),
("gpt-4o-mini", "gpt-4o-mini", "2024-07-18"),
("model-router", "model-router", "2025-11-18"),
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResponsesTarget {
pub upstream_id: &'static str,
pub path: &'static str,
}
static RESPONSES_UPSTREAM: &[(&str, ResponsesTarget)] = &[
("gpt-oss-20b", ResponsesTarget { upstream_id: "openai.gpt-oss-20b", path: "/v1/responses" }),
("gpt-oss-120b", ResponsesTarget { upstream_id: "openai.gpt-oss-120b", path: "/v1/responses" }),
("gpt-5.6-sol", ResponsesTarget { upstream_id: "openai.gpt-5.6-sol", path: "/openai/v1/responses" }),
("gpt-5.6-terra", ResponsesTarget { upstream_id: "openai.gpt-5.6-terra", path: "/openai/v1/responses" }),
("gpt-5.6-luna", ResponsesTarget { upstream_id: "openai.gpt-5.6-luna", path: "/openai/v1/responses" }),
("gpt-5.5", ResponsesTarget { upstream_id: "openai.gpt-5.5", path: "/openai/v1/responses" }),
("gpt-5.4", ResponsesTarget { upstream_id: "openai.gpt-5.4", path: "/openai/v1/responses" }),
];
pub fn responses_target(public_id: &str) -> Option<ResponsesTarget> {
RESPONSES_UPSTREAM
.iter()
.find(|(public, _)| *public == public_id)
.map(|(_, target)| *target)
}
pub fn models_for(cloud: Platform) -> Vec<&'static CatalogModel> {
CATALOG.iter().filter(|m| m.cloud == cloud).collect()
}
pub fn lookup(public_id: &str) -> Option<&'static CatalogModel> {
CATALOG.iter().find(|m| m.public_id == public_id)
}
fn lookup_for(public_id: &str, cloud: Platform) -> Option<&'static CatalogModel> {
CATALOG.iter().find(|m| m.public_id == public_id && m.cloud == cloud)
}
pub fn resolve_for(model_id: &str, cloud: Platform) -> Option<&'static CatalogModel> {
lookup_for(model_id, cloud).or_else(|| lookup_for(&canonical_public_id(model_id), cloud))
}
pub fn resolve(model_id: &str) -> Option<&'static CatalogModel> {
lookup(model_id).or_else(|| lookup(&canonical_public_id(model_id)))
}
fn canonical_public_id(model_id: &str) -> String {
let mut id = model_id;
if let Some(pos) = id.rfind("anthropic.") {
id = &id[pos + "anthropic.".len()..];
}
id = id.split_once('@').map_or(id, |(base, _)| base);
id = strip_invoke_version(id);
id = strip_release_date(id);
dot_minor_version(id)
}
fn strip_invoke_version(id: &str) -> &str {
let base = id.split_once(':').map_or(id, |(base, _)| base);
match base.rsplit_once("-v") {
Some((stem, digits)) if !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit()) => {
stem
}
_ => base,
}
}
fn strip_release_date(id: &str) -> &str {
match id.rsplit_once('-') {
Some((stem, date))
if date.len() == 8 && date.starts_with("20") && date.bytes().all(|b| b.is_ascii_digit()) =>
{
stem
}
_ => id,
}
}
fn dot_minor_version(id: &str) -> String {
let Some((stem, minor)) = id.rsplit_once('-') else {
return id.to_string();
};
let Some((prefix, major)) = stem.rsplit_once('-') else {
return id.to_string();
};
let both_numeric = !major.is_empty()
&& !minor.is_empty()
&& major.bytes().all(|b| b.is_ascii_digit())
&& minor.bytes().all(|b| b.is_ascii_digit());
if both_numeric {
format!("{prefix}-{major}.{minor}")
} else {
id.to_string()
}
}
pub fn azure_deployments() -> Vec<(&'static str, &'static str, &'static str)> {
AZURE_DEPLOYMENTS.to_vec()
}
#[cfg(test)]
mod tests {
#[test]
fn public_ids_are_unique_per_cloud() {
let mut seen = std::collections::HashSet::new();
for model in super::CATALOG {
assert!(
seen.insert((model.cloud, model.public_id)),
"public id '{}' appears more than once under {:?}",
model.public_id,
model.cloud
);
}
}
use super::*;
#[test]
fn resolve_accepts_anthropic_native_spellings() {
assert_eq!(resolve("claude-haiku-4-5").unwrap().public_id, "claude-haiku-4.5");
assert_eq!(
resolve("claude-sonnet-4-5-20250929").unwrap().public_id,
"claude-sonnet-4.5"
);
assert_eq!(
resolve("us.anthropic.claude-haiku-4-5-20251001-v1:0").unwrap().public_id,
"claude-haiku-4.5"
);
assert_eq!(
resolve("anthropic.claude-opus-4-6-v1").unwrap().public_id,
"claude-opus-4.6"
);
assert_eq!(resolve("claude-sonnet-5").unwrap().public_id, "claude-sonnet-5");
assert_eq!(resolve("claude-opus-4.8").unwrap().public_id, "claude-opus-4.8");
assert_eq!(resolve("gpt-oss-20b").unwrap().public_id, "gpt-oss-20b");
assert!(resolve("claude-nonexistent-9-9").is_none());
assert!(resolve("gpt-5").is_none());
}
#[test]
fn aws_has_openai_and_anthropic_with_plain_ids() {
let aws = models_for(Platform::Aws);
assert!(!aws.is_empty());
assert!(aws
.iter()
.any(|m| m.public_id == "gpt-oss-20b" && m.protocol == Protocol::OpenAi));
assert!(
aws.iter().any(|m| m.protocol == Protocol::Anthropic),
"Claude must be included via the Anthropic protocol"
);
assert!(aws.iter().all(|m| !m.upstream_id.starts_with("us.")));
}
#[test]
fn resolve_for_scopes_to_cloud() {
let aws = resolve_for("claude-opus-4.8", Platform::Aws).expect("aws claude");
assert_eq!(aws.upstream_id, "anthropic.claude-opus-4-8");
let gcp = resolve_for("claude-opus-4.8", Platform::Gcp).expect("gcp claude");
assert_eq!(gcp.upstream_id, "claude-opus-4-8");
assert_eq!(gcp.protocol, Protocol::Anthropic);
let dated = resolve_for("claude-haiku-4-5-20251001", Platform::Gcp).expect("dated id");
assert_eq!(dated.upstream_id, "claude-haiku-4-5@20251001");
let vertex = resolve_for("claude-sonnet-4-5@20250929", Platform::Gcp).expect("vertex id");
assert_eq!(vertex.upstream_id, "claude-sonnet-4-5@20250929");
assert!(resolve_for("gemini-2.5-pro", Platform::Aws).is_none());
assert!(resolve_for("gpt-4.1", Platform::Gcp).is_none());
}
#[test]
fn lookup_round_trips() {
let m = lookup("gpt-oss-20b").expect("known model");
assert_eq!(m.cloud, Platform::Aws);
assert_eq!(m.protocol, Protocol::OpenAi);
assert_eq!(m.upstream_id, "openai.gpt-oss-20b-1:0");
let c = lookup("claude-opus-4.8").expect("claude known");
assert_eq!(c.protocol, Protocol::Anthropic);
assert!(lookup("nonexistent-model").is_none());
}
#[test]
fn azure_deployments_map_to_catalog() {
assert!(!azure_deployments().is_empty());
for (deployment, _, _) in azure_deployments() {
assert!(
models_for(Platform::Azure)
.iter()
.any(|m| m.upstream_id == deployment),
"azure deployment {deployment} must map to a catalog model"
);
}
}
#[test]
fn protocol_serializes_lowercase() {
assert_eq!(serde_json::to_string(&Protocol::OpenAi).unwrap(), "\"openai\"");
assert_eq!(serde_json::to_string(&Protocol::Anthropic).unwrap(), "\"anthropic\"");
assert_eq!(
serde_json::to_string(&Protocol::OpenAiResponses).unwrap(),
"\"openairesponses\""
);
}
#[test]
fn every_model_has_provider_display_name_and_activation() {
for m in CATALOG {
assert_ne!(m.provider(), "unknown", "no provider mapping for '{}'", m.public_id);
assert_ne!(
m.display_name(),
m.public_id,
"no curated display_name for '{}'",
m.public_id
);
let is_claude = m.public_id.starts_with("claude");
match m.activation() {
Activation::OutOfBox => {
assert!(!is_claude, "'{}' (Claude) must require a one-time step", m.public_id)
}
Activation::RequiresOneTimeStep(summary) => {
assert!(is_claude, "'{}' must be out of the box", m.public_id);
assert!(!summary.is_empty(), "'{}' step summary is empty", m.public_id);
}
}
}
}
#[test]
fn only_claude_ids_speak_the_anthropic_protocol() {
for m in CATALOG {
assert_eq!(
m.protocol == Protocol::Anthropic,
m.public_id.starts_with("claude"),
"'{}' is {:?} but its id says otherwise",
m.public_id,
m.protocol
);
}
}
}