Skip to repository content1538 lines · 56.5 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T03:57:08.714Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
models.rs
1use serde::{Deserialize, Serialize};
2use strum::EnumIter;
3
4#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq)]
6pub enum BedrockAdaptiveThinkingEffort {
7 Low,
8 Medium,
9 #[default]
10 High,
11 XHigh,
12 Max,
13}
14
15impl BedrockAdaptiveThinkingEffort {
16 pub fn as_str(&self) -> &'static str {
17 match self {
18 Self::Low => "low",
19 Self::Medium => "medium",
20 Self::High => "high",
21 Self::XHigh => "xhigh",
22 Self::Max => "max",
23 }
24 }
25}
26
27#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
28#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
29pub enum BedrockModelMode {
30 #[default]
31 Default,
32 Thinking {
33 budget_tokens: Option<u64>,
34 },
35 AdaptiveThinking {
36 effort: BedrockAdaptiveThinkingEffort,
37 },
38}
39
40#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
41#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
42pub struct BedrockModelCacheConfiguration {
43 pub max_cache_anchors: usize,
44 pub min_total_token: u64,
45}
46
47#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
48#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
49pub enum ConverseModel {
50 // Anthropic Claude 4+ models
51 #[serde(
52 rename = "claude-fable-5",
53 alias = "claude-fable-5-latest",
54 alias = "claude-fable-5-thinking",
55 alias = "claude-fable-5-thinking-latest"
56 )]
57 ClaudeFable5,
58 #[serde(
59 rename = "claude-opus-5",
60 alias = "claude-opus-5-latest",
61 alias = "claude-opus-5-thinking",
62 alias = "claude-opus-5-thinking-latest"
63 )]
64 ClaudeOpus5,
65 #[serde(
66 rename = "claude-opus-4-8",
67 alias = "claude-opus-4-8-latest",
68 alias = "claude-opus-4-8-thinking",
69 alias = "claude-opus-4-8-thinking-latest"
70 )]
71 ClaudeOpus4_8,
72 #[serde(
73 rename = "claude-opus-4-7",
74 alias = "claude-opus-4-7-latest",
75 alias = "claude-opus-4-7-thinking",
76 alias = "claude-opus-4-7-thinking-latest"
77 )]
78 ClaudeOpus4_7,
79 #[serde(
80 rename = "claude-opus-4-6",
81 alias = "claude-opus-4-6-latest",
82 alias = "claude-opus-4-6-thinking",
83 alias = "claude-opus-4-6-thinking-latest"
84 )]
85 ClaudeOpus4_6,
86 #[serde(
87 rename = "claude-opus-4-5",
88 alias = "claude-opus-4-5-latest",
89 alias = "claude-opus-4-5-thinking",
90 alias = "claude-opus-4-5-thinking-latest"
91 )]
92 ClaudeOpus4_5,
93 #[serde(
94 rename = "claude-opus-4-1",
95 alias = "claude-opus-4-1-latest",
96 alias = "claude-opus-4-1-thinking",
97 alias = "claude-opus-4-1-thinking-latest"
98 )]
99 ClaudeOpus4_1,
100 #[serde(
101 rename = "claude-sonnet-5",
102 alias = "claude-sonnet-5-latest",
103 alias = "claude-sonnet-5-thinking",
104 alias = "claude-sonnet-5-thinking-latest"
105 )]
106 ClaudeSonnet5,
107 #[serde(
108 rename = "claude-sonnet-4-6",
109 alias = "claude-sonnet-4-6-latest",
110 alias = "claude-sonnet-4-6-thinking",
111 alias = "claude-sonnet-4-6-thinking-latest"
112 )]
113 ClaudeSonnet4_6,
114 #[default]
115 #[serde(
116 rename = "claude-sonnet-4-5",
117 alias = "claude-sonnet-4-5-latest",
118 alias = "claude-sonnet-4-5-thinking",
119 alias = "claude-sonnet-4-5-thinking-latest"
120 )]
121 ClaudeSonnet4_5,
122 #[serde(
123 rename = "claude-sonnet-4",
124 alias = "claude-sonnet-4-latest",
125 alias = "claude-sonnet-4-thinking",
126 alias = "claude-sonnet-4-thinking-latest"
127 )]
128 ClaudeSonnet4,
129 #[serde(rename = "claude-haiku-4-5", alias = "claude-haiku-4-5-latest")]
130 ClaudeHaiku4_5,
131
132 // Meta Llama 4 models
133 #[serde(rename = "llama-4-scout-17b")]
134 Llama4Scout17B,
135 #[serde(rename = "llama-4-maverick-17b")]
136 Llama4Maverick17B,
137
138 // Google Gemma 3 models
139 #[serde(rename = "gemma-3-4b")]
140 Gemma3_4B,
141 #[serde(rename = "gemma-3-12b")]
142 Gemma3_12B,
143 #[serde(rename = "gemma-3-27b")]
144 Gemma3_27B,
145
146 // Mistral models
147 #[serde(rename = "magistral-small")]
148 MagistralSmall,
149 #[serde(rename = "mistral-large-3")]
150 MistralLarge3,
151 #[serde(rename = "pixtral-large")]
152 PixtralLarge,
153 #[serde(rename = "devstral-2-123b")]
154 Devstral2_123B,
155 #[serde(rename = "ministral-14b")]
156 Ministral14B,
157
158 // Qwen models
159 #[serde(rename = "qwen3-32b")]
160 Qwen3_32B,
161 #[serde(rename = "qwen3-vl-235b")]
162 Qwen3VL235B,
163 #[serde(rename = "qwen3-235b")]
164 Qwen3_235B,
165 #[serde(rename = "qwen3-next-80b")]
166 Qwen3Next80B,
167 #[serde(rename = "qwen3-coder-30b")]
168 Qwen3Coder30B,
169 #[serde(rename = "qwen3-coder-next")]
170 Qwen3CoderNext,
171 #[serde(rename = "qwen3-coder-480b")]
172 Qwen3Coder480B,
173
174 // Amazon Nova models
175 #[serde(rename = "nova-lite")]
176 NovaLite,
177 #[serde(rename = "nova-pro")]
178 NovaPro,
179 #[serde(rename = "nova-premier")]
180 NovaPremier,
181 #[serde(rename = "nova-2-lite")]
182 Nova2Lite,
183
184 // OpenAI GPT OSS models
185 #[serde(rename = "gpt-oss-20b")]
186 GptOss20B,
187 #[serde(rename = "gpt-oss-120b")]
188 GptOss120B,
189
190 // NVIDIA Nemotron models
191 #[serde(rename = "nemotron-super-3-120b")]
192 NemotronSuper3_120B,
193 #[serde(rename = "nemotron-nano-3-30b")]
194 NemotronNano3_30B,
195
196 // MiniMax models
197 #[serde(rename = "minimax-m2")]
198 MiniMaxM2,
199 #[serde(rename = "minimax-m2-1")]
200 MiniMaxM2_1,
201 #[serde(rename = "minimax-m2-5")]
202 MiniMaxM2_5,
203
204 // Z.AI GLM models
205 #[serde(rename = "glm-5")]
206 GLM5,
207 #[serde(rename = "glm-4-7")]
208 GLM4_7,
209 #[serde(rename = "glm-4-7-flash")]
210 GLM4_7Flash,
211
212 // Moonshot models
213 #[serde(rename = "kimi-k2-thinking")]
214 KimiK2Thinking,
215 #[serde(rename = "kimi-k2-5")]
216 KimiK2_5,
217
218 // DeepSeek models
219 #[serde(rename = "deepseek-r1")]
220 DeepSeekR1,
221 #[serde(rename = "deepseek-v3")]
222 DeepSeekV3_1,
223 #[serde(rename = "deepseek-v3-2")]
224 DeepSeekV3_2,
225
226 #[serde(rename = "custom")]
227 Custom {
228 name: String,
229 max_tokens: u64,
230 display_name: Option<String>,
231 max_output_tokens: Option<u64>,
232 default_temperature: Option<f32>,
233 cache_configuration: Option<BedrockModelCacheConfiguration>,
234 },
235}
236
237impl ConverseModel {
238 pub fn default_fast(_region: &str) -> Self {
239 Self::ClaudeHaiku4_5
240 }
241
242 pub fn from_id(id: &str) -> anyhow::Result<Self> {
243 if id.starts_with("claude-fable-5") {
244 Ok(Self::ClaudeFable5)
245 } else if id.starts_with("claude-opus-5") {
246 Ok(Self::ClaudeOpus5)
247 } else if id.starts_with("claude-opus-4-8") {
248 Ok(Self::ClaudeOpus4_8)
249 } else if id.starts_with("claude-opus-4-7") {
250 Ok(Self::ClaudeOpus4_7)
251 } else if id.starts_with("claude-opus-4-6") {
252 Ok(Self::ClaudeOpus4_6)
253 } else if id.starts_with("claude-opus-4-5") {
254 Ok(Self::ClaudeOpus4_5)
255 } else if id.starts_with("claude-opus-4-1") {
256 Ok(Self::ClaudeOpus4_1)
257 } else if id.starts_with("claude-sonnet-5") {
258 Ok(Self::ClaudeSonnet5)
259 } else if id.starts_with("claude-sonnet-4-6") {
260 Ok(Self::ClaudeSonnet4_6)
261 } else if id.starts_with("claude-sonnet-4-5") {
262 Ok(Self::ClaudeSonnet4_5)
263 } else if id.starts_with("claude-sonnet-4") {
264 Ok(Self::ClaudeSonnet4)
265 } else if id.starts_with("claude-haiku-4-5") {
266 Ok(Self::ClaudeHaiku4_5)
267 } else {
268 anyhow::bail!("invalid model id {id}");
269 }
270 }
271
272 pub fn id(&self) -> &str {
273 match self {
274 Self::ClaudeFable5 => "claude-fable-5",
275 Self::ClaudeOpus5 => "claude-opus-5",
276 Self::ClaudeOpus4_8 => "claude-opus-4-8",
277 Self::ClaudeOpus4_7 => "claude-opus-4-7",
278 Self::ClaudeOpus4_6 => "claude-opus-4-6",
279 Self::ClaudeOpus4_5 => "claude-opus-4-5",
280 Self::ClaudeOpus4_1 => "claude-opus-4-1",
281 Self::ClaudeSonnet5 => "claude-sonnet-5",
282 Self::ClaudeSonnet4_6 => "claude-sonnet-4-6",
283 Self::ClaudeSonnet4_5 => "claude-sonnet-4-5",
284 Self::ClaudeSonnet4 => "claude-sonnet-4",
285 Self::ClaudeHaiku4_5 => "claude-haiku-4-5",
286 Self::Llama4Scout17B => "llama-4-scout-17b",
287 Self::Llama4Maverick17B => "llama-4-maverick-17b",
288 Self::Gemma3_4B => "gemma-3-4b",
289 Self::Gemma3_12B => "gemma-3-12b",
290 Self::Gemma3_27B => "gemma-3-27b",
291 Self::MagistralSmall => "magistral-small",
292 Self::MistralLarge3 => "mistral-large-3",
293 Self::PixtralLarge => "pixtral-large",
294 Self::Devstral2_123B => "devstral-2-123b",
295 Self::Ministral14B => "ministral-14b",
296 Self::Qwen3_32B => "qwen3-32b",
297 Self::Qwen3VL235B => "qwen3-vl-235b",
298 Self::Qwen3_235B => "qwen3-235b",
299 Self::Qwen3Next80B => "qwen3-next-80b",
300 Self::Qwen3Coder30B => "qwen3-coder-30b",
301 Self::Qwen3CoderNext => "qwen3-coder-next",
302 Self::Qwen3Coder480B => "qwen3-coder-480b",
303 Self::NovaLite => "nova-lite",
304 Self::NovaPro => "nova-pro",
305 Self::NovaPremier => "nova-premier",
306 Self::Nova2Lite => "nova-2-lite",
307 Self::GptOss20B => "gpt-oss-20b",
308 Self::GptOss120B => "gpt-oss-120b",
309 Self::NemotronSuper3_120B => "nemotron-super-3-120b",
310 Self::NemotronNano3_30B => "nemotron-nano-3-30b",
311 Self::MiniMaxM2 => "minimax-m2",
312 Self::MiniMaxM2_1 => "minimax-m2-1",
313 Self::MiniMaxM2_5 => "minimax-m2-5",
314 Self::GLM5 => "glm-5",
315 Self::GLM4_7 => "glm-4-7",
316 Self::GLM4_7Flash => "glm-4-7-flash",
317 Self::KimiK2Thinking => "kimi-k2-thinking",
318 Self::KimiK2_5 => "kimi-k2-5",
319 Self::DeepSeekR1 => "deepseek-r1",
320 Self::DeepSeekV3_1 => "deepseek-v3",
321 Self::DeepSeekV3_2 => "deepseek-v3-2",
322 Self::Custom { name, .. } => name,
323 }
324 }
325
326 pub fn request_id(&self) -> &str {
327 match self {
328 Self::ClaudeFable5 => "anthropic.claude-fable-5",
329 Self::ClaudeOpus5 => "anthropic.claude-opus-5",
330 Self::ClaudeOpus4_8 => "anthropic.claude-opus-4-8",
331 Self::ClaudeOpus4_7 => "anthropic.claude-opus-4-7",
332 Self::ClaudeOpus4_6 => "anthropic.claude-opus-4-6-v1",
333 Self::ClaudeOpus4_5 => "anthropic.claude-opus-4-5-20251101-v1:0",
334 Self::ClaudeOpus4_1 => "anthropic.claude-opus-4-1-20250805-v1:0",
335 Self::ClaudeSonnet5 => "anthropic.claude-sonnet-5",
336 Self::ClaudeSonnet4_6 => "anthropic.claude-sonnet-4-6",
337 Self::ClaudeSonnet4_5 => "anthropic.claude-sonnet-4-5-20250929-v1:0",
338 Self::ClaudeSonnet4 => "anthropic.claude-sonnet-4-20250514-v1:0",
339 Self::ClaudeHaiku4_5 => "anthropic.claude-haiku-4-5-20251001-v1:0",
340 Self::Llama4Scout17B => "meta.llama4-scout-17b-instruct-v1:0",
341 Self::Llama4Maverick17B => "meta.llama4-maverick-17b-instruct-v1:0",
342 Self::Gemma3_4B => "google.gemma-3-4b-it",
343 Self::Gemma3_12B => "google.gemma-3-12b-it",
344 Self::Gemma3_27B => "google.gemma-3-27b-it",
345 Self::MagistralSmall => "mistral.magistral-small-2509",
346 Self::MistralLarge3 => "mistral.mistral-large-3-675b-instruct",
347 Self::PixtralLarge => "mistral.pixtral-large-2502-v1:0",
348 Self::Devstral2_123B => "mistral.devstral-2-123b",
349 Self::Ministral14B => "mistral.ministral-3-14b-instruct",
350 Self::Qwen3VL235B => "qwen.qwen3-vl-235b-a22b",
351 Self::Qwen3_32B => "qwen.qwen3-32b-v1:0",
352 Self::Qwen3_235B => "qwen.qwen3-235b-a22b-2507-v1:0",
353 Self::Qwen3Next80B => "qwen.qwen3-next-80b-a3b",
354 Self::Qwen3Coder30B => "qwen.qwen3-coder-30b-a3b-v1:0",
355 Self::Qwen3CoderNext => "qwen.qwen3-coder-next",
356 Self::Qwen3Coder480B => "qwen.qwen3-coder-480b-a35b-v1:0",
357 Self::NovaLite => "amazon.nova-lite-v1:0",
358 Self::NovaPro => "amazon.nova-pro-v1:0",
359 Self::NovaPremier => "amazon.nova-premier-v1:0",
360 Self::Nova2Lite => "amazon.nova-2-lite-v1:0",
361 Self::GptOss20B => "openai.gpt-oss-20b-1:0",
362 Self::GptOss120B => "openai.gpt-oss-120b-1:0",
363 Self::NemotronSuper3_120B => "nvidia.nemotron-super-3-120b",
364 Self::NemotronNano3_30B => "nvidia.nemotron-nano-3-30b",
365 Self::MiniMaxM2 => "minimax.minimax-m2",
366 Self::MiniMaxM2_1 => "minimax.minimax-m2.1",
367 Self::MiniMaxM2_5 => "minimax.minimax-m2.5",
368 Self::GLM5 => "zai.glm-5",
369 Self::GLM4_7 => "zai.glm-4.7",
370 Self::GLM4_7Flash => "zai.glm-4.7-flash",
371 Self::KimiK2Thinking => "moonshot.kimi-k2-thinking",
372 Self::KimiK2_5 => "moonshotai.kimi-k2.5",
373 Self::DeepSeekR1 => "deepseek.r1-v1:0",
374 Self::DeepSeekV3_1 => "deepseek.v3-v1:0",
375 Self::DeepSeekV3_2 => "deepseek.v3.2",
376 Self::Custom { name, .. } => name,
377 }
378 }
379
380 pub fn display_name(&self) -> &str {
381 match self {
382 Self::ClaudeFable5 => "Claude Fable 5",
383 Self::ClaudeOpus5 => "Claude Opus 5",
384 Self::ClaudeOpus4_8 => "Claude Opus 4.8",
385 Self::ClaudeOpus4_7 => "Claude Opus 4.7",
386 Self::ClaudeOpus4_6 => "Claude Opus 4.6",
387 Self::ClaudeOpus4_5 => "Claude Opus 4.5",
388 Self::ClaudeOpus4_1 => "Claude Opus 4.1",
389 Self::ClaudeSonnet5 => "Claude Sonnet 5",
390 Self::ClaudeSonnet4_6 => "Claude Sonnet 4.6",
391 Self::ClaudeSonnet4_5 => "Claude Sonnet 4.5",
392 Self::ClaudeSonnet4 => "Claude Sonnet 4",
393 Self::ClaudeHaiku4_5 => "Claude Haiku 4.5",
394 Self::Llama4Scout17B => "Llama 4 Scout 17B",
395 Self::Llama4Maverick17B => "Llama 4 Maverick 17B",
396 Self::Gemma3_4B => "Gemma 3 4B",
397 Self::Gemma3_12B => "Gemma 3 12B",
398 Self::Gemma3_27B => "Gemma 3 27B",
399 Self::MagistralSmall => "Magistral Small",
400 Self::MistralLarge3 => "Mistral Large 3",
401 Self::PixtralLarge => "Pixtral Large",
402 Self::Devstral2_123B => "Devstral 2 123B",
403 Self::Ministral14B => "Ministral 14B",
404 Self::Qwen3VL235B => "Qwen3 VL 235B",
405 Self::Qwen3_32B => "Qwen3 32B",
406 Self::Qwen3_235B => "Qwen3 235B",
407 Self::Qwen3Next80B => "Qwen3 Next 80B",
408 Self::Qwen3Coder30B => "Qwen3 Coder 30B",
409 Self::Qwen3CoderNext => "Qwen3 Coder Next",
410 Self::Qwen3Coder480B => "Qwen3 Coder 480B",
411 Self::NovaLite => "Amazon Nova Lite",
412 Self::NovaPro => "Amazon Nova Pro",
413 Self::NovaPremier => "Amazon Nova Premier",
414 Self::Nova2Lite => "Amazon Nova 2 Lite",
415 Self::GptOss20B => "GPT OSS 20B",
416 Self::GptOss120B => "GPT OSS 120B",
417 Self::NemotronSuper3_120B => "Nemotron Super 3 120B",
418 Self::NemotronNano3_30B => "Nemotron Nano 3 30B",
419 Self::MiniMaxM2 => "MiniMax M2",
420 Self::MiniMaxM2_1 => "MiniMax M2.1",
421 Self::MiniMaxM2_5 => "MiniMax M2.5",
422 Self::GLM5 => "GLM 5",
423 Self::GLM4_7 => "GLM 4.7",
424 Self::GLM4_7Flash => "GLM 4.7 Flash",
425 Self::KimiK2Thinking => "Kimi K2 Thinking",
426 Self::KimiK2_5 => "Kimi K2.5",
427 Self::DeepSeekR1 => "DeepSeek R1",
428 Self::DeepSeekV3_1 => "DeepSeek V3.1",
429 Self::DeepSeekV3_2 => "DeepSeek V3.2",
430 Self::Custom {
431 display_name, name, ..
432 } => display_name.as_deref().unwrap_or(name.as_str()),
433 }
434 }
435
436 pub fn max_token_count(&self) -> u64 {
437 match self {
438 Self::ClaudeFable5
439 | Self::ClaudeOpus5
440 | Self::ClaudeOpus4_8
441 | Self::ClaudeOpus4_7
442 | Self::ClaudeOpus4_6
443 | Self::ClaudeOpus4_5
444 | Self::ClaudeSonnet5
445 | Self::ClaudeSonnet4_6
446 | Self::ClaudeSonnet4_5
447 | Self::ClaudeSonnet4
448 | Self::ClaudeHaiku4_5 => 1_000_000,
449 Self::ClaudeOpus4_1 => 200_000,
450 Self::Llama4Scout17B | Self::Llama4Maverick17B => 128_000,
451 Self::Gemma3_4B | Self::Gemma3_12B | Self::Gemma3_27B => 128_000,
452 Self::MagistralSmall | Self::MistralLarge3 | Self::PixtralLarge => 128_000,
453 Self::Devstral2_123B | Self::Ministral14B => 256_000,
454 Self::Qwen3_32B
455 | Self::Qwen3VL235B
456 | Self::Qwen3_235B
457 | Self::Qwen3Next80B
458 | Self::Qwen3Coder30B
459 | Self::Qwen3CoderNext
460 | Self::Qwen3Coder480B => 128_000,
461 Self::NovaLite | Self::NovaPro => 300_000,
462 Self::NovaPremier => 1_000_000,
463 Self::Nova2Lite => 300_000,
464 Self::GptOss20B | Self::GptOss120B => 128_000,
465 Self::NemotronSuper3_120B | Self::NemotronNano3_30B => 262_000,
466 Self::MiniMaxM2 | Self::MiniMaxM2_1 | Self::MiniMaxM2_5 => 196_000,
467 Self::GLM5 | Self::GLM4_7 | Self::GLM4_7Flash => 203_000,
468 Self::KimiK2Thinking | Self::KimiK2_5 => 128_000,
469 Self::DeepSeekR1 | Self::DeepSeekV3_1 | Self::DeepSeekV3_2 => 128_000,
470 Self::Custom { max_tokens, .. } => *max_tokens,
471 }
472 }
473
474 pub fn max_output_tokens(&self) -> u64 {
475 match self {
476 Self::ClaudeFable5
477 | Self::ClaudeOpus5
478 | Self::ClaudeOpus4_8
479 | Self::ClaudeOpus4_7
480 | Self::ClaudeOpus4_6
481 | Self::ClaudeSonnet5 => 128_000,
482 Self::ClaudeOpus4_5
483 | Self::ClaudeSonnet4_6
484 | Self::ClaudeSonnet4_5
485 | Self::ClaudeSonnet4
486 | Self::ClaudeHaiku4_5 => 64_000,
487 Self::ClaudeOpus4_1 => 32_000,
488 Self::Llama4Scout17B
489 | Self::Llama4Maverick17B
490 | Self::Gemma3_4B
491 | Self::Gemma3_12B
492 | Self::Gemma3_27B
493 | Self::MagistralSmall
494 | Self::MistralLarge3
495 | Self::PixtralLarge => 8_192,
496 Self::Devstral2_123B | Self::Ministral14B => 131_000,
497 Self::Qwen3_32B
498 | Self::Qwen3VL235B
499 | Self::Qwen3_235B
500 | Self::Qwen3Next80B
501 | Self::Qwen3Coder30B
502 | Self::Qwen3CoderNext
503 | Self::Qwen3Coder480B => 8_192,
504 Self::NovaLite | Self::NovaPro | Self::NovaPremier | Self::Nova2Lite => 5_000,
505 Self::GptOss20B | Self::GptOss120B => 16_000,
506 Self::NemotronSuper3_120B | Self::NemotronNano3_30B => 131_000,
507 Self::MiniMaxM2 | Self::MiniMaxM2_1 | Self::MiniMaxM2_5 => 98_000,
508 Self::GLM5 | Self::GLM4_7 | Self::GLM4_7Flash => 101_000,
509 Self::KimiK2Thinking | Self::KimiK2_5 => 16_000,
510 Self::DeepSeekR1 | Self::DeepSeekV3_1 | Self::DeepSeekV3_2 => 16_000,
511 Self::Custom {
512 max_output_tokens, ..
513 } => max_output_tokens.unwrap_or(4_096),
514 }
515 }
516
517 pub fn default_temperature(&self) -> f32 {
518 match self {
519 Self::ClaudeFable5
520 | Self::ClaudeOpus5
521 | Self::ClaudeOpus4_8
522 | Self::ClaudeOpus4_7
523 | Self::ClaudeOpus4_6
524 | Self::ClaudeOpus4_5
525 | Self::ClaudeOpus4_1
526 | Self::ClaudeSonnet5
527 | Self::ClaudeSonnet4_6
528 | Self::ClaudeSonnet4_5
529 | Self::ClaudeSonnet4
530 | Self::ClaudeHaiku4_5 => 1.0,
531 Self::Custom {
532 default_temperature,
533 ..
534 } => default_temperature.unwrap_or(1.0),
535 _ => 1.0,
536 }
537 }
538
539 pub fn supports_tool_use(&self) -> bool {
540 match self {
541 Self::ClaudeFable5
542 | Self::ClaudeOpus5
543 | Self::ClaudeOpus4_8
544 | Self::ClaudeOpus4_7
545 | Self::ClaudeOpus4_6
546 | Self::ClaudeOpus4_5
547 | Self::ClaudeOpus4_1
548 | Self::ClaudeSonnet5
549 | Self::ClaudeSonnet4_6
550 | Self::ClaudeSonnet4_5
551 | Self::ClaudeSonnet4
552 | Self::ClaudeHaiku4_5 => true,
553 Self::NovaLite | Self::NovaPro | Self::NovaPremier | Self::Nova2Lite => true,
554 Self::MistralLarge3 | Self::PixtralLarge | Self::MagistralSmall => true,
555 Self::Devstral2_123B | Self::Ministral14B => true,
556 // Gemma accepts toolConfig without error but produces unreliable tool
557 // calls -- malformed JSON args, hallucinated tool names, dropped calls.
558 Self::Qwen3_32B
559 | Self::Qwen3VL235B
560 | Self::Qwen3_235B
561 | Self::Qwen3Next80B
562 | Self::Qwen3Coder30B
563 | Self::Qwen3CoderNext
564 | Self::Qwen3Coder480B => true,
565 Self::MiniMaxM2 | Self::MiniMaxM2_1 | Self::MiniMaxM2_5 => true,
566 Self::NemotronSuper3_120B | Self::NemotronNano3_30B => true,
567 Self::GLM5 | Self::GLM4_7 | Self::GLM4_7Flash => true,
568 Self::KimiK2Thinking | Self::KimiK2_5 => true,
569 Self::DeepSeekR1 | Self::DeepSeekV3_1 | Self::DeepSeekV3_2 => true,
570 _ => false,
571 }
572 }
573
574 pub fn supports_images(&self) -> bool {
575 match self {
576 Self::ClaudeFable5
577 | Self::ClaudeOpus5
578 | Self::ClaudeOpus4_8
579 | Self::ClaudeOpus4_7
580 | Self::ClaudeOpus4_6
581 | Self::ClaudeOpus4_5
582 | Self::ClaudeOpus4_1
583 | Self::ClaudeSonnet5
584 | Self::ClaudeSonnet4_6
585 | Self::ClaudeSonnet4_5
586 | Self::ClaudeSonnet4
587 | Self::ClaudeHaiku4_5 => true,
588 Self::NovaLite | Self::NovaPro => true,
589 Self::PixtralLarge => true,
590 Self::Qwen3VL235B => true,
591 Self::KimiK2_5 => true,
592 _ => false,
593 }
594 }
595
596 pub fn supports_caching(&self) -> bool {
597 match self {
598 Self::ClaudeFable5
599 | Self::ClaudeOpus5
600 | Self::ClaudeOpus4_8
601 | Self::ClaudeOpus4_7
602 | Self::ClaudeOpus4_6
603 | Self::ClaudeOpus4_5
604 | Self::ClaudeOpus4_1
605 | Self::ClaudeSonnet5
606 | Self::ClaudeSonnet4_6
607 | Self::ClaudeSonnet4_5
608 | Self::ClaudeSonnet4
609 | Self::ClaudeHaiku4_5 => true,
610 Self::Custom {
611 cache_configuration,
612 ..
613 } => cache_configuration.is_some(),
614 _ => false,
615 }
616 }
617
618 pub fn supports_thinking(&self) -> bool {
619 matches!(
620 self,
621 Self::ClaudeFable5
622 | Self::ClaudeOpus5
623 | Self::ClaudeOpus4_8
624 | Self::ClaudeOpus4_7
625 | Self::ClaudeOpus4_6
626 | Self::ClaudeOpus4_5
627 | Self::ClaudeOpus4_1
628 | Self::ClaudeSonnet5
629 | Self::ClaudeSonnet4_6
630 | Self::ClaudeSonnet4_5
631 | Self::ClaudeSonnet4
632 | Self::ClaudeHaiku4_5
633 )
634 }
635
636 pub fn supports_adaptive_thinking(&self) -> bool {
637 matches!(
638 self,
639 Self::ClaudeFable5
640 | Self::ClaudeOpus5
641 | Self::ClaudeOpus4_8
642 | Self::ClaudeOpus4_7
643 | Self::ClaudeOpus4_6
644 | Self::ClaudeSonnet5
645 | Self::ClaudeSonnet4_6
646 )
647 }
648
649 pub fn supports_xhigh_adaptive_thinking(&self) -> bool {
650 matches!(
651 self,
652 Self::ClaudeFable5 | Self::ClaudeOpus5 | Self::ClaudeOpus4_8 | Self::ClaudeSonnet5
653 )
654 }
655
656 pub fn thinking_mode(&self) -> BedrockModelMode {
657 if self.supports_adaptive_thinking() {
658 BedrockModelMode::AdaptiveThinking {
659 effort: BedrockAdaptiveThinkingEffort::default(),
660 }
661 } else if self.supports_thinking() {
662 BedrockModelMode::Thinking {
663 budget_tokens: Some(4096),
664 }
665 } else {
666 BedrockModelMode::Default
667 }
668 }
669
670 pub fn cross_region_inference_id(
671 &self,
672 region: &str,
673 allow_global: bool,
674 ) -> anyhow::Result<String> {
675 let model_id = self.request_id();
676
677 let supports_global = matches!(
678 self,
679 Self::ClaudeFable5
680 | Self::ClaudeOpus5
681 | Self::ClaudeOpus4_8
682 | Self::ClaudeOpus4_7
683 | Self::ClaudeOpus4_6
684 | Self::ClaudeOpus4_5
685 | Self::ClaudeSonnet5
686 | Self::ClaudeSonnet4_6
687 | Self::ClaudeSonnet4_5
688 | Self::ClaudeSonnet4
689 | Self::ClaudeHaiku4_5
690 | Self::Nova2Lite
691 );
692
693 // Determine region group based on AWS region
694 let region_group = if region.starts_with("us-gov-") {
695 "us-gov"
696 } else if region.starts_with("us-") || region.starts_with("sa-") {
697 if allow_global && supports_global {
698 "global"
699 } else {
700 "us"
701 }
702 } else if region.starts_with("ca-") {
703 if allow_global && supports_global {
704 "global"
705 } else {
706 "ca"
707 }
708 } else if region.starts_with("eu-") {
709 if allow_global && supports_global {
710 "global"
711 } else {
712 "eu"
713 }
714 } else if region == "ap-southeast-2" || region == "ap-southeast-4" {
715 // Australia
716 if allow_global && supports_global {
717 "global"
718 } else {
719 "au"
720 }
721 } else if region == "ap-northeast-1" || region == "ap-northeast-3" {
722 // Japan
723 if allow_global && supports_global {
724 "global"
725 } else {
726 "jp"
727 }
728 } else if region.starts_with("ap-") || region.starts_with("me-") {
729 if allow_global && supports_global {
730 "global"
731 } else {
732 "apac"
733 }
734 } else {
735 anyhow::bail!("Unsupported Region {region}");
736 };
737
738 match (self, region_group) {
739 (Self::Custom { .. }, _) => Ok(model_id.into()),
740
741 // Global inference profiles
742 (
743 Self::ClaudeFable5
744 | Self::ClaudeOpus5
745 | Self::ClaudeOpus4_8
746 | Self::ClaudeOpus4_7
747 | Self::ClaudeOpus4_6
748 | Self::ClaudeOpus4_5
749 | Self::ClaudeSonnet5
750 | Self::ClaudeSonnet4_6
751 | Self::ClaudeSonnet4_5
752 | Self::ClaudeSonnet4
753 | Self::ClaudeHaiku4_5
754 | Self::Nova2Lite,
755 "global",
756 ) => Ok(format!("{}.{}", region_group, model_id)),
757
758 // US Government region inference profiles
759 (Self::ClaudeSonnet4_5, "us-gov") => Ok(format!("{}.{}", region_group, model_id)),
760
761 // US region inference profiles
762 (
763 Self::ClaudeFable5
764 | Self::ClaudeOpus5
765 | Self::ClaudeOpus4_8
766 | Self::ClaudeOpus4_7
767 | Self::ClaudeOpus4_6
768 | Self::ClaudeOpus4_5
769 | Self::ClaudeOpus4_1
770 | Self::ClaudeSonnet5
771 | Self::ClaudeSonnet4_6
772 | Self::ClaudeSonnet4_5
773 | Self::ClaudeSonnet4
774 | Self::ClaudeHaiku4_5
775 | Self::Llama4Scout17B
776 | Self::Llama4Maverick17B
777 | Self::NovaLite
778 | Self::NovaPro
779 | Self::NovaPremier
780 | Self::Nova2Lite
781 | Self::PixtralLarge
782 | Self::DeepSeekR1,
783 "us",
784 ) => Ok(format!("{}.{}", region_group, model_id)),
785
786 // Canada region inference profiles
787 (Self::NovaLite, "ca") => Ok(format!("{}.{}", region_group, model_id)),
788
789 // Canada has no Claude-specific `ca.` profiles. AWS instead lists
790 // ca-central-1 and ca-west-1 as source regions of the US geo
791 // profile for these models, which keeps data within US and Canada
792 // regions. See the per-model cards, e.g.
793 // <https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-5.html>.
794 (Self::ClaudeOpus5, "ca") => Ok(format!("us.{}", model_id)),
795
796 // EU region inference profiles
797 (
798 Self::ClaudeOpus5
799 | Self::ClaudeOpus4_8
800 | Self::ClaudeOpus4_7
801 | Self::ClaudeOpus4_6
802 | Self::ClaudeSonnet4_6
803 | Self::ClaudeSonnet4_5
804 | Self::ClaudeSonnet4
805 | Self::ClaudeHaiku4_5
806 | Self::NovaLite
807 | Self::NovaPro
808 | Self::Nova2Lite,
809 "eu",
810 ) => Ok(format!("{}.{}", region_group, model_id)),
811
812 // Australia region inference profiles
813 (
814 Self::ClaudeOpus5
815 | Self::ClaudeOpus4_8
816 | Self::ClaudeOpus4_7
817 | Self::ClaudeOpus4_6
818 | Self::ClaudeSonnet4_6
819 | Self::ClaudeSonnet4_5
820 | Self::ClaudeHaiku4_5,
821 "au",
822 ) => Ok(format!("{}.{}", region_group, model_id)),
823
824 // Japan region inference profiles. Claude Opus 5 is deliberately
825 // absent: its model card lists only `us.`/`eu.`/`au.` geo profiles
826 // (plus `global.`):
827 // <https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-5.html>
828 (
829 Self::ClaudeSonnet4_6
830 | Self::ClaudeSonnet4_5
831 | Self::ClaudeHaiku4_5
832 | Self::Nova2Lite,
833 "jp",
834 ) => Ok(format!("{}.{}", region_group, model_id)),
835
836 // APAC region inference profiles (other than AU/JP)
837 (
838 Self::ClaudeSonnet4_5
839 | Self::ClaudeSonnet4
840 | Self::ClaudeHaiku4_5
841 | Self::NovaLite
842 | Self::NovaPro
843 | Self::Nova2Lite,
844 "apac",
845 ) => Ok(format!("{}.{}", region_group, model_id)),
846
847 (Self::ClaudeFable5 | Self::ClaudeSonnet5, _) => Ok(format!("global.{}", model_id)),
848
849 // Default: use model ID directly
850 _ => Ok(model_id.into()),
851 }
852 }
853}
854
855/// The wire protocol used to talk to a [`MantleModel`] on the `bedrock-mantle` endpoint.
856#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
857#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
858pub enum MantleProtocol {
859 /// The OpenAI Chat Completions API (`/chat/completions`).
860 #[default]
861 ChatCompletions,
862 /// The OpenAI Responses API (`/responses`).
863 Responses,
864}
865
866/// Models only reachable through the `bedrock-mantle` endpoint's
867/// OpenAI-compatible APIs, i.e. with no `Converse`/`Invoke` support on
868/// `bedrock-runtime`.
869#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
870#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, EnumIter)]
871pub enum MantleModel {
872 #[serde(rename = "gpt-5.6-sol")]
873 Gpt5_6Sol,
874 #[serde(rename = "gpt-5.6-terra")]
875 Gpt5_6Terra,
876 #[serde(rename = "gpt-5.6-luna")]
877 Gpt5_6Luna,
878 #[serde(rename = "gpt-5.5")]
879 Gpt5_5,
880 #[serde(rename = "gpt-5.4")]
881 Gpt5_4,
882 #[serde(rename = "grok-4.3")]
883 Grok4_3,
884 #[serde(rename = "custom")]
885 Custom {
886 name: String,
887 display_name: Option<String>,
888 max_tokens: u64,
889 max_output_tokens: Option<u64>,
890 protocol: MantleProtocol,
891 supports_tools: bool,
892 supports_images: bool,
893 supports_thinking: bool,
894 },
895}
896
897impl MantleModel {
898 /// The model id Zed uses internally (also used as the `name` in settings).
899 pub fn id(&self) -> &str {
900 match self {
901 Self::Gpt5_6Sol => "gpt-5.6-sol",
902 Self::Gpt5_6Terra => "gpt-5.6-terra",
903 Self::Gpt5_6Luna => "gpt-5.6-luna",
904 Self::Gpt5_5 => "gpt-5.5",
905 Self::Gpt5_4 => "gpt-5.4",
906 Self::Grok4_3 => "grok-4.3",
907 Self::Custom { name, .. } => name,
908 }
909 }
910
911 /// The model id as expected in Bedrock Mantle request bodies, e.g. `openai.gpt-5.5`.
912 pub fn request_id(&self) -> &str {
913 match self {
914 Self::Gpt5_6Sol => "openai.gpt-5.6-sol",
915 Self::Gpt5_6Terra => "openai.gpt-5.6-terra",
916 Self::Gpt5_6Luna => "openai.gpt-5.6-luna",
917 Self::Gpt5_5 => "openai.gpt-5.5",
918 Self::Gpt5_4 => "openai.gpt-5.4",
919 Self::Grok4_3 => "xai.grok-4.3",
920 Self::Custom { name, .. } => name,
921 }
922 }
923
924 pub fn display_name(&self) -> &str {
925 match self {
926 Self::Gpt5_6Sol => "GPT-5.6 Sol",
927 Self::Gpt5_6Terra => "GPT-5.6 Terra",
928 Self::Gpt5_6Luna => "GPT-5.6 Luna",
929 Self::Gpt5_5 => "GPT-5.5",
930 Self::Gpt5_4 => "GPT-5.4",
931 Self::Grok4_3 => "Grok 4.3",
932 Self::Custom {
933 display_name, name, ..
934 } => display_name.as_deref().unwrap_or(name.as_str()),
935 }
936 }
937
938 /// Which OpenAI-compatible API this model must be called through.
939 pub fn protocol(&self) -> MantleProtocol {
940 match self {
941 Self::Gpt5_6Sol
942 | Self::Gpt5_6Terra
943 | Self::Gpt5_6Luna
944 | Self::Gpt5_5
945 | Self::Gpt5_4
946 | Self::Grok4_3 => MantleProtocol::Responses,
947 Self::Custom { protocol, .. } => *protocol,
948 }
949 }
950
951 pub fn max_token_count(&self) -> u64 {
952 match self {
953 Self::Gpt5_6Sol
954 | Self::Gpt5_6Terra
955 | Self::Gpt5_6Luna
956 | Self::Gpt5_5
957 | Self::Gpt5_4 => 272_000,
958 Self::Grok4_3 => 1_000_000,
959 Self::Custom { max_tokens, .. } => *max_tokens,
960 }
961 }
962
963 pub fn max_output_tokens(&self) -> u64 {
964 match self {
965 // AWS doesn't document a hard cap for the GPT-5.x models on Mantle.
966 Self::Gpt5_6Sol
967 | Self::Gpt5_6Terra
968 | Self::Gpt5_6Luna
969 | Self::Gpt5_5
970 | Self::Gpt5_4 => 128_000,
971 Self::Grok4_3 => 131_072,
972 Self::Custom {
973 max_output_tokens, ..
974 } => max_output_tokens.unwrap_or(4_096),
975 }
976 }
977
978 pub fn supports_tools(&self) -> bool {
979 match self {
980 Self::Gpt5_6Sol
981 | Self::Gpt5_6Terra
982 | Self::Gpt5_6Luna
983 | Self::Gpt5_5
984 | Self::Gpt5_4
985 | Self::Grok4_3 => true,
986 Self::Custom { supports_tools, .. } => *supports_tools,
987 }
988 }
989
990 pub fn supports_images(&self) -> bool {
991 match self {
992 Self::Gpt5_6Sol
993 | Self::Gpt5_6Terra
994 | Self::Gpt5_6Luna
995 | Self::Gpt5_5
996 | Self::Gpt5_4
997 | Self::Grok4_3 => true,
998 Self::Custom {
999 supports_images, ..
1000 } => *supports_images,
1001 }
1002 }
1003
1004 pub fn supports_thinking(&self) -> bool {
1005 match self {
1006 Self::Gpt5_6Sol
1007 | Self::Gpt5_6Terra
1008 | Self::Gpt5_6Luna
1009 | Self::Gpt5_5
1010 | Self::Gpt5_4
1011 | Self::Grok4_3 => true,
1012 Self::Custom {
1013 supports_thinking, ..
1014 } => *supports_thinking,
1015 }
1016 }
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021 use super::*;
1022
1023 #[test]
1024 fn test_builtin_mantle_models_use_responses_protocol() {
1025 assert_eq!(MantleModel::Gpt5_6Sol.protocol(), MantleProtocol::Responses);
1026 assert_eq!(
1027 MantleModel::Gpt5_6Terra.protocol(),
1028 MantleProtocol::Responses
1029 );
1030 assert_eq!(
1031 MantleModel::Gpt5_6Luna.protocol(),
1032 MantleProtocol::Responses
1033 );
1034 assert_eq!(MantleModel::Gpt5_5.protocol(), MantleProtocol::Responses);
1035 assert_eq!(MantleModel::Gpt5_4.protocol(), MantleProtocol::Responses);
1036 assert_eq!(MantleModel::Grok4_3.protocol(), MantleProtocol::Responses);
1037 }
1038
1039 #[test]
1040 fn test_gpt_5_6_mantle_model_metadata() {
1041 // Values mirror the AWS Bedrock model cards for GPT-5.6 Sol/Terra/Luna,
1042 // which are served only through the `bedrock-mantle` Responses API.
1043 for (model, id, request_id, display_name) in [
1044 (
1045 MantleModel::Gpt5_6Sol,
1046 "gpt-5.6-sol",
1047 "openai.gpt-5.6-sol",
1048 "GPT-5.6 Sol",
1049 ),
1050 (
1051 MantleModel::Gpt5_6Terra,
1052 "gpt-5.6-terra",
1053 "openai.gpt-5.6-terra",
1054 "GPT-5.6 Terra",
1055 ),
1056 (
1057 MantleModel::Gpt5_6Luna,
1058 "gpt-5.6-luna",
1059 "openai.gpt-5.6-luna",
1060 "GPT-5.6 Luna",
1061 ),
1062 ] {
1063 assert_eq!(model.id(), id);
1064 assert_eq!(model.request_id(), request_id);
1065 assert_eq!(model.display_name(), display_name);
1066 assert_eq!(model.max_token_count(), 272_000);
1067 assert!(model.supports_tools());
1068 assert!(model.supports_images());
1069 assert!(model.supports_thinking());
1070 }
1071 }
1072
1073 #[test]
1074 fn test_builtin_mantle_models_have_unique_ids_and_sane_token_limits() {
1075 use std::collections::HashSet;
1076 use strum::IntoEnumIterator;
1077
1078 let mut ids = HashSet::new();
1079 let mut request_ids = HashSet::new();
1080 for model in
1081 MantleModel::iter().filter(|model| !matches!(model, MantleModel::Custom { .. }))
1082 {
1083 assert!(
1084 ids.insert(model.id().to_string()),
1085 "duplicate MantleModel id: {}",
1086 model.id()
1087 );
1088 assert!(
1089 request_ids.insert(model.request_id().to_string()),
1090 "duplicate MantleModel request_id: {}",
1091 model.request_id()
1092 );
1093 assert!(
1094 model.max_output_tokens() <= model.max_token_count(),
1095 "{} has max_output_tokens ({}) greater than max_token_count ({})",
1096 model.id(),
1097 model.max_output_tokens(),
1098 model.max_token_count()
1099 );
1100 }
1101 }
1102
1103 #[test]
1104 fn test_us_region_inference_ids() -> anyhow::Result<()> {
1105 assert_eq!(
1106 ConverseModel::ClaudeSonnet4_5.cross_region_inference_id("us-east-1", false)?,
1107 "us.anthropic.claude-sonnet-4-5-20250929-v1:0"
1108 );
1109 assert_eq!(
1110 ConverseModel::ClaudeSonnet4.cross_region_inference_id("us-west-2", false)?,
1111 "us.anthropic.claude-sonnet-4-20250514-v1:0"
1112 );
1113 assert_eq!(
1114 ConverseModel::ClaudeFable5.cross_region_inference_id("us-east-1", false)?,
1115 "us.anthropic.claude-fable-5"
1116 );
1117 assert_eq!(
1118 ConverseModel::ClaudeSonnet5.cross_region_inference_id("us-east-1", false)?,
1119 "us.anthropic.claude-sonnet-5"
1120 );
1121 assert_eq!(
1122 ConverseModel::ClaudeOpus5.cross_region_inference_id("us-east-1", false)?,
1123 "us.anthropic.claude-opus-5"
1124 );
1125 assert_eq!(
1126 ConverseModel::NovaPro.cross_region_inference_id("us-east-2", false)?,
1127 "us.amazon.nova-pro-v1:0"
1128 );
1129 assert_eq!(
1130 ConverseModel::DeepSeekR1.cross_region_inference_id("us-east-1", false)?,
1131 "us.deepseek.r1-v1:0"
1132 );
1133 Ok(())
1134 }
1135
1136 #[test]
1137 fn test_eu_region_inference_ids() -> anyhow::Result<()> {
1138 assert_eq!(
1139 ConverseModel::ClaudeSonnet4.cross_region_inference_id("eu-west-1", false)?,
1140 "eu.anthropic.claude-sonnet-4-20250514-v1:0"
1141 );
1142 assert_eq!(
1143 ConverseModel::ClaudeSonnet4_5.cross_region_inference_id("eu-west-1", false)?,
1144 "eu.anthropic.claude-sonnet-4-5-20250929-v1:0"
1145 );
1146 assert_eq!(
1147 ConverseModel::NovaLite.cross_region_inference_id("eu-north-1", false)?,
1148 "eu.amazon.nova-lite-v1:0"
1149 );
1150 assert_eq!(
1151 ConverseModel::ClaudeOpus4_6.cross_region_inference_id("eu-west-1", false)?,
1152 "eu.anthropic.claude-opus-4-6-v1"
1153 );
1154 assert_eq!(
1155 ConverseModel::ClaudeOpus4_7.cross_region_inference_id("eu-west-1", false)?,
1156 "eu.anthropic.claude-opus-4-7"
1157 );
1158 assert_eq!(
1159 ConverseModel::ClaudeOpus4_8.cross_region_inference_id("eu-west-1", false)?,
1160 "eu.anthropic.claude-opus-4-8"
1161 );
1162 assert_eq!(
1163 ConverseModel::ClaudeOpus5.cross_region_inference_id("eu-west-1", false)?,
1164 "eu.anthropic.claude-opus-5"
1165 );
1166 Ok(())
1167 }
1168
1169 #[test]
1170 fn test_inference_profile_only_models_fall_back_to_global() -> anyhow::Result<()> {
1171 assert_eq!(
1172 ConverseModel::ClaudeFable5.cross_region_inference_id("eu-west-1", false)?,
1173 "global.anthropic.claude-fable-5"
1174 );
1175 assert_eq!(
1176 ConverseModel::ClaudeSonnet5.cross_region_inference_id("eu-west-1", false)?,
1177 "global.anthropic.claude-sonnet-5"
1178 );
1179 assert_eq!(
1180 ConverseModel::ClaudeFable5.cross_region_inference_id("ap-southeast-2", false)?,
1181 "global.anthropic.claude-fable-5"
1182 );
1183 assert_eq!(
1184 ConverseModel::ClaudeSonnet5.cross_region_inference_id("ap-northeast-1", false)?,
1185 "global.anthropic.claude-sonnet-5"
1186 );
1187 Ok(())
1188 }
1189
1190 #[test]
1191 fn test_apac_region_inference_ids() -> anyhow::Result<()> {
1192 assert_eq!(
1193 ConverseModel::ClaudeSonnet4_5.cross_region_inference_id("ap-south-1", false)?,
1194 "apac.anthropic.claude-sonnet-4-5-20250929-v1:0"
1195 );
1196 assert_eq!(
1197 ConverseModel::NovaLite.cross_region_inference_id("ap-south-1", false)?,
1198 "apac.amazon.nova-lite-v1:0"
1199 );
1200 Ok(())
1201 }
1202
1203 #[test]
1204 fn test_au_region_inference_ids() -> anyhow::Result<()> {
1205 assert_eq!(
1206 ConverseModel::ClaudeHaiku4_5.cross_region_inference_id("ap-southeast-2", false)?,
1207 "au.anthropic.claude-haiku-4-5-20251001-v1:0"
1208 );
1209 assert_eq!(
1210 ConverseModel::ClaudeSonnet4_5.cross_region_inference_id("ap-southeast-4", false)?,
1211 "au.anthropic.claude-sonnet-4-5-20250929-v1:0"
1212 );
1213 assert_eq!(
1214 ConverseModel::ClaudeOpus4_6.cross_region_inference_id("ap-southeast-2", false)?,
1215 "au.anthropic.claude-opus-4-6-v1"
1216 );
1217 assert_eq!(
1218 ConverseModel::ClaudeOpus4_7.cross_region_inference_id("ap-southeast-2", false)?,
1219 "au.anthropic.claude-opus-4-7"
1220 );
1221 assert_eq!(
1222 ConverseModel::ClaudeOpus4_8.cross_region_inference_id("ap-southeast-2", false)?,
1223 "au.anthropic.claude-opus-4-8"
1224 );
1225 assert_eq!(
1226 ConverseModel::ClaudeOpus5.cross_region_inference_id("ap-southeast-2", false)?,
1227 "au.anthropic.claude-opus-5"
1228 );
1229 Ok(())
1230 }
1231
1232 #[test]
1233 fn test_jp_region_inference_ids() -> anyhow::Result<()> {
1234 assert_eq!(
1235 ConverseModel::ClaudeHaiku4_5.cross_region_inference_id("ap-northeast-1", false)?,
1236 "jp.anthropic.claude-haiku-4-5-20251001-v1:0"
1237 );
1238 // Claude Opus 5 has no `jp.` geo profile, so it falls through to the
1239 // bare model id (which fails at request time with a region hint)
1240 // rather than fabricating an undocumented profile.
1241 assert_eq!(
1242 ConverseModel::ClaudeOpus5.cross_region_inference_id("ap-northeast-1", false)?,
1243 "anthropic.claude-opus-5"
1244 );
1245 assert_eq!(
1246 ConverseModel::ClaudeSonnet4_5.cross_region_inference_id("ap-northeast-3", false)?,
1247 "jp.anthropic.claude-sonnet-4-5-20250929-v1:0"
1248 );
1249 assert_eq!(
1250 ConverseModel::Nova2Lite.cross_region_inference_id("ap-northeast-1", false)?,
1251 "jp.amazon.nova-2-lite-v1:0"
1252 );
1253 Ok(())
1254 }
1255
1256 #[test]
1257 fn test_ca_region_inference_ids() -> anyhow::Result<()> {
1258 assert_eq!(
1259 ConverseModel::NovaLite.cross_region_inference_id("ca-central-1", false)?,
1260 "ca.amazon.nova-lite-v1:0"
1261 );
1262 // Canadian regions are source regions of the US geo profile for
1263 // recent Opus models; there are no `ca.` Claude profiles.
1264 assert_eq!(
1265 ConverseModel::ClaudeOpus5.cross_region_inference_id("ca-central-1", false)?,
1266 "us.anthropic.claude-opus-5"
1267 );
1268 Ok(())
1269 }
1270
1271 #[test]
1272 fn test_gov_region_inference_ids() -> anyhow::Result<()> {
1273 assert_eq!(
1274 ConverseModel::ClaudeSonnet4_5.cross_region_inference_id("us-gov-east-1", false)?,
1275 "us-gov.anthropic.claude-sonnet-4-5-20250929-v1:0"
1276 );
1277 assert_eq!(
1278 ConverseModel::ClaudeSonnet4_5.cross_region_inference_id("us-gov-west-1", false)?,
1279 "us-gov.anthropic.claude-sonnet-4-5-20250929-v1:0"
1280 );
1281 Ok(())
1282 }
1283
1284 #[test]
1285 fn test_global_inference_ids() -> anyhow::Result<()> {
1286 assert_eq!(
1287 ConverseModel::ClaudeSonnet4.cross_region_inference_id("us-east-1", true)?,
1288 "global.anthropic.claude-sonnet-4-20250514-v1:0"
1289 );
1290 assert_eq!(
1291 ConverseModel::ClaudeSonnet4_5.cross_region_inference_id("eu-west-1", true)?,
1292 "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
1293 );
1294 assert_eq!(
1295 ConverseModel::ClaudeHaiku4_5.cross_region_inference_id("ap-south-1", true)?,
1296 "global.anthropic.claude-haiku-4-5-20251001-v1:0"
1297 );
1298 assert_eq!(
1299 ConverseModel::ClaudeOpus4_6.cross_region_inference_id("us-east-1", true)?,
1300 "global.anthropic.claude-opus-4-6-v1"
1301 );
1302 assert_eq!(
1303 ConverseModel::ClaudeOpus4_7.cross_region_inference_id("us-east-1", true)?,
1304 "global.anthropic.claude-opus-4-7"
1305 );
1306 assert_eq!(
1307 ConverseModel::ClaudeOpus4_8.cross_region_inference_id("us-east-1", true)?,
1308 "global.anthropic.claude-opus-4-8"
1309 );
1310 assert_eq!(
1311 ConverseModel::ClaudeOpus5.cross_region_inference_id("us-east-1", true)?,
1312 "global.anthropic.claude-opus-5"
1313 );
1314 assert_eq!(
1315 ConverseModel::ClaudeFable5.cross_region_inference_id("us-east-1", true)?,
1316 "global.anthropic.claude-fable-5"
1317 );
1318 assert_eq!(
1319 ConverseModel::ClaudeSonnet5.cross_region_inference_id("us-east-1", true)?,
1320 "global.anthropic.claude-sonnet-5"
1321 );
1322 assert_eq!(
1323 ConverseModel::Nova2Lite.cross_region_inference_id("us-east-1", true)?,
1324 "global.amazon.nova-2-lite-v1:0"
1325 );
1326
1327 // Models without global support fall back to regional
1328 assert_eq!(
1329 ConverseModel::NovaPro.cross_region_inference_id("us-east-1", true)?,
1330 "us.amazon.nova-pro-v1:0"
1331 );
1332 Ok(())
1333 }
1334
1335 #[test]
1336 fn test_models_without_cross_region() -> anyhow::Result<()> {
1337 // Models without cross-region support return their request_id directly
1338 assert_eq!(
1339 ConverseModel::Gemma3_4B.cross_region_inference_id("us-east-1", false)?,
1340 "google.gemma-3-4b-it"
1341 );
1342 assert_eq!(
1343 ConverseModel::MistralLarge3.cross_region_inference_id("eu-west-1", false)?,
1344 "mistral.mistral-large-3-675b-instruct"
1345 );
1346 assert_eq!(
1347 ConverseModel::Qwen3VL235B.cross_region_inference_id("ap-south-1", false)?,
1348 "qwen.qwen3-vl-235b-a22b"
1349 );
1350 assert_eq!(
1351 ConverseModel::GptOss120B.cross_region_inference_id("us-east-1", false)?,
1352 "openai.gpt-oss-120b-1:0"
1353 );
1354 assert_eq!(
1355 ConverseModel::MiniMaxM2.cross_region_inference_id("us-east-1", false)?,
1356 "minimax.minimax-m2"
1357 );
1358 assert_eq!(
1359 ConverseModel::KimiK2Thinking.cross_region_inference_id("us-east-1", false)?,
1360 "moonshot.kimi-k2-thinking"
1361 );
1362 Ok(())
1363 }
1364
1365 #[test]
1366 fn test_custom_model_inference_ids() -> anyhow::Result<()> {
1367 let custom_model = ConverseModel::Custom {
1368 name: "custom.my-model-v1:0".to_string(),
1369 max_tokens: 100000,
1370 display_name: Some("My Custom Model".to_string()),
1371 max_output_tokens: Some(8192),
1372 default_temperature: Some(0.7),
1373 cache_configuration: None,
1374 };
1375
1376 assert_eq!(
1377 custom_model.cross_region_inference_id("us-east-1", false)?,
1378 "custom.my-model-v1:0"
1379 );
1380 assert_eq!(
1381 custom_model.cross_region_inference_id("eu-west-1", true)?,
1382 "custom.my-model-v1:0"
1383 );
1384 Ok(())
1385 }
1386
1387 #[test]
1388 fn test_friendly_id_vs_request_id() {
1389 assert_eq!(ConverseModel::ClaudeSonnet4_5.id(), "claude-sonnet-4-5");
1390 assert_eq!(ConverseModel::NovaLite.id(), "nova-lite");
1391 assert_eq!(ConverseModel::DeepSeekR1.id(), "deepseek-r1");
1392 assert_eq!(ConverseModel::Llama4Scout17B.id(), "llama-4-scout-17b");
1393 assert_eq!(ConverseModel::ClaudeFable5.id(), "claude-fable-5");
1394 assert_eq!(ConverseModel::ClaudeOpus5.id(), "claude-opus-5");
1395 assert_eq!(ConverseModel::ClaudeSonnet5.id(), "claude-sonnet-5");
1396
1397 assert_eq!(
1398 ConverseModel::ClaudeSonnet4_5.request_id(),
1399 "anthropic.claude-sonnet-4-5-20250929-v1:0"
1400 );
1401 assert_eq!(
1402 ConverseModel::NovaLite.request_id(),
1403 "amazon.nova-lite-v1:0"
1404 );
1405 assert_eq!(ConverseModel::DeepSeekR1.request_id(), "deepseek.r1-v1:0");
1406 assert_eq!(
1407 ConverseModel::Llama4Scout17B.request_id(),
1408 "meta.llama4-scout-17b-instruct-v1:0"
1409 );
1410 assert_eq!(
1411 ConverseModel::ClaudeFable5.request_id(),
1412 "anthropic.claude-fable-5"
1413 );
1414 assert_eq!(
1415 ConverseModel::ClaudeOpus5.request_id(),
1416 "anthropic.claude-opus-5"
1417 );
1418 assert_eq!(
1419 ConverseModel::ClaudeSonnet5.request_id(),
1420 "anthropic.claude-sonnet-5"
1421 );
1422
1423 // Thinking aliases deserialize to the same model
1424 assert_eq!(ConverseModel::ClaudeSonnet4.id(), "claude-sonnet-4");
1425 assert_eq!(
1426 ConverseModel::from_id("claude-sonnet-4-thinking")
1427 .unwrap()
1428 .id(),
1429 "claude-sonnet-4"
1430 );
1431 assert_eq!(
1432 ConverseModel::from_id("claude-fable-5-thinking")
1433 .unwrap()
1434 .id(),
1435 "claude-fable-5"
1436 );
1437 assert_eq!(
1438 ConverseModel::from_id("claude-opus-5-thinking")
1439 .unwrap()
1440 .id(),
1441 "claude-opus-5"
1442 );
1443 assert_eq!(
1444 ConverseModel::from_id("claude-sonnet-5-thinking")
1445 .unwrap()
1446 .id(),
1447 "claude-sonnet-5"
1448 );
1449 }
1450
1451 #[test]
1452 fn test_thinking_modes() {
1453 assert!(ConverseModel::ClaudeHaiku4_5.supports_thinking());
1454 assert!(ConverseModel::ClaudeSonnet4.supports_thinking());
1455 assert!(ConverseModel::ClaudeSonnet4_5.supports_thinking());
1456 assert!(ConverseModel::ClaudeOpus4_6.supports_thinking());
1457 assert!(ConverseModel::ClaudeFable5.supports_thinking());
1458 assert!(ConverseModel::ClaudeOpus5.supports_thinking());
1459
1460 assert!(!ConverseModel::ClaudeSonnet4.supports_adaptive_thinking());
1461 assert!(ConverseModel::ClaudeOpus4_6.supports_adaptive_thinking());
1462 assert!(ConverseModel::ClaudeSonnet4_6.supports_adaptive_thinking());
1463 assert!(ConverseModel::ClaudeFable5.supports_adaptive_thinking());
1464 assert!(ConverseModel::ClaudeOpus5.supports_adaptive_thinking());
1465 assert!(ConverseModel::ClaudeSonnet5.supports_adaptive_thinking());
1466 assert!(!ConverseModel::ClaudeOpus4_7.supports_xhigh_adaptive_thinking());
1467 assert!(ConverseModel::ClaudeFable5.supports_xhigh_adaptive_thinking());
1468 assert!(ConverseModel::ClaudeOpus5.supports_xhigh_adaptive_thinking());
1469 assert!(ConverseModel::ClaudeSonnet5.supports_xhigh_adaptive_thinking());
1470 assert!(ConverseModel::ClaudeOpus4_8.supports_xhigh_adaptive_thinking());
1471 assert_eq!(BedrockAdaptiveThinkingEffort::XHigh.as_str(), "xhigh");
1472
1473 assert_eq!(
1474 ConverseModel::ClaudeSonnet4.thinking_mode(),
1475 BedrockModelMode::Thinking {
1476 budget_tokens: Some(4096)
1477 }
1478 );
1479 assert_eq!(
1480 ConverseModel::ClaudeOpus4_6.thinking_mode(),
1481 BedrockModelMode::AdaptiveThinking {
1482 effort: BedrockAdaptiveThinkingEffort::High
1483 }
1484 );
1485 assert_eq!(
1486 ConverseModel::ClaudeHaiku4_5.thinking_mode(),
1487 BedrockModelMode::Thinking {
1488 budget_tokens: Some(4096)
1489 }
1490 );
1491 }
1492
1493 #[test]
1494 fn test_max_token_count() {
1495 assert_eq!(ConverseModel::ClaudeSonnet4_5.max_token_count(), 1_000_000);
1496 assert_eq!(ConverseModel::ClaudeOpus4_6.max_token_count(), 1_000_000);
1497 assert_eq!(ConverseModel::ClaudeFable5.max_token_count(), 1_000_000);
1498 assert_eq!(ConverseModel::ClaudeOpus5.max_token_count(), 1_000_000);
1499 assert_eq!(ConverseModel::ClaudeSonnet5.max_token_count(), 1_000_000);
1500 assert_eq!(ConverseModel::Llama4Scout17B.max_token_count(), 128_000);
1501 assert_eq!(ConverseModel::NovaPremier.max_token_count(), 1_000_000);
1502 }
1503
1504 #[test]
1505 fn test_max_output_tokens() {
1506 assert_eq!(ConverseModel::ClaudeSonnet4_5.max_output_tokens(), 64_000);
1507 assert_eq!(ConverseModel::ClaudeOpus4_6.max_output_tokens(), 128_000);
1508 assert_eq!(ConverseModel::ClaudeFable5.max_output_tokens(), 128_000);
1509 assert_eq!(ConverseModel::ClaudeOpus5.max_output_tokens(), 128_000);
1510 assert_eq!(ConverseModel::ClaudeSonnet5.max_output_tokens(), 128_000);
1511 assert_eq!(ConverseModel::ClaudeOpus4_1.max_output_tokens(), 32_000);
1512 assert_eq!(ConverseModel::Gemma3_4B.max_output_tokens(), 8_192);
1513 }
1514
1515 #[test]
1516 fn test_supports_tool_use() {
1517 assert!(ConverseModel::ClaudeSonnet4_5.supports_tool_use());
1518 assert!(ConverseModel::ClaudeFable5.supports_tool_use());
1519 assert!(ConverseModel::NovaPro.supports_tool_use());
1520 assert!(ConverseModel::MistralLarge3.supports_tool_use());
1521 assert!(!ConverseModel::Gemma3_4B.supports_tool_use());
1522 assert!(ConverseModel::Qwen3_32B.supports_tool_use());
1523 assert!(ConverseModel::MiniMaxM2.supports_tool_use());
1524 assert!(ConverseModel::KimiK2_5.supports_tool_use());
1525 assert!(ConverseModel::DeepSeekR1.supports_tool_use());
1526 assert!(!ConverseModel::Llama4Scout17B.supports_tool_use());
1527 }
1528
1529 #[test]
1530 fn test_supports_caching() {
1531 assert!(ConverseModel::ClaudeSonnet4_5.supports_caching());
1532 assert!(ConverseModel::ClaudeOpus4_6.supports_caching());
1533 assert!(ConverseModel::ClaudeFable5.supports_caching());
1534 assert!(!ConverseModel::Llama4Scout17B.supports_caching());
1535 assert!(!ConverseModel::NovaPro.supports_caching());
1536 }
1537}
1538