models.rs

   1use serde::{Deserialize, Serialize};
   2use strum::EnumIter;
   3
   4#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
   5#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
   6pub enum BedrockModelMode {
   7    #[default]
   8    Default,
   9    Thinking {
  10        budget_tokens: Option<u64>,
  11    },
  12}
  13
  14#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
  15#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
  16pub struct BedrockModelCacheConfiguration {
  17    pub max_cache_anchors: usize,
  18    pub min_total_token: u64,
  19}
  20
  21#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
  22#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
  23pub enum Model {
  24    // Anthropic models (already included)
  25    #[serde(rename = "claude-sonnet-4", alias = "claude-sonnet-4-latest")]
  26    ClaudeSonnet4,
  27    #[serde(
  28        rename = "claude-sonnet-4-thinking",
  29        alias = "claude-sonnet-4-thinking-latest"
  30    )]
  31    ClaudeSonnet4Thinking,
  32    #[default]
  33    #[serde(rename = "claude-sonnet-4-5", alias = "claude-sonnet-4-5-latest")]
  34    ClaudeSonnet4_5,
  35    #[serde(
  36        rename = "claude-sonnet-4-5-thinking",
  37        alias = "claude-sonnet-4-5-thinking-latest"
  38    )]
  39    ClaudeSonnet4_5Thinking,
  40    #[serde(rename = "claude-opus-4", alias = "claude-opus-4-latest")]
  41    ClaudeOpus4,
  42    #[serde(rename = "claude-opus-4-1", alias = "claude-opus-4-1-latest")]
  43    ClaudeOpus4_1,
  44    #[serde(
  45        rename = "claude-opus-4-thinking",
  46        alias = "claude-opus-4-thinking-latest"
  47    )]
  48    ClaudeOpus4Thinking,
  49    #[serde(
  50        rename = "claude-opus-4-1-thinking",
  51        alias = "claude-opus-4-1-thinking-latest"
  52    )]
  53    ClaudeOpus4_1Thinking,
  54    #[serde(rename = "claude-opus-4-5", alias = "claude-opus-4-5-latest")]
  55    ClaudeOpus4_5,
  56    #[serde(
  57        rename = "claude-opus-4-5-thinking",
  58        alias = "claude-opus-4-5-thinking-latest"
  59    )]
  60    ClaudeOpus4_5Thinking,
  61    #[serde(rename = "claude-sonnet-4-6", alias = "claude-sonnet-4-6-latest")]
  62    ClaudeSonnet4_6,
  63    #[serde(
  64        rename = "claude-sonnet-4-6-thinking",
  65        alias = "claude-sonnet-4-6-thinking-latest"
  66    )]
  67    ClaudeSonnet4_6Thinking,
  68    #[serde(rename = "claude-3-5-sonnet-v2", alias = "claude-3-5-sonnet-latest")]
  69    Claude3_5SonnetV2,
  70    #[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
  71    Claude3_7Sonnet,
  72    #[serde(
  73        rename = "claude-3-7-sonnet-thinking",
  74        alias = "claude-3-7-sonnet-thinking-latest"
  75    )]
  76    Claude3_7SonnetThinking,
  77    #[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
  78    Claude3Opus,
  79    #[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-latest")]
  80    Claude3Sonnet,
  81    #[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
  82    Claude3_5Haiku,
  83    #[serde(rename = "claude-haiku-4-5", alias = "claude-haiku-4-5-latest")]
  84    ClaudeHaiku4_5,
  85    Claude3_5Sonnet,
  86    Claude3Haiku,
  87    // Amazon Nova Models
  88    AmazonNovaLite,
  89    AmazonNovaMicro,
  90    AmazonNovaPro,
  91    AmazonNovaPremier,
  92    // AI21 models
  93    AI21J2GrandeInstruct,
  94    AI21J2JumboInstruct,
  95    AI21J2Mid,
  96    AI21J2MidV1,
  97    AI21J2Ultra,
  98    AI21J2UltraV1_8k,
  99    AI21J2UltraV1,
 100    AI21JambaInstructV1,
 101    AI21Jamba15LargeV1,
 102    AI21Jamba15MiniV1,
 103    // Cohere models
 104    CohereCommandTextV14_4k,
 105    CohereCommandRV1,
 106    CohereCommandRPlusV1,
 107    CohereCommandLightTextV14_4k,
 108    // DeepSeek
 109    DeepSeekR1,
 110    // Meta models
 111    MetaLlama38BInstructV1,
 112    MetaLlama370BInstructV1,
 113    MetaLlama318BInstructV1_128k,
 114    MetaLlama318BInstructV1,
 115    MetaLlama3170BInstructV1_128k,
 116    MetaLlama3170BInstructV1,
 117    MetaLlama31405BInstructV1,
 118    MetaLlama321BInstructV1,
 119    MetaLlama323BInstructV1,
 120    MetaLlama3211BInstructV1,
 121    MetaLlama3290BInstructV1,
 122    MetaLlama3370BInstructV1,
 123    #[allow(non_camel_case_types)]
 124    MetaLlama4Scout17BInstructV1,
 125    #[allow(non_camel_case_types)]
 126    MetaLlama4Maverick17BInstructV1,
 127    // Mistral models
 128    MistralMistral7BInstructV0,
 129    MistralMixtral8x7BInstructV0,
 130    MistralMistralLarge2402V1,
 131    MistralMistralSmall2402V1,
 132    MistralPixtralLarge2502V1,
 133    // Writer models
 134    PalmyraWriterX5,
 135    PalmyraWriterX4,
 136    #[serde(rename = "custom")]
 137    Custom {
 138        name: String,
 139        max_tokens: u64,
 140        /// The name displayed in the UI, such as in the assistant panel model dropdown menu.
 141        display_name: Option<String>,
 142        max_output_tokens: Option<u64>,
 143        default_temperature: Option<f32>,
 144        cache_configuration: Option<BedrockModelCacheConfiguration>,
 145    },
 146}
 147
 148impl Model {
 149    pub fn default_fast(region: &str) -> Self {
 150        if region.starts_with("us-") {
 151            Self::Claude3_5Haiku
 152        } else {
 153            Self::Claude3Haiku
 154        }
 155    }
 156
 157    pub fn from_id(id: &str) -> anyhow::Result<Self> {
 158        if id.starts_with("claude-opus-4-5-thinking") {
 159            Ok(Self::ClaudeOpus4_5Thinking)
 160        } else if id.starts_with("claude-opus-4-5") {
 161            Ok(Self::ClaudeOpus4_5)
 162        } else if id.starts_with("claude-opus-4-1-thinking") {
 163            Ok(Self::ClaudeOpus4_1Thinking)
 164        } else if id.starts_with("claude-opus-4-1") {
 165            Ok(Self::ClaudeOpus4_1)
 166        } else if id.starts_with("claude-opus-4-thinking") {
 167            Ok(Self::ClaudeOpus4Thinking)
 168        } else if id.starts_with("claude-opus-4") {
 169            Ok(Self::ClaudeOpus4)
 170        } else if id.starts_with("claude-3-5-sonnet-v2") {
 171            Ok(Self::Claude3_5SonnetV2)
 172        } else if id.starts_with("claude-3-opus") {
 173            Ok(Self::Claude3Opus)
 174        } else if id.starts_with("claude-3-sonnet") {
 175            Ok(Self::Claude3Sonnet)
 176        } else if id.starts_with("claude-3-5-haiku") {
 177            Ok(Self::Claude3_5Haiku)
 178        } else if id.starts_with("claude-haiku-4-5") {
 179            Ok(Self::ClaudeHaiku4_5)
 180        } else if id.starts_with("claude-3-7-sonnet") {
 181            Ok(Self::Claude3_7Sonnet)
 182        } else if id.starts_with("claude-3-7-sonnet-thinking") {
 183            Ok(Self::Claude3_7SonnetThinking)
 184        } else if id.starts_with("claude-sonnet-4-6-thinking") {
 185            Ok(Self::ClaudeSonnet4_6Thinking)
 186        } else if id.starts_with("claude-sonnet-4-6") {
 187            Ok(Self::ClaudeSonnet4_6)
 188        } else if id.starts_with("claude-sonnet-4-5-thinking") {
 189            Ok(Self::ClaudeSonnet4_5Thinking)
 190        } else if id.starts_with("claude-sonnet-4-5") {
 191            Ok(Self::ClaudeSonnet4_5)
 192        } else if id.starts_with("claude-sonnet-4-thinking") {
 193            Ok(Self::ClaudeSonnet4Thinking)
 194        } else if id.starts_with("claude-sonnet-4") {
 195            Ok(Self::ClaudeSonnet4)
 196        } else {
 197            anyhow::bail!("invalid model id {id}");
 198        }
 199    }
 200
 201    pub fn id(&self) -> &str {
 202        match self {
 203            Model::ClaudeSonnet4 => "claude-sonnet-4",
 204            Model::ClaudeSonnet4Thinking => "claude-sonnet-4-thinking",
 205            Model::ClaudeSonnet4_5 => "claude-sonnet-4-5",
 206            Model::ClaudeSonnet4_5Thinking => "claude-sonnet-4-5-thinking",
 207            Model::ClaudeSonnet4_6 => "claude-sonnet-4-6",
 208            Model::ClaudeSonnet4_6Thinking => "claude-sonnet-4-6-thinking",
 209            Model::ClaudeOpus4 => "claude-opus-4",
 210            Model::ClaudeOpus4_1 => "claude-opus-4-1",
 211            Model::ClaudeOpus4Thinking => "claude-opus-4-thinking",
 212            Model::ClaudeOpus4_1Thinking => "claude-opus-4-1-thinking",
 213            Model::ClaudeOpus4_5 => "claude-opus-4-5",
 214            Model::ClaudeOpus4_5Thinking => "claude-opus-4-5-thinking",
 215            Model::Claude3_5SonnetV2 => "claude-3-5-sonnet-v2",
 216            Model::Claude3_5Sonnet => "claude-3-5-sonnet",
 217            Model::Claude3Opus => "claude-3-opus",
 218            Model::Claude3Sonnet => "claude-3-sonnet",
 219            Model::Claude3Haiku => "claude-3-haiku",
 220            Model::Claude3_5Haiku => "claude-3-5-haiku",
 221            Model::ClaudeHaiku4_5 => "claude-haiku-4-5",
 222            Model::Claude3_7Sonnet => "claude-3-7-sonnet",
 223            Model::Claude3_7SonnetThinking => "claude-3-7-sonnet-thinking",
 224            Model::AmazonNovaLite => "amazon-nova-lite",
 225            Model::AmazonNovaMicro => "amazon-nova-micro",
 226            Model::AmazonNovaPro => "amazon-nova-pro",
 227            Model::AmazonNovaPremier => "amazon-nova-premier",
 228            Model::DeepSeekR1 => "deepseek-r1",
 229            Model::AI21J2GrandeInstruct => "ai21-j2-grande-instruct",
 230            Model::AI21J2JumboInstruct => "ai21-j2-jumbo-instruct",
 231            Model::AI21J2Mid => "ai21-j2-mid",
 232            Model::AI21J2MidV1 => "ai21-j2-mid-v1",
 233            Model::AI21J2Ultra => "ai21-j2-ultra",
 234            Model::AI21J2UltraV1_8k => "ai21-j2-ultra-v1-8k",
 235            Model::AI21J2UltraV1 => "ai21-j2-ultra-v1",
 236            Model::AI21JambaInstructV1 => "ai21-jamba-instruct-v1",
 237            Model::AI21Jamba15LargeV1 => "ai21-jamba-1-5-large-v1",
 238            Model::AI21Jamba15MiniV1 => "ai21-jamba-1-5-mini-v1",
 239            Model::CohereCommandTextV14_4k => "cohere-command-text-v14-4k",
 240            Model::CohereCommandRV1 => "cohere-command-r-v1",
 241            Model::CohereCommandRPlusV1 => "cohere-command-r-plus-v1",
 242            Model::CohereCommandLightTextV14_4k => "cohere-command-light-text-v14-4k",
 243            Model::MetaLlama38BInstructV1 => "meta-llama3-8b-instruct-v1",
 244            Model::MetaLlama370BInstructV1 => "meta-llama3-70b-instruct-v1",
 245            Model::MetaLlama318BInstructV1_128k => "meta-llama3-1-8b-instruct-v1-128k",
 246            Model::MetaLlama318BInstructV1 => "meta-llama3-1-8b-instruct-v1",
 247            Model::MetaLlama3170BInstructV1_128k => "meta-llama3-1-70b-instruct-v1-128k",
 248            Model::MetaLlama3170BInstructV1 => "meta-llama3-1-70b-instruct-v1",
 249            Model::MetaLlama31405BInstructV1 => "meta-llama3-1-405b-instruct-v1",
 250            Model::MetaLlama321BInstructV1 => "meta-llama3-2-1b-instruct-v1",
 251            Model::MetaLlama323BInstructV1 => "meta-llama3-2-3b-instruct-v1",
 252            Model::MetaLlama3211BInstructV1 => "meta-llama3-2-11b-instruct-v1",
 253            Model::MetaLlama3290BInstructV1 => "meta-llama3-2-90b-instruct-v1",
 254            Model::MetaLlama3370BInstructV1 => "meta-llama3-3-70b-instruct-v1",
 255            Model::MetaLlama4Scout17BInstructV1 => "meta-llama4-scout-17b-instruct-v1",
 256            Model::MetaLlama4Maverick17BInstructV1 => "meta-llama4-maverick-17b-instruct-v1",
 257            Model::MistralMistral7BInstructV0 => "mistral-7b-instruct-v0",
 258            Model::MistralMixtral8x7BInstructV0 => "mistral-mixtral-8x7b-instruct-v0",
 259            Model::MistralMistralLarge2402V1 => "mistral-large-2402-v1",
 260            Model::MistralMistralSmall2402V1 => "mistral-small-2402-v1",
 261            Model::MistralPixtralLarge2502V1 => "mistral-pixtral-large-2502-v1",
 262            Model::PalmyraWriterX4 => "palmyra-writer-x4",
 263            Model::PalmyraWriterX5 => "palmyra-writer-x5",
 264            Self::Custom { name, .. } => name,
 265        }
 266    }
 267
 268    pub fn request_id(&self) -> &str {
 269        match self {
 270            Model::ClaudeSonnet4 | Model::ClaudeSonnet4Thinking => {
 271                "anthropic.claude-sonnet-4-20250514-v1:0"
 272            }
 273            Model::ClaudeSonnet4_5 | Model::ClaudeSonnet4_5Thinking => {
 274                "anthropic.claude-sonnet-4-5-20250929-v1:0"
 275            }
 276            Model::ClaudeSonnet4_6 | Model::ClaudeSonnet4_6Thinking => {
 277                "anthropic.claude-sonnet-4-6"
 278            }
 279            Model::ClaudeOpus4 | Model::ClaudeOpus4Thinking => {
 280                "anthropic.claude-opus-4-20250514-v1:0"
 281            }
 282            Model::ClaudeOpus4_1 | Model::ClaudeOpus4_1Thinking => {
 283                "anthropic.claude-opus-4-1-20250805-v1:0"
 284            }
 285            Model::ClaudeOpus4_5 | Model::ClaudeOpus4_5Thinking => {
 286                "anthropic.claude-opus-4-5-20251101-v1:0"
 287            }
 288            Model::Claude3_5SonnetV2 => "anthropic.claude-3-5-sonnet-20241022-v2:0",
 289            Model::Claude3_5Sonnet => "anthropic.claude-3-5-sonnet-20240620-v1:0",
 290            Model::Claude3Opus => "anthropic.claude-3-opus-20240229-v1:0",
 291            Model::Claude3Sonnet => "anthropic.claude-3-sonnet-20240229-v1:0",
 292            Model::Claude3Haiku => "anthropic.claude-3-haiku-20240307-v1:0",
 293            Model::Claude3_5Haiku => "anthropic.claude-3-5-haiku-20241022-v1:0",
 294            Model::ClaudeHaiku4_5 => "anthropic.claude-haiku-4-5-20251001-v1:0",
 295            Model::Claude3_7Sonnet | Model::Claude3_7SonnetThinking => {
 296                "anthropic.claude-3-7-sonnet-20250219-v1:0"
 297            }
 298            Model::AmazonNovaLite => "amazon.nova-lite-v1:0",
 299            Model::AmazonNovaMicro => "amazon.nova-micro-v1:0",
 300            Model::AmazonNovaPro => "amazon.nova-pro-v1:0",
 301            Model::AmazonNovaPremier => "amazon.nova-premier-v1:0",
 302            Model::DeepSeekR1 => "deepseek.r1-v1:0",
 303            Model::AI21J2GrandeInstruct => "ai21.j2-grande-instruct",
 304            Model::AI21J2JumboInstruct => "ai21.j2-jumbo-instruct",
 305            Model::AI21J2Mid => "ai21.j2-mid",
 306            Model::AI21J2MidV1 => "ai21.j2-mid-v1",
 307            Model::AI21J2Ultra => "ai21.j2-ultra",
 308            Model::AI21J2UltraV1_8k => "ai21.j2-ultra-v1:0:8k",
 309            Model::AI21J2UltraV1 => "ai21.j2-ultra-v1",
 310            Model::AI21JambaInstructV1 => "ai21.jamba-instruct-v1:0",
 311            Model::AI21Jamba15LargeV1 => "ai21.jamba-1-5-large-v1:0",
 312            Model::AI21Jamba15MiniV1 => "ai21.jamba-1-5-mini-v1:0",
 313            Model::CohereCommandTextV14_4k => "cohere.command-text-v14:7:4k",
 314            Model::CohereCommandRV1 => "cohere.command-r-v1:0",
 315            Model::CohereCommandRPlusV1 => "cohere.command-r-plus-v1:0",
 316            Model::CohereCommandLightTextV14_4k => "cohere.command-light-text-v14:7:4k",
 317            Model::MetaLlama38BInstructV1 => "meta.llama3-8b-instruct-v1:0",
 318            Model::MetaLlama370BInstructV1 => "meta.llama3-70b-instruct-v1:0",
 319            Model::MetaLlama318BInstructV1_128k => "meta.llama3-1-8b-instruct-v1:0",
 320            Model::MetaLlama318BInstructV1 => "meta.llama3-1-8b-instruct-v1:0",
 321            Model::MetaLlama3170BInstructV1_128k => "meta.llama3-1-70b-instruct-v1:0",
 322            Model::MetaLlama3170BInstructV1 => "meta.llama3-1-70b-instruct-v1:0",
 323            Model::MetaLlama31405BInstructV1 => "meta.llama3-1-405b-instruct-v1:0",
 324            Model::MetaLlama3211BInstructV1 => "meta.llama3-2-11b-instruct-v1:0",
 325            Model::MetaLlama3290BInstructV1 => "meta.llama3-2-90b-instruct-v1:0",
 326            Model::MetaLlama321BInstructV1 => "meta.llama3-2-1b-instruct-v1:0",
 327            Model::MetaLlama323BInstructV1 => "meta.llama3-2-3b-instruct-v1:0",
 328            Model::MetaLlama3370BInstructV1 => "meta.llama3-3-70b-instruct-v1:0",
 329            Model::MetaLlama4Scout17BInstructV1 => "meta.llama4-scout-17b-instruct-v1:0",
 330            Model::MetaLlama4Maverick17BInstructV1 => "meta.llama4-maverick-17b-instruct-v1:0",
 331            Model::MistralMistral7BInstructV0 => "mistral.mistral-7b-instruct-v0:2",
 332            Model::MistralMixtral8x7BInstructV0 => "mistral.mixtral-8x7b-instruct-v0:1",
 333            Model::MistralMistralLarge2402V1 => "mistral.mistral-large-2402-v1:0",
 334            Model::MistralMistralSmall2402V1 => "mistral.mistral-small-2402-v1:0",
 335            Model::MistralPixtralLarge2502V1 => "mistral.pixtral-large-2502-v1:0",
 336            Model::PalmyraWriterX4 => "writer.palmyra-x4-v1:0",
 337            Model::PalmyraWriterX5 => "writer.palmyra-x5-v1:0",
 338            Self::Custom { name, .. } => name,
 339        }
 340    }
 341
 342    pub fn display_name(&self) -> &str {
 343        match self {
 344            Self::ClaudeSonnet4 => "Claude Sonnet 4",
 345            Self::ClaudeSonnet4Thinking => "Claude Sonnet 4 Thinking",
 346            Self::ClaudeSonnet4_5 => "Claude Sonnet 4.5",
 347            Self::ClaudeSonnet4_5Thinking => "Claude Sonnet 4.5 Thinking",
 348            Self::ClaudeSonnet4_6 => "Claude Sonnet 4.6",
 349            Self::ClaudeSonnet4_6Thinking => "Claude Sonnet 4.6 Thinking",
 350            Self::ClaudeOpus4 => "Claude Opus 4",
 351            Self::ClaudeOpus4_1 => "Claude Opus 4.1",
 352            Self::ClaudeOpus4Thinking => "Claude Opus 4 Thinking",
 353            Self::ClaudeOpus4_1Thinking => "Claude Opus 4.1 Thinking",
 354            Self::ClaudeOpus4_5 => "Claude Opus 4.5",
 355            Self::ClaudeOpus4_5Thinking => "Claude Opus 4.5 Thinking",
 356            Self::Claude3_5SonnetV2 => "Claude 3.5 Sonnet v2",
 357            Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
 358            Self::Claude3Opus => "Claude 3 Opus",
 359            Self::Claude3Sonnet => "Claude 3 Sonnet",
 360            Self::Claude3Haiku => "Claude 3 Haiku",
 361            Self::Claude3_5Haiku => "Claude 3.5 Haiku",
 362            Self::ClaudeHaiku4_5 => "Claude Haiku 4.5",
 363            Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
 364            Self::Claude3_7SonnetThinking => "Claude 3.7 Sonnet Thinking",
 365            Self::AmazonNovaLite => "Amazon Nova Lite",
 366            Self::AmazonNovaMicro => "Amazon Nova Micro",
 367            Self::AmazonNovaPro => "Amazon Nova Pro",
 368            Self::AmazonNovaPremier => "Amazon Nova Premier",
 369            Self::DeepSeekR1 => "DeepSeek R1",
 370            Self::AI21J2GrandeInstruct => "AI21 Jurassic2 Grande Instruct",
 371            Self::AI21J2JumboInstruct => "AI21 Jurassic2 Jumbo Instruct",
 372            Self::AI21J2Mid => "AI21 Jurassic2 Mid",
 373            Self::AI21J2MidV1 => "AI21 Jurassic2 Mid V1",
 374            Self::AI21J2Ultra => "AI21 Jurassic2 Ultra",
 375            Self::AI21J2UltraV1_8k => "AI21 Jurassic2 Ultra V1 8K",
 376            Self::AI21J2UltraV1 => "AI21 Jurassic2 Ultra V1",
 377            Self::AI21JambaInstructV1 => "AI21 Jamba Instruct",
 378            Self::AI21Jamba15LargeV1 => "AI21 Jamba 1.5 Large",
 379            Self::AI21Jamba15MiniV1 => "AI21 Jamba 1.5 Mini",
 380            Self::CohereCommandTextV14_4k => "Cohere Command Text V14 4K",
 381            Self::CohereCommandRV1 => "Cohere Command R V1",
 382            Self::CohereCommandRPlusV1 => "Cohere Command R Plus V1",
 383            Self::CohereCommandLightTextV14_4k => "Cohere Command Light Text V14 4K",
 384            Self::MetaLlama38BInstructV1 => "Meta Llama 3 8B Instruct",
 385            Self::MetaLlama370BInstructV1 => "Meta Llama 3 70B Instruct",
 386            Self::MetaLlama318BInstructV1_128k => "Meta Llama 3.1 8B Instruct 128K",
 387            Self::MetaLlama318BInstructV1 => "Meta Llama 3.1 8B Instruct",
 388            Self::MetaLlama3170BInstructV1_128k => "Meta Llama 3.1 70B Instruct 128K",
 389            Self::MetaLlama3170BInstructV1 => "Meta Llama 3.1 70B Instruct",
 390            Self::MetaLlama31405BInstructV1 => "Meta Llama 3.1 405B Instruct",
 391            Self::MetaLlama3211BInstructV1 => "Meta Llama 3.2 11B Instruct",
 392            Self::MetaLlama3290BInstructV1 => "Meta Llama 3.2 90B Instruct",
 393            Self::MetaLlama321BInstructV1 => "Meta Llama 3.2 1B Instruct",
 394            Self::MetaLlama323BInstructV1 => "Meta Llama 3.2 3B Instruct",
 395            Self::MetaLlama3370BInstructV1 => "Meta Llama 3.3 70B Instruct",
 396            Self::MetaLlama4Scout17BInstructV1 => "Meta Llama 4 Scout 17B Instruct",
 397            Self::MetaLlama4Maverick17BInstructV1 => "Meta Llama 4 Maverick 17B Instruct",
 398            Self::MistralMistral7BInstructV0 => "Mistral 7B Instruct V0",
 399            Self::MistralMixtral8x7BInstructV0 => "Mistral Mixtral 8x7B Instruct V0",
 400            Self::MistralMistralLarge2402V1 => "Mistral Large 2402 V1",
 401            Self::MistralMistralSmall2402V1 => "Mistral Small 2402 V1",
 402            Self::MistralPixtralLarge2502V1 => "Pixtral Large 25.02 V1",
 403            Self::PalmyraWriterX5 => "Writer Palmyra X5",
 404            Self::PalmyraWriterX4 => "Writer Palmyra X4",
 405            Self::Custom {
 406                display_name, name, ..
 407            } => display_name.as_deref().unwrap_or(name),
 408        }
 409    }
 410
 411    pub fn max_token_count(&self) -> u64 {
 412        match self {
 413            Self::Claude3_5SonnetV2
 414            | Self::Claude3Opus
 415            | Self::Claude3Sonnet
 416            | Self::Claude3_5Haiku
 417            | Self::ClaudeHaiku4_5
 418            | Self::Claude3_7Sonnet
 419            | Self::ClaudeSonnet4
 420            | Self::ClaudeOpus4
 421            | Self::ClaudeOpus4_1
 422            | Self::ClaudeSonnet4Thinking
 423            | Self::ClaudeSonnet4_5
 424            | Self::ClaudeSonnet4_5Thinking
 425            | Self::ClaudeSonnet4_6
 426            | Self::ClaudeSonnet4_6Thinking
 427            | Self::ClaudeOpus4Thinking
 428            | Self::ClaudeOpus4_1Thinking
 429            | Self::ClaudeOpus4_5
 430            | Self::ClaudeOpus4_5Thinking => 200_000,
 431            Self::AmazonNovaPremier => 1_000_000,
 432            Self::PalmyraWriterX5 => 1_000_000,
 433            Self::PalmyraWriterX4 => 128_000,
 434            Self::Custom { max_tokens, .. } => *max_tokens,
 435            _ => 128_000,
 436        }
 437    }
 438
 439    pub fn max_output_tokens(&self) -> u64 {
 440        match self {
 441            Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3_5Haiku => 4_096,
 442            Self::Claude3_7Sonnet | Self::Claude3_7SonnetThinking => 128_000,
 443            Self::ClaudeSonnet4 | Self::ClaudeSonnet4Thinking => 64_000,
 444            Self::ClaudeSonnet4_5
 445            | Self::ClaudeSonnet4_5Thinking
 446            | Self::ClaudeSonnet4_6
 447            | Self::ClaudeSonnet4_6Thinking
 448            | Self::ClaudeHaiku4_5
 449            | Self::ClaudeOpus4_5
 450            | Self::ClaudeOpus4_5Thinking => 64_000,
 451            Self::ClaudeOpus4
 452            | Self::ClaudeOpus4Thinking
 453            | Self::ClaudeOpus4_1
 454            | Self::ClaudeOpus4_1Thinking => 32_000,
 455            Self::Claude3_5SonnetV2 | Self::PalmyraWriterX4 | Self::PalmyraWriterX5 => 8_192,
 456            Self::Custom {
 457                max_output_tokens, ..
 458            } => max_output_tokens.unwrap_or(4_096),
 459            _ => 4_096,
 460        }
 461    }
 462
 463    pub fn default_temperature(&self) -> f32 {
 464        match self {
 465            Self::Claude3_5SonnetV2
 466            | Self::Claude3Opus
 467            | Self::Claude3Sonnet
 468            | Self::Claude3_5Haiku
 469            | Self::ClaudeHaiku4_5
 470            | Self::Claude3_7Sonnet
 471            | Self::ClaudeOpus4
 472            | Self::ClaudeOpus4Thinking
 473            | Self::ClaudeOpus4_1
 474            | Self::ClaudeOpus4_1Thinking
 475            | Self::ClaudeOpus4_5
 476            | Self::ClaudeOpus4_5Thinking
 477            | Self::ClaudeSonnet4
 478            | Self::ClaudeSonnet4Thinking
 479            | Self::ClaudeSonnet4_5
 480            | Self::ClaudeSonnet4_5Thinking
 481            | Self::ClaudeSonnet4_6
 482            | Self::ClaudeSonnet4_6Thinking => 1.0,
 483            Self::Custom {
 484                default_temperature,
 485                ..
 486            } => default_temperature.unwrap_or(1.0),
 487            _ => 1.0,
 488        }
 489    }
 490
 491    pub fn supports_tool_use(&self) -> bool {
 492        match self {
 493            // Anthropic Claude 3 models (all support tool use)
 494            Self::Claude3Opus
 495            | Self::Claude3Sonnet
 496            | Self::Claude3_5Sonnet
 497            | Self::Claude3_5SonnetV2
 498            | Self::Claude3_7Sonnet
 499            | Self::Claude3_7SonnetThinking
 500            | Self::ClaudeOpus4
 501            | Self::ClaudeOpus4Thinking
 502            | Self::ClaudeOpus4_1
 503            | Self::ClaudeOpus4_1Thinking
 504            | Self::ClaudeOpus4_5
 505            | Self::ClaudeOpus4_5Thinking
 506            | Self::ClaudeSonnet4
 507            | Self::ClaudeSonnet4Thinking
 508            | Self::ClaudeSonnet4_5
 509            | Self::ClaudeSonnet4_5Thinking
 510            | Self::ClaudeSonnet4_6
 511            | Self::ClaudeSonnet4_6Thinking
 512            | Self::Claude3_5Haiku
 513            | Self::ClaudeHaiku4_5 => true,
 514
 515            // Amazon Nova models (all support tool use)
 516            Self::AmazonNovaPremier
 517            | Self::AmazonNovaPro
 518            | Self::AmazonNovaLite
 519            | Self::AmazonNovaMicro => true,
 520
 521            // AI21 Jamba 1.5 models support tool use
 522            Self::AI21Jamba15LargeV1 | Self::AI21Jamba15MiniV1 => true,
 523
 524            // Cohere Command R models support tool use
 525            Self::CohereCommandRV1 | Self::CohereCommandRPlusV1 => true,
 526
 527            // All other models don't support tool use
 528            // Including Meta Llama 3.2, AI21 Jurassic, and others
 529            _ => false,
 530        }
 531    }
 532
 533    pub fn supports_caching(&self) -> bool {
 534        match self {
 535            // Only Claude models on Bedrock support caching
 536            // Nova models support only text caching
 537            // https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html#prompt-caching-models
 538            Self::Claude3_5Haiku
 539            | Self::ClaudeHaiku4_5
 540            | Self::Claude3_7Sonnet
 541            | Self::Claude3_7SonnetThinking
 542            | Self::ClaudeSonnet4
 543            | Self::ClaudeSonnet4Thinking
 544            | Self::ClaudeSonnet4_5
 545            | Self::ClaudeSonnet4_5Thinking
 546            | Self::ClaudeSonnet4_6
 547            | Self::ClaudeSonnet4_6Thinking
 548            | Self::ClaudeOpus4
 549            | Self::ClaudeOpus4Thinking
 550            | Self::ClaudeOpus4_1
 551            | Self::ClaudeOpus4_1Thinking
 552            | Self::ClaudeOpus4_5
 553            | Self::ClaudeOpus4_5Thinking => true,
 554
 555            // Custom models - check if they have cache configuration
 556            Self::Custom {
 557                cache_configuration,
 558                ..
 559            } => cache_configuration.is_some(),
 560
 561            // All other models don't support caching
 562            _ => false,
 563        }
 564    }
 565
 566    pub fn cache_configuration(&self) -> Option<BedrockModelCacheConfiguration> {
 567        match self {
 568            Self::Claude3_7Sonnet
 569            | Self::Claude3_7SonnetThinking
 570            | Self::ClaudeSonnet4
 571            | Self::ClaudeSonnet4Thinking
 572            | Self::ClaudeSonnet4_6
 573            | Self::ClaudeSonnet4_6Thinking
 574            | Self::ClaudeOpus4
 575            | Self::ClaudeOpus4Thinking
 576            | Self::ClaudeOpus4_1
 577            | Self::ClaudeOpus4_1Thinking
 578            | Self::ClaudeOpus4_5
 579            | Self::ClaudeOpus4_5Thinking => Some(BedrockModelCacheConfiguration {
 580                max_cache_anchors: 4,
 581                min_total_token: 1024,
 582            }),
 583
 584            Self::Claude3_5Haiku | Self::ClaudeHaiku4_5 => Some(BedrockModelCacheConfiguration {
 585                max_cache_anchors: 4,
 586                min_total_token: 2048,
 587            }),
 588
 589            Self::Custom {
 590                cache_configuration,
 591                ..
 592            } => cache_configuration.clone(),
 593
 594            _ => None,
 595        }
 596    }
 597
 598    pub fn mode(&self) -> BedrockModelMode {
 599        match self {
 600            Model::Claude3_7SonnetThinking => BedrockModelMode::Thinking {
 601                budget_tokens: Some(4096),
 602            },
 603            Model::ClaudeSonnet4Thinking
 604            | Model::ClaudeSonnet4_5Thinking
 605            | Model::ClaudeSonnet4_6Thinking => BedrockModelMode::Thinking {
 606                budget_tokens: Some(4096),
 607            },
 608            Model::ClaudeOpus4Thinking
 609            | Model::ClaudeOpus4_1Thinking
 610            | Model::ClaudeOpus4_5Thinking => BedrockModelMode::Thinking {
 611                budget_tokens: Some(4096),
 612            },
 613            _ => BedrockModelMode::Default,
 614        }
 615    }
 616
 617    pub fn cross_region_inference_id(
 618        &self,
 619        region: &str,
 620        allow_global: bool,
 621    ) -> anyhow::Result<String> {
 622        // List derived from here:
 623        // https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html#inference-profiles-support-system
 624        let model_id = self.request_id();
 625
 626        let supports_global = matches!(
 627            self,
 628            Model::ClaudeOpus4_5
 629                | Model::ClaudeOpus4_5Thinking
 630                | Model::ClaudeHaiku4_5
 631                | Model::ClaudeSonnet4
 632                | Model::ClaudeSonnet4Thinking
 633                | Model::ClaudeSonnet4_5
 634                | Model::ClaudeSonnet4_5Thinking
 635                | Model::ClaudeSonnet4_6
 636                | Model::ClaudeSonnet4_6Thinking
 637        );
 638
 639        let region_group = if region.starts_with("us-gov-") {
 640            "us-gov"
 641        } else if region.starts_with("us-")
 642            || region.starts_with("ca-")
 643            || region.starts_with("sa-")
 644        {
 645            if allow_global && supports_global {
 646                "global"
 647            } else {
 648                "us"
 649            }
 650        } else if region.starts_with("eu-") {
 651            if allow_global && supports_global {
 652                "global"
 653            } else {
 654                "eu"
 655            }
 656        } else if region.starts_with("ap-") || region == "me-central-1" || region == "me-south-1" {
 657            if allow_global && supports_global {
 658                "global"
 659            } else {
 660                "apac"
 661            }
 662        } else {
 663            anyhow::bail!("Unsupported Region {region}");
 664        };
 665
 666        match (self, region_group, region) {
 667            (Model::Custom { .. }, _, _) => Ok(self.request_id().into()),
 668
 669            (
 670                Model::ClaudeOpus4_5
 671                | Model::ClaudeOpus4_5Thinking
 672                | Model::ClaudeHaiku4_5
 673                | Model::ClaudeSonnet4
 674                | Model::ClaudeSonnet4Thinking
 675                | Model::ClaudeSonnet4_5
 676                | Model::ClaudeSonnet4_5Thinking
 677                | Model::ClaudeSonnet4_6
 678                | Model::ClaudeSonnet4_6Thinking,
 679                "global",
 680                _,
 681            ) => Ok(format!("{}.{}", region_group, model_id)),
 682
 683            (
 684                Model::Claude3Haiku
 685                | Model::Claude3_5Sonnet
 686                | Model::Claude3_7Sonnet
 687                | Model::Claude3_7SonnetThinking
 688                | Model::ClaudeSonnet4_5
 689                | Model::ClaudeSonnet4_5Thinking
 690                | Model::ClaudeSonnet4_6
 691                | Model::ClaudeSonnet4_6Thinking,
 692                "us-gov",
 693                _,
 694            ) => Ok(format!("{}.{}", region_group, model_id)),
 695
 696            (
 697                Model::ClaudeHaiku4_5
 698                | Model::ClaudeSonnet4_5
 699                | Model::ClaudeSonnet4_5Thinking
 700                | Model::ClaudeSonnet4_6
 701                | Model::ClaudeSonnet4_6Thinking,
 702                "apac",
 703                "ap-southeast-2" | "ap-southeast-4",
 704            ) => Ok(format!("au.{}", model_id)),
 705
 706            (
 707                Model::ClaudeHaiku4_5
 708                | Model::ClaudeSonnet4_5
 709                | Model::ClaudeSonnet4_5Thinking
 710                | Model::ClaudeSonnet4_6
 711                | Model::ClaudeSonnet4_6Thinking,
 712                "apac",
 713                "ap-northeast-1" | "ap-northeast-3",
 714            ) => Ok(format!("jp.{}", model_id)),
 715
 716            (Model::AmazonNovaLite, "us", r) if r.starts_with("ca-") => {
 717                Ok(format!("ca.{}", model_id))
 718            }
 719
 720            (
 721                Model::AmazonNovaPremier
 722                | Model::AmazonNovaLite
 723                | Model::AmazonNovaMicro
 724                | Model::AmazonNovaPro
 725                | Model::Claude3_5Haiku
 726                | Model::ClaudeHaiku4_5
 727                | Model::Claude3_5Sonnet
 728                | Model::Claude3_5SonnetV2
 729                | Model::Claude3_7Sonnet
 730                | Model::Claude3_7SonnetThinking
 731                | Model::ClaudeSonnet4
 732                | Model::ClaudeSonnet4Thinking
 733                | Model::ClaudeSonnet4_5
 734                | Model::ClaudeSonnet4_5Thinking
 735                | Model::ClaudeSonnet4_6
 736                | Model::ClaudeSonnet4_6Thinking
 737                | Model::ClaudeOpus4
 738                | Model::ClaudeOpus4Thinking
 739                | Model::ClaudeOpus4_1
 740                | Model::ClaudeOpus4_1Thinking
 741                | Model::ClaudeOpus4_5
 742                | Model::ClaudeOpus4_5Thinking
 743                | Model::Claude3Haiku
 744                | Model::Claude3Opus
 745                | Model::Claude3Sonnet
 746                | Model::DeepSeekR1
 747                | Model::MetaLlama31405BInstructV1
 748                | Model::MetaLlama3170BInstructV1_128k
 749                | Model::MetaLlama3170BInstructV1
 750                | Model::MetaLlama318BInstructV1_128k
 751                | Model::MetaLlama318BInstructV1
 752                | Model::MetaLlama3211BInstructV1
 753                | Model::MetaLlama321BInstructV1
 754                | Model::MetaLlama323BInstructV1
 755                | Model::MetaLlama3290BInstructV1
 756                | Model::MetaLlama3370BInstructV1
 757                | Model::MetaLlama4Maverick17BInstructV1
 758                | Model::MetaLlama4Scout17BInstructV1
 759                | Model::MistralPixtralLarge2502V1
 760                | Model::PalmyraWriterX4
 761                | Model::PalmyraWriterX5,
 762                "us",
 763                _,
 764            ) => Ok(format!("{}.{}", region_group, model_id)),
 765
 766            (
 767                Model::AmazonNovaLite
 768                | Model::AmazonNovaMicro
 769                | Model::AmazonNovaPro
 770                | Model::Claude3_5Sonnet
 771                | Model::ClaudeHaiku4_5
 772                | Model::Claude3_7Sonnet
 773                | Model::Claude3_7SonnetThinking
 774                | Model::ClaudeSonnet4
 775                | Model::ClaudeSonnet4_5
 776                | Model::ClaudeSonnet4_5Thinking
 777                | Model::ClaudeSonnet4_6
 778                | Model::ClaudeSonnet4_6Thinking
 779                | Model::Claude3Haiku
 780                | Model::Claude3Sonnet
 781                | Model::MetaLlama321BInstructV1
 782                | Model::MetaLlama323BInstructV1
 783                | Model::MistralPixtralLarge2502V1,
 784                "eu",
 785                _,
 786            ) => Ok(format!("{}.{}", region_group, model_id)),
 787
 788            (
 789                Model::AmazonNovaLite
 790                | Model::AmazonNovaMicro
 791                | Model::AmazonNovaPro
 792                | Model::Claude3_5Sonnet
 793                | Model::Claude3_5SonnetV2
 794                | Model::ClaudeHaiku4_5
 795                | Model::Claude3_7Sonnet
 796                | Model::Claude3_7SonnetThinking
 797                | Model::ClaudeSonnet4
 798                | Model::ClaudeSonnet4_6
 799                | Model::Claude3Haiku
 800                | Model::Claude3Sonnet,
 801                "apac",
 802                _,
 803            ) => Ok(format!("{}.{}", region_group, model_id)),
 804
 805            _ => Ok(model_id.into()),
 806        }
 807    }
 808}
 809
 810#[cfg(test)]
 811mod tests {
 812    use super::*;
 813
 814    #[test]
 815    fn test_us_region_inference_ids() -> anyhow::Result<()> {
 816        // Test US regions
 817        assert_eq!(
 818            Model::Claude3_5SonnetV2.cross_region_inference_id("us-east-1", false)?,
 819            "us.anthropic.claude-3-5-sonnet-20241022-v2:0"
 820        );
 821        assert_eq!(
 822            Model::Claude3_5SonnetV2.cross_region_inference_id("us-west-2", false)?,
 823            "us.anthropic.claude-3-5-sonnet-20241022-v2:0"
 824        );
 825        assert_eq!(
 826            Model::AmazonNovaPro.cross_region_inference_id("us-east-2", false)?,
 827            "us.amazon.nova-pro-v1:0"
 828        );
 829        Ok(())
 830    }
 831
 832    #[test]
 833    fn test_eu_region_inference_ids() -> anyhow::Result<()> {
 834        // Test European regions
 835        assert_eq!(
 836            Model::ClaudeSonnet4.cross_region_inference_id("eu-west-1", false)?,
 837            "eu.anthropic.claude-sonnet-4-20250514-v1:0"
 838        );
 839        assert_eq!(
 840            Model::ClaudeSonnet4_5.cross_region_inference_id("eu-west-1", false)?,
 841            "eu.anthropic.claude-sonnet-4-5-20250929-v1:0"
 842        );
 843        assert_eq!(
 844            Model::Claude3Sonnet.cross_region_inference_id("eu-west-1", false)?,
 845            "eu.anthropic.claude-3-sonnet-20240229-v1:0"
 846        );
 847        assert_eq!(
 848            Model::AmazonNovaMicro.cross_region_inference_id("eu-north-1", false)?,
 849            "eu.amazon.nova-micro-v1:0"
 850        );
 851        Ok(())
 852    }
 853
 854    #[test]
 855    fn test_apac_region_inference_ids() -> anyhow::Result<()> {
 856        // Test Asia-Pacific regions
 857        assert_eq!(
 858            Model::Claude3_5SonnetV2.cross_region_inference_id("ap-northeast-1", false)?,
 859            "apac.anthropic.claude-3-5-sonnet-20241022-v2:0"
 860        );
 861        assert_eq!(
 862            Model::Claude3_5SonnetV2.cross_region_inference_id("ap-southeast-2", false)?,
 863            "apac.anthropic.claude-3-5-sonnet-20241022-v2:0"
 864        );
 865        assert_eq!(
 866            Model::AmazonNovaLite.cross_region_inference_id("ap-south-1", false)?,
 867            "apac.amazon.nova-lite-v1:0"
 868        );
 869        Ok(())
 870    }
 871
 872    #[test]
 873    fn test_gov_region_inference_ids() -> anyhow::Result<()> {
 874        // Test Government regions
 875        assert_eq!(
 876            Model::Claude3_5Sonnet.cross_region_inference_id("us-gov-east-1", false)?,
 877            "us-gov.anthropic.claude-3-5-sonnet-20240620-v1:0"
 878        );
 879        assert_eq!(
 880            Model::Claude3Haiku.cross_region_inference_id("us-gov-west-1", false)?,
 881            "us-gov.anthropic.claude-3-haiku-20240307-v1:0"
 882        );
 883        Ok(())
 884    }
 885
 886    #[test]
 887    fn test_meta_models_inference_ids() -> anyhow::Result<()> {
 888        // Test Meta models
 889        assert_eq!(
 890            Model::MetaLlama370BInstructV1.cross_region_inference_id("us-east-1", false)?,
 891            "meta.llama3-70b-instruct-v1:0"
 892        );
 893        assert_eq!(
 894            Model::MetaLlama3170BInstructV1.cross_region_inference_id("us-east-1", false)?,
 895            "us.meta.llama3-1-70b-instruct-v1:0"
 896        );
 897        assert_eq!(
 898            Model::MetaLlama321BInstructV1.cross_region_inference_id("eu-west-1", false)?,
 899            "eu.meta.llama3-2-1b-instruct-v1:0"
 900        );
 901        Ok(())
 902    }
 903
 904    #[test]
 905    fn test_mistral_models_inference_ids() -> anyhow::Result<()> {
 906        // Mistral models don't follow the regional prefix pattern,
 907        // so they should return their original IDs
 908        assert_eq!(
 909            Model::MistralMistralLarge2402V1.cross_region_inference_id("us-east-1", false)?,
 910            "mistral.mistral-large-2402-v1:0"
 911        );
 912        assert_eq!(
 913            Model::MistralMixtral8x7BInstructV0.cross_region_inference_id("eu-west-1", false)?,
 914            "mistral.mixtral-8x7b-instruct-v0:1"
 915        );
 916        Ok(())
 917    }
 918
 919    #[test]
 920    fn test_ai21_models_inference_ids() -> anyhow::Result<()> {
 921        // AI21 models don't follow the regional prefix pattern,
 922        // so they should return their original IDs
 923        assert_eq!(
 924            Model::AI21J2UltraV1.cross_region_inference_id("us-east-1", false)?,
 925            "ai21.j2-ultra-v1"
 926        );
 927        assert_eq!(
 928            Model::AI21JambaInstructV1.cross_region_inference_id("eu-west-1", false)?,
 929            "ai21.jamba-instruct-v1:0"
 930        );
 931        Ok(())
 932    }
 933
 934    #[test]
 935    fn test_cohere_models_inference_ids() -> anyhow::Result<()> {
 936        // Cohere models don't follow the regional prefix pattern,
 937        // so they should return their original IDs
 938        assert_eq!(
 939            Model::CohereCommandRV1.cross_region_inference_id("us-east-1", false)?,
 940            "cohere.command-r-v1:0"
 941        );
 942        assert_eq!(
 943            Model::CohereCommandTextV14_4k.cross_region_inference_id("ap-southeast-1", false)?,
 944            "cohere.command-text-v14:7:4k"
 945        );
 946        Ok(())
 947    }
 948
 949    #[test]
 950    fn test_custom_model_inference_ids() -> anyhow::Result<()> {
 951        // Test custom models
 952        let custom_model = Model::Custom {
 953            name: "custom.my-model-v1:0".to_string(),
 954            max_tokens: 100000,
 955            display_name: Some("My Custom Model".to_string()),
 956            max_output_tokens: Some(8192),
 957            default_temperature: Some(0.7),
 958            cache_configuration: None,
 959        };
 960
 961        // Custom model should return its name unchanged
 962        assert_eq!(
 963            custom_model.cross_region_inference_id("us-east-1", false)?,
 964            "custom.my-model-v1:0"
 965        );
 966
 967        // Test that models without global support fall back to regional when allow_global is true
 968        assert_eq!(
 969            Model::AmazonNovaPro.cross_region_inference_id("us-east-1", true)?,
 970            "us.amazon.nova-pro-v1:0",
 971            "Nova Pro should fall back to regional profile even when allow_global is true"
 972        );
 973
 974        Ok(())
 975    }
 976
 977    #[test]
 978    fn test_friendly_id_vs_request_id() {
 979        // Test that id() returns friendly identifiers
 980        assert_eq!(Model::Claude3_5SonnetV2.id(), "claude-3-5-sonnet-v2");
 981        assert_eq!(Model::AmazonNovaLite.id(), "amazon-nova-lite");
 982        assert_eq!(Model::DeepSeekR1.id(), "deepseek-r1");
 983        assert_eq!(
 984            Model::MetaLlama38BInstructV1.id(),
 985            "meta-llama3-8b-instruct-v1"
 986        );
 987
 988        // Test that request_id() returns actual backend model IDs
 989        assert_eq!(
 990            Model::Claude3_5SonnetV2.request_id(),
 991            "anthropic.claude-3-5-sonnet-20241022-v2:0"
 992        );
 993        assert_eq!(Model::AmazonNovaLite.request_id(), "amazon.nova-lite-v1:0");
 994        assert_eq!(Model::DeepSeekR1.request_id(), "deepseek.r1-v1:0");
 995        assert_eq!(
 996            Model::MetaLlama38BInstructV1.request_id(),
 997            "meta.llama3-8b-instruct-v1:0"
 998        );
 999
1000        // Test thinking models have different friendly IDs but same request IDs
1001        assert_eq!(Model::ClaudeSonnet4.id(), "claude-sonnet-4");
1002        assert_eq!(
1003            Model::ClaudeSonnet4Thinking.id(),
1004            "claude-sonnet-4-thinking"
1005        );
1006        assert_eq!(
1007            Model::ClaudeSonnet4.request_id(),
1008            Model::ClaudeSonnet4Thinking.request_id()
1009        );
1010    }
1011}
1012
1013#[test]
1014fn test_global_inference_ids() -> anyhow::Result<()> {
1015    // Test global inference for models that support it when allow_global is true
1016    assert_eq!(
1017        Model::ClaudeSonnet4.cross_region_inference_id("us-east-1", true)?,
1018        "global.anthropic.claude-sonnet-4-20250514-v1:0"
1019    );
1020    assert_eq!(
1021        Model::ClaudeSonnet4_5.cross_region_inference_id("eu-west-1", true)?,
1022        "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
1023    );
1024    assert_eq!(
1025        Model::ClaudeHaiku4_5.cross_region_inference_id("ap-south-1", true)?,
1026        "global.anthropic.claude-haiku-4-5-20251001-v1:0"
1027    );
1028
1029    // Test that regional prefix is used when allow_global is false
1030    assert_eq!(
1031        Model::ClaudeSonnet4.cross_region_inference_id("us-east-1", false)?,
1032        "us.anthropic.claude-sonnet-4-20250514-v1:0"
1033    );
1034
1035    Ok(())
1036}