#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProviderCapabilities {
pub streaming_tool_calls: bool,
pub named_sse_events: bool,
pub reasoning_content: bool,
pub image_input: bool,
pub system_as_top_level: bool,
pub ndjson_streaming: bool,
pub tool_support: bool,
pub structured_output: bool,
pub requires_version_header: bool,
pub supports_parallel_tool_calls: bool,
pub max_context_tokens: Option<u32>,
pub tokenizer_name: Option<&'static str>,
}
impl ProviderCapabilities {
pub const fn openai_compatible() -> Self {
Self {
streaming_tool_calls: true,
named_sse_events: false,
reasoning_content: true,
image_input: true,
system_as_top_level: false,
ndjson_streaming: false,
tool_support: true,
structured_output: true,
requires_version_header: false,
supports_parallel_tool_calls: true,
max_context_tokens: None, tokenizer_name: None,
}
}
pub const fn anthropic() -> Self {
Self {
streaming_tool_calls: false, named_sse_events: true,
reasoning_content: false, image_input: true,
system_as_top_level: true,
ndjson_streaming: false,
tool_support: true,
structured_output: false, requires_version_header: true,
supports_parallel_tool_calls: true,
max_context_tokens: Some(200_000), tokenizer_name: Some("claude"),
}
}
pub const fn ollama() -> Self {
Self {
streaming_tool_calls: false,
named_sse_events: false,
reasoning_content: false,
image_input: false,
system_as_top_level: false,
ndjson_streaming: true,
tool_support: true,
structured_output: false,
requires_version_header: false,
supports_parallel_tool_calls: false,
max_context_tokens: None, tokenizer_name: None,
}
}
pub fn from_provider_name(name: &str) -> Self {
match name.to_ascii_lowercase().as_str() {
"anthropic" => Self::anthropic(),
"ollama" => Self::ollama(),
_ => Self::openai_compatible(),
}
}
}
#[derive(Debug, Clone)]
pub struct ModelProfile {
pub provider: String,
pub model_name: String,
pub capabilities: ProviderCapabilities,
pub supports_reasoning: bool,
pub supports_images: bool,
pub supports_tools: bool,
pub max_output_tokens: Option<u32>,
pub supports_streaming: bool,
pub supports_parallel_tool_calls: bool,
pub max_context_tokens: Option<u32>,
pub tokenizer_name: Option<&'static str>,
}
impl ModelProfile {
pub fn new(model_name: &str, provider: &str, capabilities: ProviderCapabilities) -> Self {
let lower = model_name.to_ascii_lowercase();
let supports_reasoning = capabilities.reasoning_content
&& (lower.starts_with("qwen3-")
|| lower.starts_with("qwen-")
|| lower.starts_with("deepseek-")
|| lower.starts_with("o1")
|| lower.starts_with("o3")
|| lower.starts_with("o4"));
let supports_images = capabilities.image_input
&& !lower.starts_with("o1") && !lower.starts_with("o3");
let max_context_tokens = if lower.contains("qwen3-235b") {
Some(131_072)
} else if lower.starts_with("gpt-4o") || lower.starts_with("gpt-4.5") {
Some(128_000)
} else if lower.starts_with("gpt-4") && !lower.starts_with("gpt-4o") {
Some(8_192)
} else if lower.starts_with("claude-3-opus") {
Some(200_000)
} else if lower.starts_with("claude-3.5") || lower.starts_with("claude-4") {
Some(200_000)
} else if lower.starts_with("claude-") {
Some(200_000)
} else if lower.starts_with("deepseek-") {
Some(128_000)
} else if lower.starts_with("qwen-") {
Some(131_072)
} else {
capabilities.max_context_tokens
};
let max_output_tokens = if lower.contains("qwen3-235b") {
Some(131_072)
} else if lower.starts_with("gpt-4o") {
Some(16_384)
} else if lower.starts_with("claude-") {
Some(8_192)
} else {
None
};
let tokenizer_name = if lower.starts_with("gpt-4o") || lower.starts_with("gpt-4.5") {
Some("o200k_base")
} else if lower.starts_with("gpt-4") || lower.starts_with("gpt-3") {
Some("cl100k_base")
} else {
capabilities.tokenizer_name
};
Self {
provider: provider.to_string(),
model_name: model_name.to_string(),
capabilities,
supports_reasoning,
supports_images,
supports_tools: capabilities.tool_support,
max_output_tokens,
supports_streaming: true, supports_parallel_tool_calls: capabilities.supports_parallel_tool_calls,
max_context_tokens,
tokenizer_name,
}
}
pub fn from_provider_name(model_name: &str, provider: &str) -> Self {
let capabilities = ProviderCapabilities::from_provider_name(provider);
Self::new(model_name, provider, capabilities)
}
}