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