use std::collections::HashMap;
use std::sync::OnceLock;
use crate::core::types::model::ModelInfo;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ModelFeature {
MultimodalSupport,
ToolCalling,
FunctionCalling,
StreamingSupport,
CacheControl,
SystemMessages,
BatchProcessing,
ThinkingMode,
ComputerUse,
}
#[derive(Debug, Clone, PartialEq)]
pub enum AnthropicModelFamily {
ClaudeOpus46,
ClaudeOpus45,
ClaudeSonnet46,
ClaudeSonnet45,
ClaudeHaiku45,
ClaudeSonnet4,
Claude35Sonnet,
Claude3Opus,
Claude3Sonnet,
Claude3Haiku,
Claude21,
Claude2,
ClaudeInstant,
}
#[derive(Debug, Clone)]
pub struct ModelPricing {
pub input_price: f64,
pub output_price: f64,
pub cache_write_price: Option<f64>,
pub cache_read_price: Option<f64>,
pub batch_discount: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct ModelLimits {
pub max_context_length: u32,
pub max_output_tokens: u32,
pub max_images: Option<u32>,
pub max_document_size_mb: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct ModelSpec {
pub model_info: ModelInfo,
pub family: AnthropicModelFamily,
pub features: Vec<ModelFeature>,
pub pricing: ModelPricing,
pub limits: ModelLimits,
pub config: ModelConfig,
}
#[derive(Debug, Clone, Default)]
pub struct ModelConfig {
pub requires_special_formatting: bool,
pub max_concurrent_requests: Option<u32>,
pub custom_params: HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct AnthropicModelRegistry {
models: HashMap<String, ModelSpec>,
}
impl AnthropicModelRegistry {
pub fn new() -> Self {
let mut registry = Self {
models: HashMap::new(),
};
registry.initialize_models();
registry
}
fn initialize_models(&mut self) {
self.register_model(
"claude-opus-4-6",
ModelSpec {
model_info: ModelInfo {
id: "claude-opus-4-6".to_string(),
name: "Claude Opus 4.6".to_string(),
provider: "anthropic".to_string(),
max_context_length: 1_000_000,
max_output_length: Some(128_000),
supports_streaming: true,
supports_tools: true,
supports_multimodal: true,
input_cost_per_1k_tokens: Some(0.005), output_cost_per_1k_tokens: Some(0.025), currency: "USD".to_string(),
capabilities: vec![
crate::core::types::model::ProviderCapability::ChatCompletion,
crate::core::types::model::ProviderCapability::ChatCompletionStream,
crate::core::types::model::ProviderCapability::ToolCalling,
],
created_at: None,
updated_at: None,
metadata: std::collections::HashMap::new(),
},
family: AnthropicModelFamily::ClaudeOpus46,
features: vec![
ModelFeature::MultimodalSupport,
ModelFeature::ToolCalling,
ModelFeature::FunctionCalling,
ModelFeature::StreamingSupport,
ModelFeature::CacheControl,
ModelFeature::SystemMessages,
ModelFeature::BatchProcessing,
ModelFeature::ThinkingMode,
ModelFeature::ComputerUse,
],
pricing: ModelPricing {
input_price: 5.0,
output_price: 25.0,
cache_write_price: Some(6.25),
cache_read_price: Some(0.50),
batch_discount: Some(0.5),
},
limits: ModelLimits {
max_context_length: 1_000_000,
max_output_tokens: 128_000,
max_images: Some(100),
max_document_size_mb: Some(100),
},
config: ModelConfig::default(),
},
);
self.register_model(
"claude-opus-4-5-20251101",
ModelSpec {
model_info: ModelInfo {
id: "claude-opus-4-5-20251101".to_string(),
name: "Claude Opus 4.5".to_string(),
provider: "anthropic".to_string(),
max_context_length: 200_000,
max_output_length: Some(32_000),
supports_streaming: true,
supports_tools: true,
supports_multimodal: true,
input_cost_per_1k_tokens: Some(0.005), output_cost_per_1k_tokens: Some(0.025), currency: "USD".to_string(),
capabilities: vec![
crate::core::types::model::ProviderCapability::ChatCompletion,
crate::core::types::model::ProviderCapability::ChatCompletionStream,
crate::core::types::model::ProviderCapability::ToolCalling,
],
created_at: None,
updated_at: None,
metadata: std::collections::HashMap::new(),
},
family: AnthropicModelFamily::ClaudeOpus45,
features: vec![
ModelFeature::MultimodalSupport,
ModelFeature::ToolCalling,
ModelFeature::FunctionCalling,
ModelFeature::StreamingSupport,
ModelFeature::CacheControl,
ModelFeature::SystemMessages,
ModelFeature::BatchProcessing,
ModelFeature::ThinkingMode,
ModelFeature::ComputerUse,
],
pricing: ModelPricing {
input_price: 5.0, output_price: 25.0, cache_write_price: Some(6.25), cache_read_price: Some(0.50), batch_discount: Some(0.5),
},
limits: ModelLimits {
max_context_length: 200_000,
max_output_tokens: 32_000,
max_images: Some(100),
max_document_size_mb: Some(100),
},
config: ModelConfig::default(),
},
);
self.register_model(
"claude-sonnet-4-5-20250929",
ModelSpec {
model_info: ModelInfo {
id: "claude-sonnet-4-5-20250929".to_string(),
name: "Claude Sonnet 4.5".to_string(),
provider: "anthropic".to_string(),
max_context_length: 200_000,
max_output_length: Some(16_000),
supports_streaming: true,
supports_tools: true,
supports_multimodal: true,
input_cost_per_1k_tokens: Some(0.003),
output_cost_per_1k_tokens: Some(0.015),
currency: "USD".to_string(),
capabilities: vec![
crate::core::types::model::ProviderCapability::ChatCompletion,
crate::core::types::model::ProviderCapability::ChatCompletionStream,
crate::core::types::model::ProviderCapability::ToolCalling,
],
created_at: None,
updated_at: None,
metadata: std::collections::HashMap::new(),
},
family: AnthropicModelFamily::ClaudeSonnet45,
features: vec![
ModelFeature::MultimodalSupport,
ModelFeature::ToolCalling,
ModelFeature::FunctionCalling,
ModelFeature::StreamingSupport,
ModelFeature::CacheControl,
ModelFeature::SystemMessages,
ModelFeature::BatchProcessing,
ModelFeature::ThinkingMode,
ModelFeature::ComputerUse,
],
pricing: ModelPricing {
input_price: 3.0,
output_price: 15.0,
cache_write_price: Some(3.75),
cache_read_price: Some(0.30),
batch_discount: Some(0.5),
},
limits: ModelLimits {
max_context_length: 200_000,
max_output_tokens: 16_000,
max_images: Some(100),
max_document_size_mb: Some(100),
},
config: ModelConfig::default(),
},
);
self.register_model(
"claude-sonnet-4-6-20251001",
ModelSpec {
model_info: ModelInfo {
id: "claude-sonnet-4-6-20251001".to_string(),
name: "Claude Sonnet 4.6".to_string(),
provider: "anthropic".to_string(),
max_context_length: 200_000,
max_output_length: Some(16_000),
supports_streaming: true,
supports_tools: true,
supports_multimodal: true,
input_cost_per_1k_tokens: Some(0.003),
output_cost_per_1k_tokens: Some(0.015),
currency: "USD".to_string(),
capabilities: vec![
crate::core::types::model::ProviderCapability::ChatCompletion,
crate::core::types::model::ProviderCapability::ChatCompletionStream,
crate::core::types::model::ProviderCapability::ToolCalling,
],
created_at: None,
updated_at: None,
metadata: std::collections::HashMap::new(),
},
family: AnthropicModelFamily::ClaudeSonnet46,
features: vec![
ModelFeature::MultimodalSupport,
ModelFeature::ToolCalling,
ModelFeature::FunctionCalling,
ModelFeature::StreamingSupport,
ModelFeature::CacheControl,
ModelFeature::SystemMessages,
ModelFeature::BatchProcessing,
ModelFeature::ThinkingMode,
ModelFeature::ComputerUse,
],
pricing: ModelPricing {
input_price: 3.0,
output_price: 15.0,
cache_write_price: Some(3.75),
cache_read_price: Some(0.30),
batch_discount: Some(0.5),
},
limits: ModelLimits {
max_context_length: 200_000,
max_output_tokens: 16_000,
max_images: Some(100),
max_document_size_mb: Some(100),
},
config: ModelConfig::default(),
},
);
self.register_model(
"claude-haiku-4-5-20251001",
ModelSpec {
model_info: ModelInfo {
id: "claude-haiku-4-5-20251001".to_string(),
name: "Claude Haiku 4.5".to_string(),
provider: "anthropic".to_string(),
max_context_length: 200_000,
max_output_length: Some(16_000),
supports_streaming: true,
supports_tools: true,
supports_multimodal: true,
input_cost_per_1k_tokens: Some(0.0008),
output_cost_per_1k_tokens: Some(0.004),
currency: "USD".to_string(),
capabilities: vec![
crate::core::types::model::ProviderCapability::ChatCompletion,
crate::core::types::model::ProviderCapability::ChatCompletionStream,
crate::core::types::model::ProviderCapability::ToolCalling,
],
created_at: None,
updated_at: None,
metadata: std::collections::HashMap::new(),
},
family: AnthropicModelFamily::ClaudeHaiku45,
features: vec![
ModelFeature::MultimodalSupport,
ModelFeature::ToolCalling,
ModelFeature::FunctionCalling,
ModelFeature::StreamingSupport,
ModelFeature::CacheControl,
ModelFeature::SystemMessages,
ModelFeature::BatchProcessing,
],
pricing: ModelPricing {
input_price: 0.80,
output_price: 4.0,
cache_write_price: Some(1.00),
cache_read_price: Some(0.08),
batch_discount: Some(0.5),
},
limits: ModelLimits {
max_context_length: 200_000,
max_output_tokens: 16_000,
max_images: Some(100),
max_document_size_mb: Some(100),
},
config: ModelConfig::default(),
},
);
self.register_model(
"claude-sonnet-4-20251022",
ModelSpec {
model_info: ModelInfo {
id: "claude-sonnet-4-20251022".to_string(),
name: "Claude Sonnet 4".to_string(),
provider: "anthropic".to_string(),
max_context_length: 200_000,
max_output_length: Some(16_000),
supports_streaming: true,
supports_tools: true,
supports_multimodal: true,
input_cost_per_1k_tokens: Some(0.003),
output_cost_per_1k_tokens: Some(0.015),
currency: "USD".to_string(),
capabilities: vec![
crate::core::types::model::ProviderCapability::ChatCompletion,
crate::core::types::model::ProviderCapability::ChatCompletionStream,
crate::core::types::model::ProviderCapability::ToolCalling,
],
created_at: None,
updated_at: None,
metadata: std::collections::HashMap::new(),
},
family: AnthropicModelFamily::ClaudeSonnet4,
features: vec![
ModelFeature::MultimodalSupport,
ModelFeature::ToolCalling,
ModelFeature::FunctionCalling,
ModelFeature::StreamingSupport,
ModelFeature::CacheControl,
ModelFeature::SystemMessages,
ModelFeature::BatchProcessing,
ModelFeature::ThinkingMode,
ModelFeature::ComputerUse,
],
pricing: ModelPricing {
input_price: 3.0,
output_price: 15.0,
cache_write_price: Some(3.75),
cache_read_price: Some(0.30),
batch_discount: Some(0.5),
},
limits: ModelLimits {
max_context_length: 200_000,
max_output_tokens: 16_000,
max_images: Some(100),
max_document_size_mb: Some(100),
},
config: ModelConfig::default(),
},
);
self.register_model(
"claude-3-5-haiku-20241022",
ModelSpec {
model_info: ModelInfo {
id: "claude-3-5-haiku-20241022".to_string(),
name: "Claude 3.5 Haiku".to_string(),
provider: "anthropic".to_string(),
max_context_length: 200_000,
max_output_length: Some(8_192),
supports_streaming: true,
supports_tools: true,
supports_multimodal: true,
input_cost_per_1k_tokens: Some(0.001),
output_cost_per_1k_tokens: Some(0.005),
currency: "USD".to_string(),
capabilities: vec![
crate::core::types::model::ProviderCapability::ChatCompletion,
crate::core::types::model::ProviderCapability::ChatCompletionStream,
crate::core::types::model::ProviderCapability::ToolCalling,
],
created_at: None,
updated_at: None,
metadata: std::collections::HashMap::new(),
},
family: AnthropicModelFamily::Claude3Haiku,
features: vec![
ModelFeature::MultimodalSupport,
ModelFeature::ToolCalling,
ModelFeature::FunctionCalling,
ModelFeature::StreamingSupport,
ModelFeature::CacheControl,
ModelFeature::SystemMessages,
ModelFeature::BatchProcessing,
],
pricing: ModelPricing {
input_price: 1.0,
output_price: 5.0,
cache_write_price: Some(1.25),
cache_read_price: Some(0.10),
batch_discount: Some(0.5),
},
limits: ModelLimits {
max_context_length: 200_000,
max_output_tokens: 8_192,
max_images: Some(20),
max_document_size_mb: Some(32),
},
config: ModelConfig::default(),
},
);
self.register_model(
"claude-3-5-sonnet-20241022",
ModelSpec {
model_info: ModelInfo {
id: "claude-3-5-sonnet-20241022".to_string(),
name: "Claude 3.5 Sonnet".to_string(),
provider: "anthropic".to_string(),
max_context_length: 200_000,
max_output_length: Some(8_192),
supports_streaming: true,
supports_tools: true,
supports_multimodal: true,
input_cost_per_1k_tokens: Some(0.003),
output_cost_per_1k_tokens: Some(0.015),
currency: "USD".to_string(),
capabilities: vec![
crate::core::types::model::ProviderCapability::ChatCompletion,
crate::core::types::model::ProviderCapability::ChatCompletionStream,
crate::core::types::model::ProviderCapability::ToolCalling,
],
created_at: None,
updated_at: None,
metadata: std::collections::HashMap::new(),
},
family: AnthropicModelFamily::Claude35Sonnet,
features: vec![
ModelFeature::MultimodalSupport,
ModelFeature::ToolCalling,
ModelFeature::FunctionCalling,
ModelFeature::StreamingSupport,
ModelFeature::CacheControl,
ModelFeature::SystemMessages,
ModelFeature::BatchProcessing,
ModelFeature::ThinkingMode,
ModelFeature::ComputerUse,
],
pricing: ModelPricing {
input_price: 3.0,
output_price: 15.0,
cache_write_price: Some(3.75),
cache_read_price: Some(0.30),
batch_discount: Some(0.5),
},
limits: ModelLimits {
max_context_length: 200_000,
max_output_tokens: 8_192,
max_images: Some(20),
max_document_size_mb: Some(32),
},
config: ModelConfig::default(),
},
);
self.register_model(
"claude-3-opus-20240229",
ModelSpec {
model_info: ModelInfo {
id: "claude-3-opus-20240229".to_string(),
name: "Claude 3 Opus".to_string(),
provider: "anthropic".to_string(),
max_context_length: 200_000,
max_output_length: Some(4_096),
supports_streaming: true,
supports_tools: true,
supports_multimodal: true,
input_cost_per_1k_tokens: Some(0.015),
output_cost_per_1k_tokens: Some(0.075),
currency: "USD".to_string(),
capabilities: vec![
crate::core::types::model::ProviderCapability::ChatCompletion,
crate::core::types::model::ProviderCapability::ChatCompletionStream,
crate::core::types::model::ProviderCapability::ToolCalling,
],
created_at: None,
updated_at: None,
metadata: std::collections::HashMap::new(),
},
family: AnthropicModelFamily::Claude3Opus,
features: vec![
ModelFeature::MultimodalSupport,
ModelFeature::ToolCalling,
ModelFeature::FunctionCalling,
ModelFeature::StreamingSupport,
ModelFeature::CacheControl,
ModelFeature::SystemMessages,
ModelFeature::BatchProcessing,
],
pricing: ModelPricing {
input_price: 15.0,
output_price: 75.0,
cache_write_price: Some(18.75),
cache_read_price: Some(1.50),
batch_discount: Some(0.5),
},
limits: ModelLimits {
max_context_length: 200_000,
max_output_tokens: 4_096,
max_images: Some(20),
max_document_size_mb: Some(32),
},
config: ModelConfig::default(),
},
);
self.register_model(
"claude-3-sonnet-20240229",
ModelSpec {
model_info: ModelInfo {
id: "claude-3-sonnet-20240229".to_string(),
name: "Claude 3 Sonnet".to_string(),
provider: "anthropic".to_string(),
max_context_length: 200_000,
max_output_length: Some(4_096),
supports_streaming: true,
supports_tools: true,
supports_multimodal: true,
input_cost_per_1k_tokens: Some(0.003),
output_cost_per_1k_tokens: Some(0.015),
currency: "USD".to_string(),
capabilities: vec![
crate::core::types::model::ProviderCapability::ChatCompletion,
crate::core::types::model::ProviderCapability::ChatCompletionStream,
crate::core::types::model::ProviderCapability::ToolCalling,
],
created_at: None,
updated_at: None,
metadata: std::collections::HashMap::new(),
},
family: AnthropicModelFamily::Claude3Sonnet,
features: vec![
ModelFeature::MultimodalSupport,
ModelFeature::ToolCalling,
ModelFeature::FunctionCalling,
ModelFeature::StreamingSupport,
ModelFeature::CacheControl,
ModelFeature::SystemMessages,
ModelFeature::BatchProcessing,
],
pricing: ModelPricing {
input_price: 3.0,
output_price: 15.0,
cache_write_price: Some(3.75),
cache_read_price: Some(0.30),
batch_discount: Some(0.5),
},
limits: ModelLimits {
max_context_length: 200_000,
max_output_tokens: 4_096,
max_images: Some(20),
max_document_size_mb: Some(32),
},
config: ModelConfig::default(),
},
);
self.register_model(
"claude-3-haiku-20240307",
ModelSpec {
model_info: ModelInfo {
id: "claude-3-haiku-20240307".to_string(),
name: "Claude 3 Haiku".to_string(),
provider: "anthropic".to_string(),
max_context_length: 200_000,
max_output_length: Some(4_096),
supports_streaming: true,
supports_tools: true,
supports_multimodal: true,
input_cost_per_1k_tokens: Some(0.00025),
output_cost_per_1k_tokens: Some(0.00125),
currency: "USD".to_string(),
capabilities: vec![
crate::core::types::model::ProviderCapability::ChatCompletion,
crate::core::types::model::ProviderCapability::ChatCompletionStream,
crate::core::types::model::ProviderCapability::ToolCalling,
],
created_at: None,
updated_at: None,
metadata: std::collections::HashMap::new(),
},
family: AnthropicModelFamily::Claude3Haiku,
features: vec![
ModelFeature::MultimodalSupport,
ModelFeature::ToolCalling,
ModelFeature::FunctionCalling,
ModelFeature::StreamingSupport,
ModelFeature::CacheControl,
ModelFeature::SystemMessages,
ModelFeature::BatchProcessing,
],
pricing: ModelPricing {
input_price: 0.25,
output_price: 1.25,
cache_write_price: Some(0.30),
cache_read_price: Some(0.03),
batch_discount: Some(0.5),
},
limits: ModelLimits {
max_context_length: 200_000,
max_output_tokens: 4_096,
max_images: Some(20),
max_document_size_mb: Some(32),
},
config: ModelConfig::default(),
},
);
self.register_model(
"claude-2.1",
ModelSpec {
model_info: ModelInfo {
id: "claude-2.1".to_string(),
name: "Claude 2.1".to_string(),
provider: "anthropic".to_string(),
max_context_length: 200_000,
max_output_length: Some(4_096),
supports_streaming: true,
supports_tools: false,
supports_multimodal: false,
input_cost_per_1k_tokens: Some(0.008),
output_cost_per_1k_tokens: Some(0.024),
currency: "USD".to_string(),
capabilities: vec![
crate::core::types::model::ProviderCapability::ChatCompletion,
crate::core::types::model::ProviderCapability::ChatCompletionStream,
],
created_at: None,
updated_at: None,
metadata: std::collections::HashMap::new(),
},
family: AnthropicModelFamily::Claude21,
features: vec![ModelFeature::StreamingSupport, ModelFeature::SystemMessages],
pricing: ModelPricing {
input_price: 8.0,
output_price: 24.0,
cache_write_price: None,
cache_read_price: None,
batch_discount: None,
},
limits: ModelLimits {
max_context_length: 200_000,
max_output_tokens: 4_096,
max_images: None,
max_document_size_mb: None,
},
config: ModelConfig::default(),
},
);
self.register_model(
"claude-instant-1.2",
ModelSpec {
model_info: ModelInfo {
id: "claude-instant-1.2".to_string(),
name: "Claude Instant 1.2".to_string(),
provider: "anthropic".to_string(),
max_context_length: 100_000,
max_output_length: Some(4_096),
supports_streaming: true,
supports_tools: false,
supports_multimodal: false,
input_cost_per_1k_tokens: Some(0.0008),
output_cost_per_1k_tokens: Some(0.0024),
currency: "USD".to_string(),
capabilities: vec![
crate::core::types::model::ProviderCapability::ChatCompletion,
crate::core::types::model::ProviderCapability::ChatCompletionStream,
],
created_at: None,
updated_at: None,
metadata: std::collections::HashMap::new(),
},
family: AnthropicModelFamily::ClaudeInstant,
features: vec![ModelFeature::StreamingSupport, ModelFeature::SystemMessages],
pricing: ModelPricing {
input_price: 0.80,
output_price: 2.40,
cache_write_price: None,
cache_read_price: None,
batch_discount: None,
},
limits: ModelLimits {
max_context_length: 100_000,
max_output_tokens: 4_096,
max_images: None,
max_document_size_mb: None,
},
config: ModelConfig::default(),
},
);
self.register_alias("claude-opus-4-6-20260114", "claude-opus-4-6");
self.register_alias("claude-opus-4-5", "claude-opus-4-5-20251101");
self.register_alias("claude-opus-4-5-20251110", "claude-opus-4-5-20251101");
self.register_alias("claude-sonnet-4-6", "claude-sonnet-4-6-20251001");
self.register_alias("claude-haiku-4-5", "claude-haiku-4-5-20251001");
self.register_alias("claude-sonnet-4-5", "claude-sonnet-4-5-20250929");
self.register_alias("claude-sonnet-4-5-20251101", "claude-sonnet-4-5-20250929");
self.register_alias("claude-sonnet-4", "claude-sonnet-4-20251022");
self.register_alias("claude-sonnet-4-20250514", "claude-sonnet-4-20251022");
self.register_alias("claude-3-5-sonnet", "claude-3-5-sonnet-20241022");
self.register_alias("claude-3.5-sonnet", "claude-3-5-sonnet-20241022");
self.register_alias("claude-3-5-haiku", "claude-3-5-haiku-20241022");
self.register_alias("claude-3.5-haiku", "claude-3-5-haiku-20241022");
self.register_alias("claude-3-opus", "claude-3-opus-20240229");
self.register_alias("claude-3-sonnet", "claude-3-sonnet-20240229");
self.register_alias("claude-3-haiku", "claude-3-haiku-20240307");
}
fn register_model(&mut self, id: &str, spec: ModelSpec) {
self.models.insert(id.to_string(), spec);
}
fn register_alias(&mut self, alias: &str, target: &str) {
if let Some(spec) = self.models.get(target) {
let mut alias_spec = spec.clone();
alias_spec.model_info.id = alias.to_string();
self.models.insert(alias.to_string(), alias_spec);
}
}
pub fn get_model_spec(&self, model_id: &str) -> Option<&ModelSpec> {
self.models.get(model_id)
}
pub fn list_models(&self) -> Vec<&ModelSpec> {
self.models.values().collect()
}
pub fn supports_feature(&self, model_id: &str, feature: &ModelFeature) -> bool {
self.get_model_spec(model_id)
.map(|spec| spec.features.contains(feature))
.unwrap_or(false)
}
pub fn get_model_family(&self, model_id: &str) -> Option<&AnthropicModelFamily> {
self.get_model_spec(model_id).map(|spec| &spec.family)
}
pub fn get_model_pricing(&self, model_id: &str) -> Option<&ModelPricing> {
self.get_model_spec(model_id).map(|spec| &spec.pricing)
}
pub fn get_model_limits(&self, model_id: &str) -> Option<&ModelLimits> {
self.get_model_spec(model_id).map(|spec| &spec.limits)
}
pub fn from_model_name(model_name: &str) -> Option<AnthropicModelFamily> {
let model_lower = model_name.to_lowercase();
if model_lower.contains("claude-opus-4-6") || model_lower.contains("claude-opus-4.6") {
Some(AnthropicModelFamily::ClaudeOpus46)
} else if model_lower.contains("claude-opus-4-5") || model_lower.contains("claude-opus-4.5")
{
Some(AnthropicModelFamily::ClaudeOpus45)
} else if model_lower.contains("claude-sonnet-4-6")
|| model_lower.contains("claude-sonnet-4.6")
{
Some(AnthropicModelFamily::ClaudeSonnet46)
} else if model_lower.contains("claude-haiku-4-5")
|| model_lower.contains("claude-haiku-4.5")
{
Some(AnthropicModelFamily::ClaudeHaiku45)
} else if model_lower.contains("claude-sonnet-4-5")
|| model_lower.contains("claude-sonnet-4.5")
{
Some(AnthropicModelFamily::ClaudeSonnet45)
} else if model_lower.contains("claude-sonnet-4")
&& !model_lower.contains("claude-sonnet-4-5")
&& !model_lower.contains("claude-sonnet-4-6")
{
Some(AnthropicModelFamily::ClaudeSonnet4)
} else if model_lower.contains("claude-3-5-sonnet")
|| model_lower.contains("claude-3.5-sonnet")
{
Some(AnthropicModelFamily::Claude35Sonnet)
} else if model_lower.contains("claude-3-5-haiku")
|| model_lower.contains("claude-3.5-haiku")
{
Some(AnthropicModelFamily::Claude3Haiku)
} else if model_lower.contains("claude-3-opus") {
Some(AnthropicModelFamily::Claude3Opus)
} else if model_lower.contains("claude-3-sonnet") {
Some(AnthropicModelFamily::Claude3Sonnet)
} else if model_lower.contains("claude-3-haiku") {
Some(AnthropicModelFamily::Claude3Haiku)
} else if model_lower.contains("claude-2.1") {
Some(AnthropicModelFamily::Claude21)
} else if model_lower.contains("claude-2") {
Some(AnthropicModelFamily::Claude2)
} else if model_lower.contains("claude-instant") {
Some(AnthropicModelFamily::ClaudeInstant)
} else {
None
}
}
}
impl Default for AnthropicModelRegistry {
fn default() -> Self {
Self::new()
}
}
pub fn get_anthropic_registry() -> &'static AnthropicModelRegistry {
static REGISTRY: OnceLock<AnthropicModelRegistry> = OnceLock::new();
REGISTRY.get_or_init(AnthropicModelRegistry::new)
}
pub struct CostCalculator;
impl CostCalculator {
pub fn calculate_cost(
model_id: &str,
prompt_tokens: u32,
completion_tokens: u32,
) -> Option<f64> {
let registry = get_anthropic_registry();
let pricing = registry.get_model_pricing(model_id)?;
let input_cost = (prompt_tokens as f64 / 1_000_000.0) * pricing.input_price;
let output_cost = (completion_tokens as f64 / 1_000_000.0) * pricing.output_price;
Some(input_cost + output_cost)
}
pub fn calculate_extended_cost(
model_id: &str,
prompt_tokens: u32,
completion_tokens: u32,
cache_read_tokens: Option<u32>,
cache_write_tokens: Option<u32>,
is_batch: bool,
) -> Option<f64> {
let registry = get_anthropic_registry();
let pricing = registry.get_model_pricing(model_id)?;
let batch_multiplier = if is_batch {
pricing.batch_discount.unwrap_or(1.0)
} else {
1.0
};
let mut total_cost = 0.0;
let mut remaining_prompt_tokens = prompt_tokens;
if let (Some(cache_read), Some(cache_read_price)) =
(cache_read_tokens, pricing.cache_read_price)
{
let cache_cost = (cache_read as f64 / 1_000_000.0) * cache_read_price;
total_cost += cache_cost;
remaining_prompt_tokens = remaining_prompt_tokens.saturating_sub(cache_read);
}
if let (Some(cache_write), Some(cache_write_price)) =
(cache_write_tokens, pricing.cache_write_price)
{
let cache_write_cost =
(cache_write as f64 / 1_000_000.0) * cache_write_price * batch_multiplier;
total_cost += cache_write_cost;
remaining_prompt_tokens = remaining_prompt_tokens.saturating_sub(cache_write);
}
let input_cost =
(remaining_prompt_tokens as f64 / 1_000_000.0) * pricing.input_price * batch_multiplier;
total_cost += input_cost;
let output_cost =
(completion_tokens as f64 / 1_000_000.0) * pricing.output_price * batch_multiplier;
total_cost += output_cost;
Some(total_cost)
}
pub fn estimate_tokens(text: &str) -> u32 {
(text.len() as f32 / 4.0).ceil() as u32
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_model_registry() {
let registry = get_anthropic_registry();
let opus_spec = registry.get_model_spec("claude-opus-4-6").unwrap();
assert_eq!(opus_spec.family, AnthropicModelFamily::ClaudeOpus46);
assert!(
opus_spec
.features
.contains(&ModelFeature::MultimodalSupport)
);
assert!(opus_spec.features.contains(&ModelFeature::ComputerUse));
assert_eq!(opus_spec.pricing.input_price, 5.0);
assert_eq!(opus_spec.pricing.output_price, 25.0);
}
#[test]
fn test_model_family_detection() {
assert_eq!(
AnthropicModelRegistry::from_model_name("claude-opus-4-6"),
Some(AnthropicModelFamily::ClaudeOpus46)
);
assert_eq!(
AnthropicModelRegistry::from_model_name("claude-3-5-sonnet-20241022"),
Some(AnthropicModelFamily::Claude35Sonnet)
);
assert_eq!(
AnthropicModelRegistry::from_model_name("claude-3-opus-20240229"),
Some(AnthropicModelFamily::Claude3Opus)
);
assert_eq!(
AnthropicModelRegistry::from_model_name("unknown-model"),
None
);
}
#[test]
fn test_cost_calculation() {
let cost = CostCalculator::calculate_cost("claude-opus-4-6", 1000, 500);
assert!(cost.is_some());
let cost_value = cost.unwrap();
assert!((cost_value - 0.0175).abs() < 0.0001);
}
#[test]
fn test_feature_support() {
let registry = get_anthropic_registry();
assert!(registry.supports_feature("claude-opus-4-6", &ModelFeature::ComputerUse));
assert!(!registry.supports_feature("claude-2.1", &ModelFeature::ComputerUse));
}
}