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