1use crate::model::RuntimeBedrockModel;
8use crate::provider::ProviderError;
9use serde::Serialize;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
13#[serde(rename_all = "snake_case")]
14pub enum BedrockConversationApi {
15 Converse,
16 ChatCompletions,
19}
20
21#[derive(Debug, Clone, Copy, Serialize)]
23pub struct BedrockModelDescriptor {
24 pub model_id: &'static str,
25 pub display_name: &'static str,
26 pub context_window_label: &'static str,
27 pub max_output_label: Option<&'static str>,
28 pub conversation_api: BedrockConversationApi,
29 pub requires_inference_profile: bool,
30 pub model_card: &'static str,
31}
32
33impl BedrockModelDescriptor {
34 pub fn with_token_limits(
39 &self,
40 context_tokens: usize,
41 output_tokens: usize,
42 ) -> Result<RuntimeBedrockModel, ProviderError> {
43 RuntimeBedrockModel::new(
44 self.display_name,
45 self.model_id,
46 context_tokens,
47 output_tokens,
48 )
49 }
50}
51
52macro_rules! descriptor {
53 ($id:literal, $name:literal, $context:literal, $output:expr, $api:ident, $profile:literal, $card:literal) => {
54 BedrockModelDescriptor {
55 model_id: $id,
56 display_name: $name,
57 context_window_label: $context,
58 max_output_label: $output,
59 conversation_api: BedrockConversationApi::$api,
60 requires_inference_profile: $profile,
61 model_card: concat!(
62 "https://docs.aws.amazon.com/bedrock/latest/userguide/",
63 $card,
64 ".html"
65 ),
66 }
67 };
68}
69
70pub const BEDROCK_MODEL_CATALOG: &[BedrockModelDescriptor] = &[
74 descriptor!(
75 "anthropic.claude-opus-5",
76 "Claude Opus 5.0",
77 "1M",
78 Some("128K"),
79 Converse,
80 true,
81 "model-card-anthropic-claude-opus-5"
82 ),
83 descriptor!(
84 "anthropic.claude-sonnet-5",
85 "Claude Sonnet 5",
86 "1M",
87 Some("128K"),
88 Converse,
89 true,
90 "model-card-anthropic-claude-sonnet-5"
91 ),
92 descriptor!(
93 "anthropic.claude-opus-4-8",
94 "Claude Opus 4.8",
95 "1M",
96 Some("128K"),
97 Converse,
98 true,
99 "model-card-anthropic-claude-opus-4-8"
100 ),
101 descriptor!(
102 "anthropic.claude-opus-4-7",
103 "Claude Opus 4.7",
104 "1M",
105 Some("128K"),
106 Converse,
107 true,
108 "model-card-anthropic-claude-opus-4-7"
109 ),
110 descriptor!(
111 "anthropic.claude-sonnet-4-6",
112 "Claude Sonnet 4.6",
113 "1M",
114 Some("64K"),
115 Converse,
116 true,
117 "model-card-anthropic-claude-sonnet-4-6"
118 ),
119 descriptor!(
120 "anthropic.claude-opus-4-5-20251101-v1:0",
121 "Claude Opus 4.5",
122 "200K",
123 Some("64K"),
124 Converse,
125 true,
126 "model-card-anthropic-claude-opus-4-5"
127 ),
128 descriptor!(
129 "anthropic.claude-sonnet-4-5-20250929-v1:0",
130 "Claude Sonnet 4.5",
131 "200K",
132 Some("64K"),
133 Converse,
134 true,
135 "model-card-anthropic-claude-sonnet-4-5"
136 ),
137 descriptor!(
138 "anthropic.claude-sonnet-4-20250514-v1:0",
139 "Claude Sonnet 4",
140 "200K",
141 Some("64K"),
142 Converse,
143 true,
144 "model-card-anthropic-claude-sonnet-4"
145 ),
146 descriptor!(
147 "anthropic.claude-haiku-4-5-20251001-v1:0",
148 "Claude Haiku 4.5",
149 "200K",
150 Some("64K"),
151 Converse,
152 true,
153 "model-card-anthropic-claude-haiku-4-5"
154 ),
155 descriptor!(
157 "anthropic.claude-fable-5",
158 "Claude Fable 5",
159 "1M",
160 Some("128K"),
161 Converse,
162 true,
163 "model-card-anthropic-claude-fable-5"
164 ),
165 descriptor!(
166 "anthropic.claude-fable-5-1",
167 "Claude Fable 5.1",
168 "1M",
169 Some("128K"),
170 Converse,
171 true,
172 "model-card-anthropic-claude-fable-5-1"
173 ),
174 descriptor!(
175 "openai.gpt-6-astra",
176 "GPT-6 Astra",
177 "1,050,000",
178 Some("128,000"),
179 Converse,
180 true,
181 "model-card-openai-gpt-6-astra"
182 ),
183 descriptor!(
184 "openai.gpt-5.6-sol",
185 "GPT-5.6 Sol",
186 "1M",
187 None,
188 Converse,
189 true,
190 "model-card-openai-gpt-56-sol"
191 ),
192 descriptor!(
193 "openai.gpt-5.6-terra",
194 "GPT-5.6 Terra",
195 "1M",
196 None,
197 Converse,
198 true,
199 "model-card-openai-gpt-56-terra"
200 ),
201 descriptor!(
202 "openai.gpt-5.6-luna",
203 "GPT-5.6 Luna",
204 "1M",
205 None,
206 Converse,
207 true,
208 "model-card-openai-gpt-56-luna"
209 ),
210 descriptor!(
211 "minimax.minimax-m2.5",
212 "MiniMax M2.5",
213 "196K",
214 Some("8K"),
215 Converse,
216 false,
217 "model-card-minimax-minimax-m2-5"
218 ),
219 descriptor!(
220 "minimax.minimax-m2.1",
221 "MiniMax M2.1",
222 "196K",
223 Some("8K"),
224 Converse,
225 false,
226 "model-card-minimax-minimax-m2-1"
227 ),
228 descriptor!(
229 "zai.glm-5",
230 "GLM 5",
231 "200K",
232 Some("128K"),
233 Converse,
234 false,
235 "model-card-zai-glm-5"
236 ),
237 descriptor!(
238 "deepseek.v3.2",
239 "DeepSeek V3.2",
240 "164K",
241 Some("8K"),
242 Converse,
243 false,
244 "model-card-deepseek-deepseek-v3-2"
245 ),
246 descriptor!(
247 "qwen.qwen3-coder-next",
248 "Qwen3 Coder Next",
249 "256K",
250 Some("16K"),
251 Converse,
252 false,
253 "model-card-qwen-qwen3-coder-next"
254 ),
255 descriptor!(
256 "xai.grok-4.6",
257 "Grok 4.6",
258 "500K",
259 None,
260 Converse,
261 true,
262 "model-card-xai-grok-4-6"
263 ),
264 descriptor!(
265 "moonshotai.kimi-k3",
266 "Kimi K3",
267 "1M",
268 None,
269 ChatCompletions,
270 true,
271 "model-card-moonshot-ai-kimi-k3"
272 ),
273];
274
275pub fn bedrock_model_descriptor(model_id: &str) -> Option<&'static BedrockModelDescriptor> {
278 let base = ["us.", "eu.", "apac.", "au.", "in.", "jp.", "global."]
279 .iter()
280 .find_map(|prefix| model_id.strip_prefix(prefix))
281 .unwrap_or(model_id);
282 BEDROCK_MODEL_CATALOG
283 .iter()
284 .find(|model| model.model_id == base)
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290 use crate::model::{BedrockModel, InferenceProfile, Model};
291 use std::collections::HashSet;
292
293 #[test]
294 fn catalog_ids_are_unique_and_constructible_without_implicit_routing() {
295 assert_eq!(BEDROCK_MODEL_CATALOG.len(), 22);
296 let mut seen = HashSet::new();
297 for descriptor in BEDROCK_MODEL_CATALOG {
298 assert!(seen.insert(descriptor.model_id));
299 let configured = descriptor.with_token_limits(128_000, 4096).unwrap();
300 assert_eq!(configured.bedrock_id(), descriptor.model_id);
301 assert_eq!(configured.name(), descriptor.display_name);
302 assert_eq!(
303 configured.default_inference_profile(),
304 InferenceProfile::None
305 );
306 assert!(descriptor
307 .model_card
308 .starts_with("https://docs.aws.amazon.com/bedrock/"));
309 }
310 }
311
312 #[test]
313 fn unknown_limits_and_unmapped_aliases_stay_unknown() {
314 assert!(bedrock_model_descriptor("openai.gpt-5.6-sol")
315 .unwrap()
316 .max_output_label
317 .is_none());
318 for alias in [
319 "auto",
320 "agi-nova-beta-1m",
321 "kirin-inference-glm",
322 "unknown.model",
323 ] {
324 assert!(bedrock_model_descriptor(alias).is_none());
325 }
326 }
327
328 #[test]
329 fn profile_lookup_does_not_substitute_models_or_infer_arns() {
330 assert_eq!(
331 bedrock_model_descriptor("us.anthropic.claude-opus-5")
332 .unwrap()
333 .display_name,
334 "Claude Opus 5.0"
335 );
336 assert!(bedrock_model_descriptor(
337 "arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/example"
338 )
339 .is_none());
340 assert_eq!(
341 bedrock_model_descriptor("moonshotai.kimi-k3")
342 .unwrap()
343 .conversation_api,
344 BedrockConversationApi::ChatCompletions
345 );
346 }
347}