mod context;
mod pricing;
pub use context::{DEFAULT_CONTEXT_WINDOW, context_window};
pub use pricing::{Pricing, pricing};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ProviderId {
OpenRouter,
Fireworks,
Bedrock,
Anthropic,
OpenAI,
Together,
AtlasCloud,
Local,
}
impl ProviderId {
pub fn as_str(self) -> &'static str {
match self {
Self::OpenRouter => "openrouter",
Self::Fireworks => "fireworks",
Self::Bedrock => "bedrock",
Self::Anthropic => "anthropic",
Self::OpenAI => "openai",
Self::Together => "together",
Self::AtlasCloud => "atlascloud",
Self::Local => "local",
}
}
pub fn credential_name(self) -> Option<&'static str> {
match self {
Self::Bedrock | Self::Local => None,
other => Some(other.as_str()),
}
}
pub fn from_slug_prefix(slug: &str) -> Option<Self> {
let prefix = slug.split('/').next()?;
match prefix.to_ascii_lowercase().as_str() {
"openrouter" => Some(Self::OpenRouter),
"fireworks" => Some(Self::Fireworks),
"bedrock" => Some(Self::Bedrock),
"anthropic" => Some(Self::Anthropic),
"openai" => Some(Self::OpenAI),
"together" => Some(Self::Together),
"atlascloud" => Some(Self::AtlasCloud),
"local" | "ollama" => Some(Self::Local),
_ => None,
}
}
}
impl fmt::Display for ProviderId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolDialect {
OpenAiFunctions,
AnthropicMessages,
PromptEmulated,
}
#[derive(Debug, Clone, Copy)]
pub struct ProviderCapabilities {
pub id: ProviderId,
pub native_tool_calling: bool,
pub tool_dialect: ToolDialect,
pub streaming: bool,
pub prompt_caching: bool,
pub structured_output: bool,
pub vision: bool,
pub detailed_usage_accounting: bool,
pub max_context_window: usize,
pub default_model: &'static str,
pub credential_env: Option<&'static str>,
}
const SEED: [ProviderCapabilities; 8] = [
ProviderCapabilities {
id: ProviderId::OpenRouter,
native_tool_calling: true,
tool_dialect: ToolDialect::OpenAiFunctions,
streaming: true,
prompt_caching: true,
structured_output: true,
vision: true,
detailed_usage_accounting: true,
max_context_window: 200_000,
default_model: "openai/gpt-4o-mini",
credential_env: Some("OPENROUTER_API_KEY"),
},
ProviderCapabilities {
id: ProviderId::Fireworks,
native_tool_calling: true,
tool_dialect: ToolDialect::OpenAiFunctions,
streaming: true,
prompt_caching: false,
structured_output: true,
vision: false,
detailed_usage_accounting: false,
max_context_window: 128_000,
default_model: "accounts/fireworks/models/llama-v3p1-70b-instruct",
credential_env: Some("FIREWORKS_API_KEY"),
},
ProviderCapabilities {
id: ProviderId::Bedrock,
native_tool_calling: true,
tool_dialect: ToolDialect::AnthropicMessages,
streaming: true,
prompt_caching: false,
structured_output: true,
vision: true,
detailed_usage_accounting: false,
max_context_window: 200_000,
default_model: "bedrock/us.anthropic.claude-sonnet-4-5",
credential_env: None,
},
ProviderCapabilities {
id: ProviderId::Anthropic,
native_tool_calling: true,
tool_dialect: ToolDialect::AnthropicMessages,
streaming: true,
prompt_caching: true,
structured_output: true,
vision: true,
detailed_usage_accounting: false,
max_context_window: 200_000,
default_model: "claude-sonnet-4-5",
credential_env: Some("ANTHROPIC_API_KEY"),
},
ProviderCapabilities {
id: ProviderId::OpenAI,
native_tool_calling: true,
tool_dialect: ToolDialect::OpenAiFunctions,
streaming: true,
prompt_caching: true,
structured_output: true,
vision: true,
detailed_usage_accounting: false,
max_context_window: 128_000,
default_model: "gpt-4o-mini",
credential_env: Some("OPENAI_API_KEY"),
},
ProviderCapabilities {
id: ProviderId::Together,
native_tool_calling: true,
tool_dialect: ToolDialect::OpenAiFunctions,
streaming: true,
prompt_caching: true,
structured_output: true,
vision: false,
detailed_usage_accounting: false,
max_context_window: 128_000,
default_model: "meta-llama/Llama-3.3-70B-Instruct-Turbo",
credential_env: Some("TOGETHER_API_KEY"),
},
ProviderCapabilities {
id: ProviderId::AtlasCloud,
native_tool_calling: true,
tool_dialect: ToolDialect::OpenAiFunctions,
streaming: true,
prompt_caching: true,
structured_output: true,
vision: false,
detailed_usage_accounting: false,
max_context_window: 1_050_000,
default_model: "openai/gpt-5.6-sol",
credential_env: Some("ATLASCLOUD_API_KEY"),
},
ProviderCapabilities {
id: ProviderId::Local,
native_tool_calling: true,
tool_dialect: ToolDialect::OpenAiFunctions,
streaming: true,
prompt_caching: false,
structured_output: false,
vision: false,
detailed_usage_accounting: false,
max_context_window: 128_000,
default_model: "llama3.1",
credential_env: None,
},
];
pub fn capabilities(id: ProviderId) -> &'static ProviderCapabilities {
SEED.iter().find(|c| c.id == id).unwrap_or(&SEED[0])
}
pub fn capabilities_for(name: &str) -> Option<&'static ProviderCapabilities> {
let lower = name.to_ascii_lowercase();
SEED.iter().find(|c| c.id.as_str() == lower)
}
pub fn all() -> &'static [ProviderCapabilities] {
&SEED
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn provider_id_round_trips() {
for id in [
ProviderId::OpenRouter,
ProviderId::Fireworks,
ProviderId::Bedrock,
ProviderId::Anthropic,
ProviderId::OpenAI,
ProviderId::Together,
ProviderId::AtlasCloud,
ProviderId::Local,
] {
assert_eq!(id.to_string(), id.as_str());
assert_eq!(ProviderId::from_slug_prefix(&format!("{id}/x")), Some(id));
}
}
#[test]
fn from_slug_prefix_matches() {
assert_eq!(
ProviderId::from_slug_prefix("anthropic/claude-sonnet-4-5"),
Some(ProviderId::Anthropic)
);
assert_eq!(
ProviderId::from_slug_prefix("BEDROCK/us.anthropic.claude"),
Some(ProviderId::Bedrock)
);
}
#[test]
fn from_slug_prefix_unknown_is_none() {
assert_eq!(ProviderId::from_slug_prefix("claude-sonnet-4-5"), None);
assert_eq!(ProviderId::from_slug_prefix("cohere/command"), None);
}
#[test]
fn credential_name_none_for_bedrock_and_local() {
assert_eq!(ProviderId::Bedrock.credential_name(), None);
assert_eq!(ProviderId::Local.credential_name(), None);
assert_eq!(ProviderId::OpenRouter.credential_name(), Some("openrouter"));
assert_eq!(ProviderId::Anthropic.credential_name(), Some("anthropic"));
}
#[test]
fn all_providers_seeded() {
assert_eq!(all().len(), 8);
for id in [
ProviderId::OpenRouter,
ProviderId::Fireworks,
ProviderId::Bedrock,
ProviderId::Anthropic,
ProviderId::OpenAI,
ProviderId::Together,
ProviderId::AtlasCloud,
ProviderId::Local,
] {
let caps = capabilities(id);
assert_eq!(caps.id, id);
assert!(caps.max_context_window >= 128_000);
}
}
#[test]
fn local_seeded_with_openai_compat_posture() {
assert_eq!(
ProviderId::from_slug_prefix("local/llama3.1"),
Some(ProviderId::Local)
);
assert_eq!(
ProviderId::from_slug_prefix("ollama/qwen3:30b"),
Some(ProviderId::Local)
);
assert_eq!(
ProviderId::from_slug_prefix("OLLAMA/llama3.1"),
Some(ProviderId::Local)
);
let caps = capabilities(ProviderId::Local);
assert_eq!(caps.id, ProviderId::Local);
assert!(caps.native_tool_calling);
assert_eq!(caps.tool_dialect, ToolDialect::OpenAiFunctions);
assert_eq!(caps.credential_env, None);
assert_eq!(
capabilities_for("Local").map(|c| c.id),
Some(ProviderId::Local)
);
assert_eq!(ProviderId::Local.credential_name(), None);
}
#[test]
fn together_seeded_with_openai_compat_posture() {
assert_eq!(
ProviderId::from_slug_prefix("together/meta-llama/Llama-3.3-70B-Instruct-Turbo"),
Some(ProviderId::Together)
);
let caps = capabilities(ProviderId::Together);
assert_eq!(caps.id, ProviderId::Together);
assert!(caps.native_tool_calling);
assert_eq!(caps.tool_dialect, ToolDialect::OpenAiFunctions);
assert_eq!(caps.credential_env, Some("TOGETHER_API_KEY"));
assert_eq!(
capabilities_for("Together").map(|c| c.id),
Some(ProviderId::Together)
);
assert_eq!(ProviderId::Together.credential_name(), Some("together"));
}
#[test]
fn atlascloud_seeded_with_openai_compat_posture() {
assert_eq!(
ProviderId::from_slug_prefix("atlascloud/openai/gpt-5.6-sol"),
Some(ProviderId::AtlasCloud)
);
assert_eq!(
ProviderId::from_slug_prefix("atlascloud/deepseek-v3"),
Some(ProviderId::AtlasCloud)
);
let caps = capabilities(ProviderId::AtlasCloud);
assert_eq!(caps.id, ProviderId::AtlasCloud);
assert!(caps.native_tool_calling);
assert_eq!(caps.tool_dialect, ToolDialect::OpenAiFunctions);
assert!(!caps.detailed_usage_accounting);
assert_eq!(caps.credential_env, Some("ATLASCLOUD_API_KEY"));
assert_eq!(caps.default_model, "openai/gpt-5.6-sol");
assert_eq!(caps.max_context_window, 1_050_000);
assert_eq!(
capabilities_for("AtlasCloud").map(|c| c.id),
Some(ProviderId::AtlasCloud)
);
assert_eq!(ProviderId::AtlasCloud.credential_name(), Some("atlascloud"));
}
#[test]
fn capabilities_for_by_name() {
assert_eq!(
capabilities_for("OpenRouter").map(|c| c.id),
Some(ProviderId::OpenRouter)
);
assert_eq!(
capabilities_for("bedrock").map(|c| c.id),
Some(ProviderId::Bedrock)
);
}
#[test]
fn capabilities_for_unknown_is_none() {
assert!(capabilities_for("cohere").is_none());
}
}