feat(providers): add Anthropic to the three provider surfaces — ApiProvider/ProviderKind variants, provider metadata (x-api-key auth env, api.anthropic.com, claude-sonnet-4-6 default), AnthropicMessages wire format, ModelRegistry rows for claude-opus-4-8 / claude-sonnet-4-6 / claude-haiku-4-5 (#3014, WIP)

Co-Authored-By: Claude <noreply@anthropic.com>
https://claude.ai/code/session_018zaP8vUfTAsrE38L6h6fw5
This commit is contained in:
Claude
2026-06-11 03:08:50 +00:00
parent 81b060928b
commit 3c38dabefb
4 changed files with 95 additions and 4 deletions
+22
View File
@@ -607,6 +607,28 @@ impl Default for ModelRegistry {
supports_tools: true,
supports_reasoning: true,
},
// Anthropic native Messages API models (#3014)
ModelInfo {
id: "claude-opus-4-8".to_string(),
provider: ProviderKind::Anthropic,
aliases: vec!["opus".to_string(), "claude-opus".to_string()],
supports_tools: true,
supports_reasoning: true,
},
ModelInfo {
id: "claude-sonnet-4-6".to_string(),
provider: ProviderKind::Anthropic,
aliases: vec!["sonnet".to_string(), "claude-sonnet".to_string()],
supports_tools: true,
supports_reasoning: true,
},
ModelInfo {
id: "claude-haiku-4-5".to_string(),
provider: ProviderKind::Anthropic,
aliases: vec!["haiku".to_string(), "claude-haiku".to_string()],
supports_tools: true,
supports_reasoning: false,
},
// MiniMax 2.7 (OpenRouter)
ModelInfo {
id: "minimax/minimax-2.7".to_string(),
+29 -3
View File
@@ -27,6 +27,8 @@ const DEFAULT_OPENAI_MODEL: &str = "deepseek-v4-pro";
const DEFAULT_DEEPSEEK_BASE_URL: &str = "https://api.deepseek.com/beta";
const DEFAULT_NVIDIA_NIM_BASE_URL: &str = "https://integrate.api.nvidia.com/v1";
const DEFAULT_OPENAI_CODEX_MODEL: &str = "gpt-5.5";
const DEFAULT_ANTHROPIC_MODEL: &str = "claude-sonnet-4-6";
const DEFAULT_ANTHROPIC_BASE_URL: &str = "https://api.anthropic.com";
const DEFAULT_OPENAI_CODEX_BASE_URL: &str = "https://chatgpt.com/backend-api";
const DEFAULT_OPENAI_BASE_URL: &str = "https://api.openai.com/v1";
const DEFAULT_ATLASCLOUD_MODEL: &str = "deepseek-ai/deepseek-v4-flash";
@@ -152,10 +154,12 @@ pub enum ProviderKind {
alias = "chatgpt_codex"
)]
OpenaiCodex,
#[serde(alias = "claude")]
Anthropic,
}
impl ProviderKind {
pub const ALL: [Self; 20] = [
pub const ALL: [Self; 21] = [
Self::Deepseek,
Self::NvidiaNim,
Self::Openai,
@@ -176,6 +180,7 @@ impl ProviderKind {
Self::Huggingface,
Self::Together,
Self::OpenaiCodex,
Self::Anthropic,
];
#[must_use]
@@ -201,6 +206,7 @@ impl ProviderKind {
Self::Huggingface => "huggingface",
Self::Together => "together",
Self::OpenaiCodex => "openai-codex",
Self::Anthropic => "anthropic",
}
}
@@ -231,6 +237,7 @@ impl ProviderKind {
"ollama" | "ollama-local" => Some(Self::Ollama),
"huggingface" | "hugging-face" | "hugging_face" | "hf" => Some(Self::Huggingface),
"together" | "together-ai" | "together_ai" => Some(Self::Together),
"anthropic" | "claude" => Some(Self::Anthropic),
"openai-codex" | "openai_codex" | "openaicodex" | "codex" | "chatgpt"
| "chatgpt-codex" | "chatgpt_codex" | "chatgptcodex" => Some(Self::OpenaiCodex),
_ => None,
@@ -312,6 +319,8 @@ pub struct ProvidersToml {
alias = "chatgpt-codex"
)]
pub openai_codex: ProviderConfigToml,
#[serde(default)]
pub anthropic: ProviderConfigToml,
}
/// Sibling `permissions.toml` schema.
@@ -361,6 +370,7 @@ impl ProvidersToml {
ProviderKind::Huggingface => &self.huggingface,
ProviderKind::Together => &self.together,
ProviderKind::OpenaiCodex => &self.openai_codex,
ProviderKind::Anthropic => &self.anthropic,
}
}
@@ -385,6 +395,7 @@ impl ProvidersToml {
ProviderKind::Huggingface => &mut self.huggingface,
ProviderKind::Together => &mut self.together,
ProviderKind::OpenaiCodex => &mut self.openai_codex,
ProviderKind::Anthropic => &mut self.anthropic,
}
}
}
@@ -2022,6 +2033,7 @@ impl ConfigToml {
ProviderKind::Huggingface => DEFAULT_HUGGINGFACE_BASE_URL.to_string(),
ProviderKind::Together => DEFAULT_TOGETHER_BASE_URL.to_string(),
ProviderKind::OpenaiCodex => DEFAULT_OPENAI_CODEX_BASE_URL.to_string(),
ProviderKind::Anthropic => DEFAULT_ANTHROPIC_BASE_URL.to_string(),
})
};
// CLI flag wins outright. Otherwise: config-file → injected secrets/env.
@@ -2454,6 +2466,7 @@ fn default_model_for_provider(provider: ProviderKind) -> &'static str {
ProviderKind::Huggingface => DEFAULT_HUGGINGFACE_MODEL,
ProviderKind::Together => DEFAULT_TOGETHER_MODEL,
ProviderKind::OpenaiCodex => DEFAULT_OPENAI_CODEX_MODEL,
ProviderKind::Anthropic => DEFAULT_ANTHROPIC_MODEL,
}
}
@@ -2479,6 +2492,7 @@ fn default_base_url_for_provider(provider: ProviderKind) -> &'static str {
ProviderKind::Huggingface => DEFAULT_HUGGINGFACE_BASE_URL,
ProviderKind::Together => DEFAULT_TOGETHER_BASE_URL,
ProviderKind::OpenaiCodex => DEFAULT_OPENAI_CODEX_BASE_URL,
ProviderKind::Anthropic => DEFAULT_ANTHROPIC_BASE_URL,
}
}
@@ -3231,6 +3245,8 @@ struct EnvRuntimeOverrides {
together_model: Option<String>,
openai_codex_base_url: Option<String>,
openai_codex_model: Option<String>,
anthropic_base_url: Option<String>,
anthropic_model: Option<String>,
}
impl EnvRuntimeOverrides {
@@ -3394,6 +3410,12 @@ impl EnvRuntimeOverrides {
.or_else(|_| std::env::var("CODEX_MODEL"))
.ok()
.filter(|v| !v.trim().is_empty()),
anthropic_base_url: std::env::var("ANTHROPIC_BASE_URL")
.ok()
.filter(|v| !v.trim().is_empty()),
anthropic_model: std::env::var("ANTHROPIC_MODEL")
.ok()
.filter(|v| !v.trim().is_empty()),
}
}
@@ -3422,6 +3444,7 @@ impl EnvRuntimeOverrides {
ProviderKind::Huggingface => self.huggingface_base_url.clone(),
ProviderKind::Together => self.together_base_url.clone(),
ProviderKind::OpenaiCodex => self.openai_codex_base_url.clone(),
ProviderKind::Anthropic => self.anthropic_base_url.clone(),
}
}
@@ -3441,6 +3464,7 @@ impl EnvRuntimeOverrides {
ProviderKind::Huggingface => self.huggingface_model.clone(),
ProviderKind::Together => self.together_model.clone(),
ProviderKind::OpenaiCodex => self.openai_codex_model.clone(),
ProviderKind::Anthropic => self.anthropic_model.clone(),
_ => None,
}?;
@@ -5132,10 +5156,12 @@ unix_socket_path = "/tmp/cw-hooks.sock"
);
assert!(!provider.display_name().trim().is_empty());
assert!(!provider.env_vars().is_empty());
// OpenAI Codex (ChatGPT) speaks the Responses API; every other
// built-in provider is OpenAI-compatible Chat Completions.
// OpenAI Codex (ChatGPT) speaks the Responses API and Anthropic
// speaks the native Messages API; every other built-in provider
// is OpenAI-compatible Chat Completions.
let expected_wire = match kind {
ProviderKind::OpenaiCodex => provider::WireFormat::Responses,
ProviderKind::Anthropic => provider::WireFormat::AnthropicMessages,
_ => provider::WireFormat::ChatCompletions,
};
assert_eq!(provider.wire(), expected_wire);
+39 -1
View File
@@ -27,6 +27,8 @@ pub enum WireFormat {
ChatCompletions,
/// OpenAI Responses API (`/responses`).
Responses,
/// Native Anthropic Messages API (`/v1/messages`).
AnthropicMessages,
}
/// Static metadata for a built-in model provider.
@@ -320,6 +322,39 @@ impl Provider for OpenaiCodex {
}
}
/// Native Anthropic Messages API provider (#3014).
pub struct Anthropic;
impl Provider for Anthropic {
fn kind(&self) -> ProviderKind {
ProviderKind::Anthropic
}
fn display_name(&self) -> &'static str {
"Anthropic"
}
fn default_base_url(&self) -> &'static str {
crate::DEFAULT_ANTHROPIC_BASE_URL
}
fn default_model(&self) -> &'static str {
crate::DEFAULT_ANTHROPIC_MODEL
}
fn env_vars(&self) -> &'static [&'static str] {
&["ANTHROPIC_API_KEY"]
}
fn provider_config_key(&self) -> &'static str {
"anthropic"
}
fn wire(&self) -> WireFormat {
WireFormat::AnthropicMessages
}
}
static DEEPSEEK: Deepseek = Deepseek;
static NVIDIA_NIM: NvidiaNim = NvidiaNim;
static OPENAI: Openai = Openai;
@@ -340,8 +375,9 @@ static OLLAMA: Ollama = Ollama;
static HUGGINGFACE: Huggingface = Huggingface;
static TOGETHER: Together = Together;
static OPENAI_CODEX: OpenaiCodex = OpenaiCodex;
static ANTHROPIC: Anthropic = Anthropic;
static PROVIDER_REGISTRY: [&dyn Provider; 20] = [
static PROVIDER_REGISTRY: [&dyn Provider; 21] = [
&DEEPSEEK,
&NVIDIA_NIM,
&OPENAI,
@@ -362,6 +398,7 @@ static PROVIDER_REGISTRY: [&dyn Provider; 20] = [
&HUGGINGFACE,
&TOGETHER,
&OPENAI_CODEX,
&ANTHROPIC,
];
/// Return all built-in provider metadata entries in `ProviderKind::ALL` order.
@@ -410,5 +447,6 @@ pub fn provider_for_kind(kind: ProviderKind) -> &'static dyn Provider {
ProviderKind::Huggingface => &HUGGINGFACE,
ProviderKind::Together => &TOGETHER,
ProviderKind::OpenaiCodex => &OPENAI_CODEX,
ProviderKind::Anthropic => &ANTHROPIC,
}
}
+5
View File
@@ -188,6 +188,7 @@ pub enum ApiProvider {
Huggingface,
Together,
OpenaiCodex,
Anthropic,
}
impl ApiProvider {
@@ -237,6 +238,7 @@ impl ApiProvider {
"ollama" | "ollama-local" => Some(Self::Ollama),
"huggingface" | "hugging-face" | "hugging_face" | "hf" => Some(Self::Huggingface),
"together" | "together-ai" | "together_ai" => Some(Self::Together),
"anthropic" | "claude" => Some(Self::Anthropic),
"openai-codex" | "openai_codex" | "openaicodex" | "codex" | "chatgpt"
| "chatgpt-codex" | "chatgpt_codex" | "chatgptcodex" => Some(Self::OpenaiCodex),
_ => None,
@@ -267,6 +269,7 @@ impl ApiProvider {
Self::Huggingface => "huggingface",
Self::Together => "together",
Self::OpenaiCodex => "openai-codex",
Self::Anthropic => "anthropic",
}
}
@@ -295,6 +298,7 @@ impl ApiProvider {
Self::Huggingface => "Hugging Face",
Self::Together => "Together AI",
Self::OpenaiCodex => "OpenAI Codex (ChatGPT)",
Self::Anthropic => "Anthropic",
}
}
@@ -322,6 +326,7 @@ impl ApiProvider {
Self::Huggingface,
Self::Together,
Self::OpenaiCodex,
Self::Anthropic,
]
}
}