models.rs

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