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}