use std::{
collections::{BTreeMap, HashMap},
sync::LazyLock,
};
use enumset::{EnumSet, EnumSetType, enum_set};
use crate::{
capabilities::{
MediaKind, MediaSupport, ModelCapabilities, ReasoningCapability, ReasoningMode,
ReasoningParamConflicts,
},
config::ReasoningEffort,
media::SourceKind,
};
#[derive(EnumSetType, Debug)]
pub(super) enum ChatFeature {
Streaming,
JsonSchema,
LatencyOptimized,
ExtendedCacheTtl,
}
#[derive(Debug, Clone)]
pub(super) struct ChatModelCapabilities {
pub(super) context_window: u32,
pub(super) features: EnumSet<ChatFeature>,
pub(super) reasoning: Option<ReasoningCapability>,
}
pub(super) const fn bedrock_reasoning_conflicts() -> ReasoningParamConflicts {
ReasoningParamConflicts {
temperature_forbidden: true,
top_k_forbidden: true,
top_p_allowed_range: Some(0.95..=1.0),
}
}
pub(super) const fn bedrock_claude_opus_4_7_reasoning() -> ReasoningCapability {
ReasoningCapability {
supported_modes: enum_set!(ReasoningMode::Adaptive),
supported_efforts: enum_set!(
ReasoningEffort::Low
| ReasoningEffort::Medium
| ReasoningEffort::High
| ReasoningEffort::XHigh
| ReasoningEffort::Max
),
manual_budget_range: None,
conflicts: bedrock_reasoning_conflicts(),
sampling_params_removed: true,
}
}
pub(super) const fn bedrock_claude_sonnet_4_6_reasoning() -> ReasoningCapability {
ReasoningCapability {
supported_modes: enum_set!(ReasoningMode::Adaptive | ReasoningMode::Manual),
supported_efforts: enum_set!(
ReasoningEffort::Low
| ReasoningEffort::Medium
| ReasoningEffort::High
| ReasoningEffort::Max
),
manual_budget_range: Some(1024..=63_000),
conflicts: bedrock_reasoning_conflicts(),
sampling_params_removed: false,
}
}
pub(super) const fn bedrock_claude_sonnet_5_reasoning() -> ReasoningCapability {
ReasoningCapability {
supported_modes: enum_set!(ReasoningMode::Adaptive),
supported_efforts: enum_set!(
ReasoningEffort::Low
| ReasoningEffort::Medium
| ReasoningEffort::High
| ReasoningEffort::XHigh
| ReasoningEffort::Max
),
manual_budget_range: None,
conflicts: bedrock_reasoning_conflicts(),
sampling_params_removed: true,
}
}
pub(super) const fn bedrock_claude_opus_4_6_reasoning() -> ReasoningCapability {
ReasoningCapability {
supported_modes: enum_set!(ReasoningMode::Adaptive | ReasoningMode::Manual),
supported_efforts: enum_set!(
ReasoningEffort::Low
| ReasoningEffort::Medium
| ReasoningEffort::High
| ReasoningEffort::Max
),
manual_budget_range: Some(1024..=127_000),
conflicts: bedrock_reasoning_conflicts(),
sampling_params_removed: false,
}
}
pub(super) const fn bedrock_claude_manual_only_reasoning(
max_output_tokens: u32,
) -> ReasoningCapability {
let upper = max_output_tokens.saturating_sub(1024);
ReasoningCapability {
supported_modes: enum_set!(ReasoningMode::Manual),
supported_efforts: enum_set!(
ReasoningEffort::Low | ReasoningEffort::Medium | ReasoningEffort::High
),
manual_budget_range: Some(1024..=upper),
conflicts: bedrock_reasoning_conflicts(),
sampling_params_removed: false,
}
}
pub(super) const fn gpt_oss_reasoning() -> ReasoningCapability {
ReasoningCapability {
supported_modes: enum_set!(ReasoningMode::Adaptive),
supported_efforts: enum_set!(
ReasoningEffort::Low | ReasoningEffort::Medium | ReasoningEffort::High
),
manual_budget_range: None,
conflicts: ReasoningParamConflicts {
temperature_forbidden: false,
top_k_forbidden: false,
top_p_allowed_range: None,
},
sampling_params_removed: false,
}
}
pub(super) static MODEL_CAPABILITIES: LazyLock<HashMap<&'static str, ChatModelCapabilities>> =
LazyLock::new(|| {
let mut m = HashMap::new();
let claude_base = |reasoning: Option<ReasoningCapability>| ChatModelCapabilities {
context_window: 200_000,
features: ChatFeature::Streaming | ChatFeature::JsonSchema,
reasoning,
};
for id in [
"anthropic.claude-sonnet-4-5-20250929-v1:0",
"anthropic.claude-opus-4-1-20250805-v1:0",
"anthropic.claude-opus-4-5-20251101-v1:0",
] {
m.insert(
id,
claude_base(Some(bedrock_claude_manual_only_reasoning(64_000))),
);
}
m.insert(
"anthropic.claude-haiku-4-5-20251001-v1:0",
claude_base(Some(bedrock_claude_manual_only_reasoning(64_000))),
);
m.insert(
"anthropic.claude-sonnet-4-6",
claude_base(Some(bedrock_claude_sonnet_4_6_reasoning())),
);
m.insert(
"anthropic.claude-sonnet-5",
claude_base(Some(bedrock_claude_sonnet_5_reasoning())),
);
m.insert(
"anthropic.claude-opus-4-6-v1",
claude_base(Some(bedrock_claude_opus_4_6_reasoning())),
);
m.insert(
"anthropic.claude-opus-4-7",
claude_base(Some(bedrock_claude_opus_4_7_reasoning())),
);
m.insert("amazon.nova-pro-v1:0", chat(300_000));
m.insert("amazon.nova-pro-v1:0:24k", chat(24_000));
m.insert("amazon.nova-pro-v1:0:300k", chat(300_000));
m.insert("amazon.nova-lite-v1:0", chat(300_000));
m.insert("amazon.nova-lite-v1:0:24k", chat(24_000));
m.insert("amazon.nova-lite-v1:0:300k", chat(300_000));
m.insert("amazon.nova-micro-v1:0", chat(128_000));
m.insert("amazon.nova-micro-v1:0:24k", chat(24_000));
m.insert("amazon.nova-micro-v1:0:128k", chat(128_000));
m.insert("amazon.nova-2-lite-v1:0", chat(256_000));
m.insert("amazon.nova-2-lite-v1:0:256k", chat(256_000));
for id in [
"meta.llama3-1-8b-instruct-v1:0",
"meta.llama3-1-70b-instruct-v1:0",
"meta.llama3-1-405b-instruct-v1:0",
"meta.llama3-3-70b-instruct-v1:0",
"meta.llama4-scout-17b-instruct-v1:0",
"meta.llama4-maverick-17b-instruct-v1:0",
] {
m.insert(id, chat(128_000));
}
for id in [
"meta.llama3-8b-instruct-v1:0",
"meta.llama3-70b-instruct-v1:0",
] {
m.insert(id, chat(8_000));
}
for id in [
"mistral.mistral-7b-instruct-v0:2",
"mistral.mixtral-8x7b-instruct-v0:1",
"mistral.mistral-large-2402-v1:0",
"mistral.mistral-small-2402-v1:0",
] {
m.insert(id, chat(32_000));
}
for id in [
"mistral.voxtral-mini-3b-2507",
"mistral.voxtral-small-24b-2507",
"mistral.pixtral-large-2502-v1:0",
"mistral.devstral-2-123b",
"mistral.magistral-small-2509",
"mistral.ministral-3-3b-instruct",
"mistral.ministral-3-8b-instruct",
"mistral.ministral-3-14b-instruct",
"mistral.mistral-large-3-675b-instruct",
] {
m.insert(id, chat(128_000));
}
m.insert("deepseek.r1-v1:0", chat(128_000));
m.insert("deepseek.v3.2", chat(128_000));
m.insert("ai21.jamba-1-5-large-v1:0", chat(256_000));
m.insert("ai21.jamba-1-5-mini-v1:0", chat(256_000));
for id in [
"openai.gpt-oss-20b-1:0",
"openai.gpt-oss-120b-1:0",
"openai.gpt-oss-safeguard-20b",
"openai.gpt-oss-safeguard-120b",
] {
m.insert(id, gpt_oss(128_000));
}
for id in [
"nvidia.nemotron-nano-9b-v2",
"nvidia.nemotron-nano-12b-v2",
"nvidia.nemotron-nano-3-30b",
"nvidia.nemotron-super-3-120b",
] {
m.insert(id, chat(128_000));
}
for id in [
"qwen.qwen3-32b-v1:0",
"qwen.qwen3-coder-30b-a3b-v1:0",
"qwen.qwen3-coder-next",
"qwen.qwen3-next-80b-a3b",
"qwen.qwen3-vl-235b-a22b",
"qwen.qwen3-235b-a22b-2507-v1:0",
] {
m.insert(id, chat(128_000));
}
for id in [
"google.gemma-3-4b-it",
"google.gemma-3-12b-it",
"google.gemma-3-27b-it",
] {
m.insert(id, chat(128_000));
}
for id in [
"writer.palmyra-x4-v1:0",
"writer.palmyra-x5-v1:0",
"writer.palmyra-vision-7b",
] {
m.insert(id, chat(128_000));
}
for id in ["zai.glm-4.7", "zai.glm-4.7-flash", "zai.glm-5"] {
m.insert(id, chat(128_000));
}
for id in [
"minimax.minimax-m2",
"minimax.minimax-m2.1",
"minimax.minimax-m2.5",
] {
m.insert(id, chat(128_000));
}
for id in ["moonshot.kimi-k2-thinking", "moonshotai.kimi-k2.5"] {
m.insert(id, chat(256_000));
}
m.insert("twelvelabs.pegasus-1-2-v1:0", chat(32_000));
for id in [
"amazon.nova-pro-v1:0",
"amazon.nova-pro-v1:0:24k",
"amazon.nova-pro-v1:0:300k",
"meta.llama3-1-70b-instruct-v1:0",
"meta.llama3-1-405b-instruct-v1:0",
] {
if let Some(caps) = m.get_mut(id) {
caps.features.insert(ChatFeature::LatencyOptimized);
}
}
for id in [
"anthropic.claude-sonnet-4-5-20250929-v1:0",
"anthropic.claude-opus-4-5-20251101-v1:0",
"anthropic.claude-haiku-4-5-20251001-v1:0",
"anthropic.claude-sonnet-4-6",
"anthropic.claude-sonnet-5",
"anthropic.claude-opus-4-6-v1",
"anthropic.claude-opus-4-7",
] {
if let Some(caps) = m.get_mut(id) {
caps.features.insert(ChatFeature::ExtendedCacheTtl);
}
}
m
});
const fn chat(context_window: u32) -> ChatModelCapabilities {
ChatModelCapabilities {
context_window,
features: enum_set!(ChatFeature::Streaming),
reasoning: None,
}
}
const fn gpt_oss(context_window: u32) -> ChatModelCapabilities {
ChatModelCapabilities {
context_window,
features: enum_set!(ChatFeature::Streaming),
reasoning: Some(gpt_oss_reasoning()),
}
}
pub(super) fn bedrock_media_support(foundation_id: &str) -> BTreeMap<MediaKind, MediaSupport> {
let bytes_or_s3 = enum_set!(SourceKind::InlineBytes | SourceKind::S3);
let mut m = BTreeMap::new();
if foundation_id.starts_with("anthropic.claude-") {
m.insert(
MediaKind::Image,
MediaSupport {
sources: bytes_or_s3,
formats: &["png", "jpeg", "gif", "webp"],
max_bytes: Some(3_932_160), max_count_per_message: None,
},
);
m.insert(
MediaKind::Document,
MediaSupport {
sources: bytes_or_s3,
formats: &[
"pdf", "csv", "doc", "docx", "xls", "xlsx", "html", "txt", "md",
],
max_bytes: Some(4_718_592), max_count_per_message: None,
},
);
return m;
}
if matches!(
foundation_id,
"amazon.nova-pro-v1:0"
| "amazon.nova-pro-v1:0:24k"
| "amazon.nova-pro-v1:0:300k"
| "amazon.nova-lite-v1:0"
| "amazon.nova-lite-v1:0:24k"
| "amazon.nova-lite-v1:0:300k"
| "amazon.nova-2-lite-v1:0"
| "amazon.nova-2-lite-v1:0:256k"
) {
m.insert(
MediaKind::Image,
MediaSupport {
sources: bytes_or_s3,
formats: &["png", "jpeg", "gif", "webp"],
max_bytes: Some(3_932_160),
max_count_per_message: None,
},
);
m.insert(
MediaKind::Document,
MediaSupport {
sources: bytes_or_s3,
formats: &[
"pdf", "csv", "doc", "docx", "xls", "xlsx", "html", "txt", "md",
],
max_bytes: Some(4_718_592),
max_count_per_message: None,
},
);
m.insert(
MediaKind::Video,
MediaSupport {
sources: bytes_or_s3,
formats: &[
"mp4", "mov", "mkv", "webm", "flv", "mpeg", "mpg", "wmv", "three_gp",
],
max_bytes: Some(25 * 1024 * 1024),
max_count_per_message: None,
},
);
return m;
}
if foundation_id.starts_with("mistral.voxtral-") {
m.insert(
MediaKind::Audio,
MediaSupport {
sources: bytes_or_s3,
formats: &["wav", "mpeg", "mp3", "flac"],
max_bytes: Some(25 * 1024 * 1024),
max_count_per_message: None,
},
);
return m;
}
if matches!(
foundation_id,
"mistral.pixtral-large-2502-v1:0"
| "meta.llama4-scout-17b-instruct-v1:0"
| "meta.llama4-maverick-17b-instruct-v1:0"
| "qwen.qwen3-vl-235b-a22b"
| "writer.palmyra-vision-7b"
| "google.gemma-3-4b-it"
| "google.gemma-3-12b-it"
| "google.gemma-3-27b-it"
| "nvidia.nemotron-nano-12b-v2"
) {
m.insert(
MediaKind::Image,
MediaSupport {
sources: bytes_or_s3,
formats: &["png", "jpeg", "gif", "webp"],
max_bytes: Some(3_932_160),
max_count_per_message: None,
},
);
return m;
}
m
}
pub(super) fn bedrock_model_capabilities(
model_id: &str,
foundation_id: &str,
caps: &ChatModelCapabilities,
) -> ModelCapabilities {
ModelCapabilities {
model_id: model_id.to_owned(),
media_support: bedrock_media_support(foundation_id),
reasoning: caps.reasoning.clone(),
latency_optimized_supported: caps.features.contains(ChatFeature::LatencyOptimized),
extended_cache_ttl_supported: caps.features.contains(ChatFeature::ExtendedCacheTtl),
}
}