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