use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::{BTreeMap, BTreeSet};
use std::sync::{OnceLock, RwLock};
static CONFIG: OnceLock<ProvidersConfig> = OnceLock::new();
static CONFIG_PATH: OnceLock<String> = OnceLock::new();
static RUNTIME_CATALOG_OVERLAY: OnceLock<RwLock<Option<ProvidersConfig>>> = OnceLock::new();
thread_local! {
static USER_OVERRIDES: RefCell<Option<ProvidersConfig>> = const { RefCell::new(None) };
}
#[derive(Debug, Clone, Deserialize, Default)]
pub struct ProvidersConfig {
#[serde(default)]
pub default_provider: Option<String>,
#[serde(default)]
pub providers: BTreeMap<String, ProviderDef>,
#[serde(default)]
pub aliases: BTreeMap<String, AliasDef>,
#[serde(default)]
pub alias_tool_calling: BTreeMap<String, AliasToolCallingDef>,
#[serde(default)]
pub models: BTreeMap<String, ModelDef>,
#[serde(default)]
pub qc_defaults: BTreeMap<String, String>,
#[serde(default)]
pub inference_rules: Vec<InferenceRule>,
#[serde(default)]
pub tier_rules: Vec<TierRule>,
#[serde(default)]
pub tier_defaults: TierDefaults,
#[serde(default)]
pub model_defaults: BTreeMap<String, BTreeMap<String, toml::Value>>,
#[serde(default)]
pub model_roles: BTreeMap<String, BTreeMap<String, toml::Value>>,
}
impl ProvidersConfig {
pub fn is_empty(&self) -> bool {
self.default_provider.is_none()
&& self.providers.is_empty()
&& self.aliases.is_empty()
&& self.alias_tool_calling.is_empty()
&& self.models.is_empty()
&& self.qc_defaults.is_empty()
&& self.inference_rules.is_empty()
&& self.tier_rules.is_empty()
&& self.model_defaults.is_empty()
&& self.model_roles.is_empty()
&& self.tier_defaults.default == default_mid()
}
pub fn merge_from(&mut self, overlay: &ProvidersConfig) {
for (name, provider) in &overlay.providers {
match self.providers.get_mut(name) {
Some(existing) => existing.merge_from(provider),
None => {
self.providers.insert(name.clone(), provider.clone());
}
}
}
self.aliases.extend(overlay.aliases.clone());
self.alias_tool_calling
.extend(overlay.alias_tool_calling.clone());
self.models.extend(overlay.models.clone());
self.qc_defaults.extend(overlay.qc_defaults.clone());
if overlay.default_provider.is_some() {
self.default_provider = overlay.default_provider.clone();
}
if !overlay.inference_rules.is_empty() {
let mut merged = overlay.inference_rules.clone();
merged.extend(self.inference_rules.clone());
self.inference_rules = merged;
}
if !overlay.tier_rules.is_empty() {
let mut merged = overlay.tier_rules.clone();
merged.extend(self.tier_rules.clone());
self.tier_rules = merged;
}
if overlay.tier_defaults.default != default_mid() {
self.tier_defaults = overlay.tier_defaults.clone();
}
for (pattern, defaults) in &overlay.model_defaults {
self.model_defaults
.entry(pattern.clone())
.or_default()
.extend(defaults.clone());
}
for (role, defaults) in &overlay.model_roles {
self.model_roles
.entry(role.clone())
.or_default()
.extend(defaults.clone());
}
}
}
#[derive(Debug, Clone)]
pub struct ProviderDef {
pub display_name: Option<String>,
pub icon: Option<String>,
pub protocol: Option<String>,
pub base_url: String,
pub base_url_env: Option<String>,
pub auth_style: String,
pub auth_header: Option<String>,
pub auth_env: AuthEnv,
pub extra_headers: BTreeMap<String, String>,
pub chat_endpoint: String,
pub completion_endpoint: Option<String>,
pub command: Option<String>,
pub args: Vec<String>,
pub env: BTreeMap<String, String>,
pub cwd: Option<String>,
pub mcp_servers: Vec<serde_json::Value>,
pub healthcheck: Option<HealthcheckDef>,
pub features: Vec<String>,
pub fallback: Option<String>,
pub retry_count: Option<u32>,
pub retry_delay_ms: Option<u64>,
pub rpm: Option<u32>,
pub cost_per_1k_in: Option<f64>,
pub cost_per_1k_out: Option<f64>,
pub latency_p50_ms: Option<u64>,
#[doc(hidden)]
pub auth_style_explicit: bool,
}
#[derive(Debug, Clone, Deserialize)]
struct ProviderDefWire {
#[serde(default)]
display_name: Option<String>,
#[serde(default)]
icon: Option<String>,
#[serde(default)]
protocol: Option<String>,
#[serde(default)]
base_url: String,
#[serde(default)]
base_url_env: Option<String>,
#[serde(default)]
auth_style: Option<String>,
#[serde(default)]
auth_header: Option<String>,
#[serde(default)]
auth_env: AuthEnv,
#[serde(default)]
extra_headers: BTreeMap<String, String>,
#[serde(default)]
chat_endpoint: String,
#[serde(default)]
completion_endpoint: Option<String>,
#[serde(default)]
command: Option<String>,
#[serde(default)]
args: Vec<String>,
#[serde(default)]
env: BTreeMap<String, String>,
#[serde(default)]
cwd: Option<String>,
#[serde(default)]
mcp_servers: Vec<serde_json::Value>,
#[serde(default)]
healthcheck: Option<HealthcheckDef>,
#[serde(default)]
features: Vec<String>,
#[serde(default)]
fallback: Option<String>,
#[serde(default)]
retry_count: Option<u32>,
#[serde(default)]
retry_delay_ms: Option<u64>,
#[serde(default)]
rpm: Option<u32>,
#[serde(default)]
cost_per_1k_in: Option<f64>,
#[serde(default)]
cost_per_1k_out: Option<f64>,
#[serde(default)]
latency_p50_ms: Option<u64>,
}
impl<'de> Deserialize<'de> for ProviderDef {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let wire = ProviderDefWire::deserialize(deserializer)?;
let auth_style_explicit = wire.auth_style.is_some();
Ok(Self {
display_name: wire.display_name,
icon: wire.icon,
protocol: wire.protocol,
base_url: wire.base_url,
base_url_env: wire.base_url_env,
auth_style: wire.auth_style.unwrap_or_else(default_bearer),
auth_header: wire.auth_header,
auth_env: wire.auth_env,
extra_headers: wire.extra_headers,
chat_endpoint: wire.chat_endpoint,
completion_endpoint: wire.completion_endpoint,
command: wire.command,
args: wire.args,
env: wire.env,
cwd: wire.cwd,
mcp_servers: wire.mcp_servers,
healthcheck: wire.healthcheck,
features: wire.features,
fallback: wire.fallback,
retry_count: wire.retry_count,
retry_delay_ms: wire.retry_delay_ms,
rpm: wire.rpm,
cost_per_1k_in: wire.cost_per_1k_in,
cost_per_1k_out: wire.cost_per_1k_out,
latency_p50_ms: wire.latency_p50_ms,
auth_style_explicit,
})
}
}
impl Default for ProviderDef {
fn default() -> Self {
Self {
display_name: None,
icon: None,
protocol: None,
base_url: String::new(),
base_url_env: None,
auth_style: default_bearer(),
auth_header: None,
auth_env: AuthEnv::None,
extra_headers: BTreeMap::new(),
chat_endpoint: String::new(),
completion_endpoint: None,
command: None,
args: Vec::new(),
env: BTreeMap::new(),
cwd: None,
mcp_servers: Vec::new(),
healthcheck: None,
features: Vec::new(),
fallback: None,
retry_count: None,
retry_delay_ms: None,
rpm: None,
cost_per_1k_in: None,
cost_per_1k_out: None,
latency_p50_ms: None,
auth_style_explicit: false,
}
}
}
impl ProviderDef {
fn merge_from(&mut self, overlay: &ProviderDef) {
merge_option(&mut self.display_name, &overlay.display_name);
merge_option(&mut self.icon, &overlay.icon);
merge_option(&mut self.protocol, &overlay.protocol);
merge_string(&mut self.base_url, &overlay.base_url);
merge_option(&mut self.base_url_env, &overlay.base_url_env);
let overlay_uses_default_auth_style = overlay.auth_style == default_bearer();
if overlay.auth_style_explicit
|| !overlay_uses_default_auth_style
|| self.auth_style == default_bearer()
{
self.auth_style = overlay.auth_style.clone();
self.auth_style_explicit |=
overlay.auth_style_explicit || !overlay_uses_default_auth_style;
}
merge_option(&mut self.auth_header, &overlay.auth_header);
if !overlay.auth_env.is_none() {
self.auth_env = overlay.auth_env.clone();
}
self.extra_headers.extend(overlay.extra_headers.clone());
merge_string(&mut self.chat_endpoint, &overlay.chat_endpoint);
merge_option(&mut self.completion_endpoint, &overlay.completion_endpoint);
merge_option(&mut self.command, &overlay.command);
merge_vec(&mut self.args, &overlay.args);
self.env.extend(overlay.env.clone());
merge_option(&mut self.cwd, &overlay.cwd);
merge_vec(&mut self.mcp_servers, &overlay.mcp_servers);
merge_option(&mut self.healthcheck, &overlay.healthcheck);
merge_vec(&mut self.features, &overlay.features);
merge_option(&mut self.fallback, &overlay.fallback);
merge_option(&mut self.retry_count, &overlay.retry_count);
merge_option(&mut self.retry_delay_ms, &overlay.retry_delay_ms);
merge_option(&mut self.rpm, &overlay.rpm);
merge_option(&mut self.cost_per_1k_in, &overlay.cost_per_1k_in);
merge_option(&mut self.cost_per_1k_out, &overlay.cost_per_1k_out);
merge_option(&mut self.latency_p50_ms, &overlay.latency_p50_ms);
}
}
fn merge_option<T: Clone>(base: &mut Option<T>, overlay: &Option<T>) {
if overlay.is_some() {
*base = overlay.clone();
}
}
fn merge_string(base: &mut String, overlay: &str) {
if !overlay.is_empty() {
*base = overlay.to_string();
}
}
fn merge_vec<T: Clone>(base: &mut Vec<T>, overlay: &[T]) {
if !overlay.is_empty() {
*base = overlay.to_vec();
}
}
fn default_bearer() -> String {
"bearer".to_string()
}
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(untagged)]
pub enum AuthEnv {
#[default]
None,
Single(String),
Multiple(Vec<String>),
}
impl AuthEnv {
fn is_none(&self) -> bool {
matches!(self, AuthEnv::None)
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct HealthcheckDef {
pub method: String,
#[serde(default)]
pub path: Option<String>,
#[serde(default)]
pub url: Option<String>,
#[serde(default)]
pub body: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct AliasDef {
pub id: String,
pub provider: String,
#[serde(default)]
pub tool_format: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct AliasToolCallingDef {
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub native: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub streaming_native: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub fallback_mode: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub failure_reason: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub last_probe_at: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct ModelPricing {
pub input_per_mtok: f64,
pub output_per_mtok: f64,
#[serde(default)]
pub cache_read_per_mtok: Option<f64>,
#[serde(default)]
pub cache_write_per_mtok: Option<f64>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct FastModeDef {
pub param: String,
pub value: String,
#[serde(default)]
pub beta_header: Option<String>,
#[serde(default)]
pub otps_speedup: Option<f64>,
#[serde(default)]
pub status: Option<String>,
#[serde(default)]
pub pricing: Option<ModelPricing>,
#[serde(default)]
pub note: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct ModelDef {
pub name: String,
pub provider: String,
pub context_window: u64,
#[serde(default)]
pub runtime_context_window: Option<u64>,
#[serde(default)]
pub stream_timeout: Option<f64>,
#[serde(default)]
pub capabilities: Vec<String>,
#[serde(default)]
pub pricing: Option<ModelPricing>,
#[serde(default)]
pub deprecated: bool,
#[serde(default)]
pub deprecation_note: Option<String>,
#[serde(default)]
pub superseded_by: Option<String>,
#[serde(default)]
pub fast_mode: Option<FastModeDef>,
#[serde(default)]
pub quality_tags: Vec<String>,
#[serde(default)]
pub availability: ModelAvailability,
#[serde(default)]
pub tier: Option<String>,
#[serde(default)]
pub open_weight: Option<bool>,
#[serde(default)]
pub strengths: Vec<String>,
#[serde(default)]
pub benchmarks: BTreeMap<String, f64>,
#[serde(default)]
pub family: Option<String>,
#[serde(default)]
pub lineage: Option<String>,
#[serde(default)]
pub complementary_with: Vec<String>,
#[serde(default)]
pub avoid_as_reviewer_for: Vec<String>,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum ModelAvailability {
#[default]
Serverless,
Dedicated,
Unknown,
}
impl ModelAvailability {
pub fn as_str(self) -> &'static str {
match self {
Self::Serverless => "serverless",
Self::Dedicated => "dedicated",
Self::Unknown => "unknown",
}
}
pub fn parse(value: &str) -> Option<Self> {
match value {
"serverless" => Some(Self::Serverless),
"dedicated" => Some(Self::Dedicated),
"unknown" => Some(Self::Unknown),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct ResolvedModel {
pub id: String,
pub provider: String,
pub alias: Option<String>,
pub tool_format: String,
pub tier: String,
pub family: String,
pub lineage: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ComplementaryReviewerOptions {
pub author_model: String,
pub author_provider: Option<String>,
pub intent: ComplementaryReviewerIntent,
pub max_price_multiplier: Option<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComplementaryReviewerIntent {
Review,
Critique,
PlanReview,
}
impl ComplementaryReviewerIntent {
pub fn parse(value: &str) -> Option<Self> {
match value {
"review" => Some(Self::Review),
"critique" => Some(Self::Critique),
"plan_review" => Some(Self::PlanReview),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::Review => "review",
Self::Critique => "critique",
Self::PlanReview => "plan_review",
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct ComplementaryReviewerSelection {
pub intent: String,
pub author: ComplementaryModelIdentity,
pub reviewer: ComplementaryModelIdentity,
pub fallback: bool,
pub fallback_reason: Option<String>,
pub reason: String,
pub estimated_incremental_cost: Option<ComplementaryCostEstimate>,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct ComplementaryModelIdentity {
pub id: String,
pub provider: String,
pub family: String,
pub lineage: String,
pub tier: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub pricing: Option<ModelPricing>,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct ComplementaryCostEstimate {
pub input_per_mtok: f64,
pub output_per_mtok: f64,
pub total_per_mtok: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub multiplier_vs_author: Option<f64>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct InferenceRule {
#[serde(default)]
pub pattern: Option<String>,
#[serde(default)]
pub contains: Option<String>,
#[serde(default)]
pub exact: Option<String>,
pub provider: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TierRule {
#[serde(default)]
pub pattern: Option<String>,
#[serde(default)]
pub contains: Option<String>,
#[serde(default)]
pub exact: Option<String>,
pub tier: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TierDefaults {
#[serde(default = "default_mid")]
pub default: String,
}
impl Default for TierDefaults {
fn default() -> Self {
Self {
default: default_mid(),
}
}
}
fn default_mid() -> String {
"mid".to_string()
}
pub fn load_config() -> &'static ProvidersConfig {
CONFIG.get_or_init(|| {
let mut config = default_config();
let verbose_config_logging = matches!(
std::env::var("HARN_VERBOSE_CONFIG").ok().as_deref(),
Some("1" | "true" | "TRUE" | "yes" | "YES")
) || matches!(
std::env::var("HARN_ACP_VERBOSE").ok().as_deref(),
Some("1" | "true" | "TRUE" | "yes" | "YES")
);
if let Ok(path) = std::env::var("HARN_PROVIDERS_CONFIG") {
if let Some(overlay) = read_external_config(&path, verbose_config_logging) {
config.merge_from(&overlay);
let _ = CONFIG_PATH.set(path);
return config;
}
}
if should_load_home_config() {
if let Some(home) = dirs_or_home() {
let path = format!("{home}/.config/harn/providers.toml");
if let Some(overlay) = read_external_config(&path, false) {
config.merge_from(&overlay);
let _ = CONFIG_PATH.set(path);
return config;
}
}
}
config
})
}
fn read_external_config(path: &str, verbose: bool) -> Option<ProvidersConfig> {
match std::fs::read_to_string(path) {
Ok(content) => match toml::from_str::<ProvidersConfig>(&content) {
Ok(config) => {
if verbose {
eprintln!(
"[llm_config] Loaded {} providers, {} aliases from {}",
config.providers.len(),
config.aliases.len(),
path
);
}
Some(config)
}
Err(error) => {
eprintln!("[llm_config] TOML parse error in {path}: {error}");
None
}
},
Err(error) => {
if verbose {
eprintln!("[llm_config] Cannot read {path}: {error}");
}
None
}
}
}
fn should_load_home_config() -> bool {
!cfg!(test)
}
pub fn parse_config_toml(src: &str) -> Result<ProvidersConfig, toml::de::Error> {
toml::from_str::<ProvidersConfig>(src)
}
pub fn loaded_config_path() -> Option<std::path::PathBuf> {
let _ = load_config();
CONFIG_PATH.get().map(std::path::PathBuf::from)
}
pub fn set_user_overrides(config: Option<ProvidersConfig>) {
USER_OVERRIDES.with(|cell| *cell.borrow_mut() = config);
}
pub fn clear_user_overrides() {
set_user_overrides(None);
}
pub fn set_runtime_catalog_overlay(config: Option<ProvidersConfig>) {
*runtime_catalog_overlay()
.write()
.expect("runtime catalog overlay poisoned") = config;
}
pub fn clear_runtime_catalog_overlay() {
set_runtime_catalog_overlay(None);
}
pub(crate) fn effective_config() -> ProvidersConfig {
let user_overrides = USER_OVERRIDES.with(|cell| cell.borrow().clone());
effective_config_with_user_overrides(user_overrides.as_ref())
}
pub(crate) fn effective_config_with_user_overrides(
user_overrides: Option<&ProvidersConfig>,
) -> ProvidersConfig {
let mut merged = load_config().clone();
if let Some(overlay) = runtime_catalog_overlay()
.read()
.expect("runtime catalog overlay poisoned")
.as_ref()
{
merged.merge_from(overlay);
}
if let Some(overlay) = user_overrides {
merged.merge_from(overlay);
}
merged
}
fn runtime_catalog_overlay() -> &'static RwLock<Option<ProvidersConfig>> {
RUNTIME_CATALOG_OVERLAY.get_or_init(|| RwLock::new(None))
}
pub fn resolve_model(alias: &str) -> (String, Option<String>) {
let config = effective_config();
if let Some(a) = config.aliases.get(alias) {
return (a.id.clone(), Some(a.provider.clone()));
}
(normalize_model_id(alias), None)
}
pub fn normalize_model_id(raw: &str) -> String {
for prefix in PROVIDER_SELECTOR_PREFIXES {
if let Some(stripped) = raw.strip_prefix(prefix) {
return stripped.to_string();
}
}
raw.to_string()
}
const PROVIDER_SELECTOR_PREFIXES: &[&str] =
&["ollama:", "local:", "huggingface:", "hf:", "cerebras/"];
pub fn resolve_model_info(selector: &str) -> ResolvedModel {
let config = effective_config();
if let Some(alias) = config.aliases.get(selector) {
let id = alias.id.clone();
let provider = alias.provider.clone();
let tool_format = alias
.tool_format
.clone()
.unwrap_or_else(|| default_tool_format_with_config(&config, &id, &provider));
return ResolvedModel {
tier: model_tier_with_config(&config, &id),
family: model_family_with_config(&config, &provider, &id),
lineage: model_lineage_with_config(&config, &provider, &id),
id,
provider,
alias: Some(selector.to_string()),
tool_format,
};
}
let id = normalize_model_id(selector);
let inference = infer_provider_with_config(&config, selector);
let source = inference.source;
let provider = inference.provider;
let tool_format = default_tool_format_with_config(&config, &id, &provider);
let tier = model_tier_with_config(&config, &id);
let family = model_family_with_inference_source(&config, &provider, &id, source);
let lineage = model_lineage_with_inference_source(&config, &provider, &id, source);
ResolvedModel {
id,
provider,
alias: None,
tool_format,
tier,
family,
lineage,
}
}
pub fn infer_provider(model_id: &str) -> String {
infer_provider_detail(model_id).provider
}
pub(crate) fn infer_provider_detail(model_id: &str) -> crate::llm::provider::ProviderInference {
let config = effective_config();
infer_provider_with_config(&config, model_id)
}
fn infer_provider_with_config(
config: &ProvidersConfig,
model_id: &str,
) -> crate::llm::provider::ProviderInference {
if model_id.starts_with("local:") || model_id.starts_with("ollama:") {
return crate::llm::provider::ProviderInference::builtin("ollama");
}
if model_id.starts_with("huggingface:") || model_id.starts_with("hf:") {
return crate::llm::provider::ProviderInference::builtin("huggingface");
}
let normalized_id = normalize_model_id(model_id);
if let Some(model) = config
.models
.get(model_id)
.or_else(|| config.models.get(&normalized_id))
{
return crate::llm::provider::ProviderInference::builtin(model.provider.clone());
}
for rule in &config.inference_rules {
if let Some(exact) = &rule.exact {
if model_id == exact {
return crate::llm::provider::ProviderInference::builtin(rule.provider.clone());
}
}
if let Some(pattern) = &rule.pattern {
if glob_match(pattern, model_id) {
return crate::llm::provider::ProviderInference::builtin(rule.provider.clone());
}
}
if let Some(substr) = &rule.contains {
if model_id.contains(substr.as_str()) {
return crate::llm::provider::ProviderInference::builtin(rule.provider.clone());
}
}
}
crate::llm::provider::infer_provider_from_model_id(
model_id,
&default_provider_with_config(config),
)
}
pub fn default_provider() -> String {
let config = effective_config();
default_provider_with_config(&config)
}
fn default_provider_with_config(config: &ProvidersConfig) -> String {
std::env::var("HARN_DEFAULT_PROVIDER")
.ok()
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty() && !value.eq_ignore_ascii_case("auto"))
.or_else(|| {
config
.default_provider
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty() && !value.eq_ignore_ascii_case("auto"))
.map(str::to_string)
})
.unwrap_or_else(|| "anthropic".to_string())
}
pub fn model_tier(model_id: &str) -> String {
let config = effective_config();
model_tier_with_config(&config, model_id)
}
pub(crate) fn model_tier_with_config(config: &ProvidersConfig, model_id: &str) -> String {
if let Some(model) = config.models.get(model_id) {
if let Some(tier) = model.tier.as_deref() {
let trimmed = tier.trim();
if !trimmed.is_empty() {
return trimmed.to_string();
}
}
}
for rule in &config.tier_rules {
if let Some(exact) = &rule.exact {
if model_id == exact {
return rule.tier.clone();
}
}
if let Some(pattern) = &rule.pattern {
if glob_match(pattern, model_id) {
return rule.tier.clone();
}
}
if let Some(substr) = &rule.contains {
if model_id.contains(substr.as_str()) {
return rule.tier.clone();
}
}
}
config.tier_defaults.default.clone()
}
pub fn model_family(provider: &str, model_id: &str) -> String {
let config = effective_config();
model_family_with_config(&config, provider, model_id)
}
pub(crate) fn model_family_with_config(
config: &ProvidersConfig,
provider: &str,
model_id: &str,
) -> String {
catalog_family_token(config, model_id)
.unwrap_or_else(|| derive_model_family(provider, model_id))
}
fn model_family_with_inference_source(
config: &ProvidersConfig,
provider: &str,
model_id: &str,
source: crate::llm::provider::ProviderInferenceSource,
) -> String {
if let Some(family) = catalog_family_token(config, model_id) {
return family;
}
let id_family = derive_model_family("", model_id);
if id_family != "unknown" {
return id_family;
}
if matches!(
source,
crate::llm::provider::ProviderInferenceSource::DefaultFallback
) {
return "unknown".to_string();
}
derive_model_family(provider, model_id)
}
pub fn model_lineage(provider: &str, model_id: &str) -> String {
let config = effective_config();
model_lineage_with_config(&config, provider, model_id)
}
pub(crate) fn model_lineage_with_config(
config: &ProvidersConfig,
provider: &str,
model_id: &str,
) -> String {
catalog_lineage_token(config, model_id)
.unwrap_or_else(|| derive_model_lineage(provider, model_id))
}
fn model_lineage_with_inference_source(
config: &ProvidersConfig,
provider: &str,
model_id: &str,
source: crate::llm::provider::ProviderInferenceSource,
) -> String {
if let Some(lineage) = catalog_lineage_token(config, model_id) {
return lineage;
}
let id_lineage = derive_model_lineage("", model_id);
if id_lineage != "unknown" {
return id_lineage;
}
if matches!(
source,
crate::llm::provider::ProviderInferenceSource::DefaultFallback
) {
return "unknown".to_string();
}
derive_model_lineage(provider, model_id)
}
fn catalog_family_token(config: &ProvidersConfig, model_id: &str) -> Option<String> {
config
.models
.get(model_id)
.and_then(|model| normalized_catalog_token(model.family.as_deref()))
}
fn catalog_lineage_token(config: &ProvidersConfig, model_id: &str) -> Option<String> {
config
.models
.get(model_id)
.and_then(|model| normalized_catalog_token(model.lineage.as_deref()))
}
fn normalized_catalog_token(value: Option<&str>) -> Option<String> {
value
.map(str::trim)
.filter(|value| !value.is_empty())
.map(|value| value.to_ascii_lowercase().replace('_', "-"))
}
fn derive_model_family(provider: &str, model_id: &str) -> String {
let id = model_id.to_ascii_lowercase();
if contains_any(&id, &["claude", "anthropic.claude"]) {
return "anthropic-claude".to_string();
}
if contains_any(&id, &["gemini", "google/gemini"]) {
return "google-gemini".to_string();
}
if contains_any(&id, &["deepseek"]) {
return "deepseek".to_string();
}
if contains_any(&id, &["qwen"]) {
return "qwen".to_string();
}
if contains_any(&id, &["kimi", "moonshot"]) {
return "kimi".to_string();
}
if contains_any(&id, &["glm", "z-ai/glm", "zhipu"]) {
return "glm".to_string();
}
if contains_any(&id, &["mistral", "mixtral", "devstral"]) {
return "mistral".to_string();
}
if contains_any(&id, &["minimax"]) {
return "minimax".to_string();
}
if contains_any(&id, &["llama"]) {
return "llama".to_string();
}
if contains_any(&id, &["gemma"]) {
return "gemma".to_string();
}
if is_openai_reasoning_model(&id) {
return "openai-reasoning".to_string();
}
if contains_any(&id, &["gpt-oss", "openai/gpt", "gpt-"]) {
return "openai-gpt".to_string();
}
match provider {
"anthropic" | "bedrock" | "vertex-anthropic" => "anthropic-claude".to_string(),
"openai" | "azure" | "azure_openai" => "openai-gpt".to_string(),
"gemini" | "vertex" | "google" => "google-gemini".to_string(),
"deepseek" => "deepseek".to_string(),
"zai" => "glm".to_string(),
"minimax" => "minimax".to_string(),
other if !other.is_empty() => normalize_identifier_token(other),
_ => "unknown".to_string(),
}
}
fn derive_model_lineage(provider: &str, model_id: &str) -> String {
let id = model_id.to_ascii_lowercase();
if contains_any(&id, &["haiku"]) {
return "claude-haiku".to_string();
}
if contains_any(&id, &["opus-4-7", "opus-4-8", "opus-mythos"]) {
return "claude-opus-adaptive".to_string();
}
if contains_any(&id, &["claude"]) {
return "claude-sonnet-opus".to_string();
}
if contains_any(&id, &["gpt-5"]) {
return "openai-gpt5".to_string();
}
if is_openai_reasoning_model(&id) {
return "openai-reasoning".to_string();
}
if contains_any(&id, &["gpt-", "gpt_"]) {
return "openai-legacy".to_string();
}
if contains_any(&id, &["gemini"]) {
if contains_any(&id, &["flash"]) {
return "gemini-flash".to_string();
}
return "gemini-pro".to_string();
}
if contains_any(&id, &["qwen3", "qwen/qwen3"]) {
return "qwen3".to_string();
}
if contains_any(&id, &["gemma4", "gemma-4"]) {
return "gemma4".to_string();
}
let family = derive_model_family(provider, model_id);
if family == "unknown" {
"unknown".to_string()
} else {
family
}
}
fn contains_any(haystack: &str, needles: &[&str]) -> bool {
needles.iter().any(|needle| haystack.contains(needle))
}
fn starts_with_any(haystack: &str, prefixes: &[&str]) -> bool {
prefixes.iter().any(|prefix| haystack.starts_with(prefix))
}
fn is_openai_reasoning_model(id: &str) -> bool {
starts_with_any(id, &["o1", "o3", "o4"])
|| contains_any(
id,
&[
"/o1", "/o3", "/o4", ":o1", ":o3", ":o4", ".o1", ".o3", ".o4",
],
)
}
fn normalize_identifier_token(value: &str) -> String {
value
.trim()
.to_ascii_lowercase()
.chars()
.map(|ch| {
if ch.is_ascii_alphanumeric() || ch == '-' {
ch
} else {
'-'
}
})
.collect::<String>()
.split('-')
.filter(|part| !part.is_empty())
.collect::<Vec<_>>()
.join("-")
}
pub fn provider_config(name: &str) -> Option<ProviderDef> {
effective_config().providers.get(name).cloned()
}
pub fn provider_protocol(name: &str) -> Option<String> {
provider_config(name).and_then(|def| def.protocol)
}
pub fn provider_uses_acp(name: &str) -> bool {
provider_protocol(name)
.as_deref()
.is_some_and(|protocol| protocol.eq_ignore_ascii_case("acp"))
}
pub fn model_params(model_id: &str) -> BTreeMap<String, toml::Value> {
let config = effective_config();
let mut params = BTreeMap::new();
for (pattern, defaults) in &config.model_defaults {
if glob_match(pattern, model_id) {
for (k, v) in defaults {
params.insert(k.clone(), v.clone());
}
}
}
params
}
pub fn model_role_defaults(role: &str) -> BTreeMap<String, toml::Value> {
let normalized = normalize_model_role_name(role);
if normalized.is_empty() {
return BTreeMap::new();
}
let config = effective_config();
let mut params = BTreeMap::new();
for key in role_lookup_keys(&normalized) {
extend_model_role_defaults(&config, &key, &mut params);
}
apply_model_role_env_overrides(&normalized, &mut params);
params
}
fn extend_model_role_defaults(
config: &ProvidersConfig,
role: &str,
params: &mut BTreeMap<String, toml::Value>,
) {
for (configured_role, defaults) in &config.model_roles {
if normalize_model_role_name(configured_role) == role {
params.extend(defaults.clone());
}
}
if let Some(defaults) = config.model_roles.get(role) {
params.extend(defaults.clone());
}
}
fn normalize_model_role_name(role: &str) -> String {
role.trim().to_ascii_lowercase().replace('-', "_")
}
fn role_lookup_keys(role: &str) -> Vec<String> {
if role == "merge" {
vec!["fast_apply".to_string(), "merge".to_string()]
} else if role == "fast_apply" {
vec!["merge".to_string(), "fast_apply".to_string()]
} else {
vec![role.to_string()]
}
}
fn role_env_token(role: &str) -> String {
role.chars()
.map(|ch| {
if ch.is_ascii_alphanumeric() {
ch.to_ascii_uppercase()
} else {
'_'
}
})
.collect::<String>()
.split('_')
.filter(|part| !part.is_empty())
.collect::<Vec<_>>()
.join("_")
}
fn apply_model_role_env_overrides(role: &str, params: &mut BTreeMap<String, toml::Value>) {
for alias in role_env_aliases(role) {
apply_model_role_env_var(&format!("HARN_LLM_{alias}_PROVIDER"), "provider", params);
apply_model_role_env_var(&format!("HARN_LLM_{alias}_MODEL"), "model", params);
apply_model_role_env_var(
&format!("HARN_LLM_{alias}_ROUTE_POLICY"),
"route_policy",
params,
);
apply_model_role_env_var(
&format!("HARN_LLM_ROLE_{alias}_PROVIDER"),
"provider",
params,
);
apply_model_role_env_var(&format!("HARN_LLM_ROLE_{alias}_MODEL"), "model", params);
apply_model_role_env_var(
&format!("HARN_LLM_ROLE_{alias}_ROUTE_POLICY"),
"route_policy",
params,
);
}
}
fn role_env_aliases(role: &str) -> Vec<String> {
let token = role_env_token(role);
if token.is_empty() {
return Vec::new();
}
if token == "MERGE" {
vec!["FAST_APPLY".to_string(), "MERGE".to_string()]
} else if token == "FAST_APPLY" {
vec!["MERGE".to_string(), "FAST_APPLY".to_string()]
} else {
vec![token]
}
}
fn apply_model_role_env_var(
env_name: &str,
option_name: &str,
params: &mut BTreeMap<String, toml::Value>,
) {
let Ok(value) = std::env::var(env_name) else {
return;
};
let trimmed = value.trim();
if trimmed.is_empty() {
return;
}
params.insert(
option_name.to_string(),
toml::Value::String(trimmed.to_string()),
);
}
pub fn provider_names() -> Vec<String> {
effective_config().providers.keys().cloned().collect()
}
pub fn known_model_names() -> Vec<String> {
effective_config().aliases.keys().cloned().collect()
}
pub fn alias_entries() -> Vec<(String, AliasDef)> {
effective_config().aliases.into_iter().collect()
}
pub fn alias_tool_calling_entry(alias: &str) -> Option<AliasToolCallingDef> {
effective_config().alias_tool_calling.get(alias).cloned()
}
pub fn model_catalog_entries() -> Vec<(String, ModelDef)> {
let config = effective_config();
model_catalog_entries_with_config(&config)
}
pub(crate) fn model_catalog_entries_with_config(
config: &ProvidersConfig,
) -> Vec<(String, ModelDef)> {
sorted_model_entries_with_config(config)
.into_iter()
.map(|(id, model)| {
let provider = model.provider.clone();
(
id.clone(),
with_effective_capability_tags(id, provider, model),
)
})
.collect()
}
pub(crate) fn sorted_model_entries_with_config(
config: &ProvidersConfig,
) -> Vec<(String, ModelDef)> {
let mut entries: Vec<_> = config
.models
.iter()
.map(|(id, model)| (id.clone(), model.clone()))
.collect();
entries.sort_by(|(id_a, model_a), (id_b, model_b)| {
model_a
.provider
.cmp(&model_b.provider)
.then_with(|| id_a.cmp(id_b))
});
entries
}
pub fn model_catalog_entry(model_id: &str) -> Option<ModelDef> {
effective_config()
.models
.get(model_id)
.cloned()
.map(|model| {
let provider = model.provider.clone();
with_effective_capability_tags(model_id.to_string(), provider, model)
})
}
pub fn qc_default_model(provider: &str) -> Option<String> {
std::env::var("BURIN_QC_MODEL")
.ok()
.filter(|value| !value.trim().is_empty())
.or_else(|| {
effective_config()
.qc_defaults
.get(&provider.to_lowercase())
.cloned()
})
}
pub fn default_model_for_provider(provider: &str) -> String {
if provider_uses_acp(provider) {
return "default".to_string();
}
match provider {
"local" => std::env::var("LOCAL_LLM_MODEL")
.or_else(|_| std::env::var("HARN_LLM_MODEL"))
.unwrap_or_else(|_| "gemma-4-26b-a4b-it".to_string()),
"mlx" => std::env::var("MLX_MODEL_ID")
.unwrap_or_else(|_| "unsloth/Qwen3.6-27B-UD-MLX-4bit".to_string()),
"openai" => "gpt-4o-mini".to_string(),
"ollama" => "llama3.2".to_string(),
"openrouter" => "anthropic/claude-sonnet-4.6".to_string(),
_ => "claude-sonnet-4-6".to_string(),
}
}
pub fn qc_defaults() -> BTreeMap<String, String> {
effective_config().qc_defaults
}
pub fn model_pricing_per_mtok(model_id: &str) -> Option<ModelPricing> {
effective_config()
.models
.get(model_id)
.and_then(|model| model.pricing.clone())
}
pub fn model_fast_pricing_per_mtok(model_id: &str) -> Option<ModelPricing> {
effective_config()
.models
.get(model_id)
.and_then(|model| model.fast_mode.as_ref())
.and_then(|fast_mode| fast_mode.pricing.clone())
}
pub fn pricing_per_1k_for(provider: &str, model_id: &str) -> Option<(f64, f64)> {
model_pricing_per_mtok(model_id)
.map(|pricing| {
(
pricing.input_per_mtok / 1000.0,
pricing.output_per_mtok / 1000.0,
)
})
.or_else(|| {
let (input, output, _) = provider_economics(provider);
match (input, output) {
(Some(input), Some(output)) => Some((input, output)),
_ => None,
}
})
}
pub fn auth_env_names(auth_env: &AuthEnv) -> Vec<String> {
match auth_env {
AuthEnv::None => Vec::new(),
AuthEnv::Single(name) => vec![name.clone()],
AuthEnv::Multiple(names) => names.clone(),
}
}
pub fn provider_key_available(provider: &str) -> bool {
let Some(pdef) = provider_config(provider) else {
return provider == "ollama";
};
if pdef.auth_style == "none" || matches!(pdef.auth_env, AuthEnv::None) {
return true;
}
auth_env_names(&pdef.auth_env).into_iter().any(|env_name| {
std::env::var(env_name)
.ok()
.is_some_and(|value| !value.trim().is_empty())
})
}
pub fn available_provider_names() -> Vec<String> {
provider_names()
.into_iter()
.filter(|provider| provider_key_available(provider))
.collect()
}
pub fn provider_has_feature(provider: &str, feature: &str) -> bool {
provider_config(provider)
.map(|p| p.features.iter().any(|f| f == feature))
.unwrap_or(false)
}
pub fn provider_economics(provider: &str) -> (Option<f64>, Option<f64>, Option<u64>) {
provider_config(provider)
.map(|p| (p.cost_per_1k_in, p.cost_per_1k_out, p.latency_p50_ms))
.unwrap_or((None, None, None))
}
pub fn default_tool_format(model: &str, provider: &str) -> String {
let config = effective_config();
default_tool_format_with_config(&config, model, provider)
}
fn default_tool_format_with_config(
config: &ProvidersConfig,
model: &str,
provider: &str,
) -> String {
for (name, alias) in &config.aliases {
let matches = (alias.id == model && alias.provider == provider) || name == model;
if matches {
if let Some(ref fmt) = alias.tool_format {
return fmt.clone();
}
}
}
let capabilities = crate::llm::capabilities::lookup(provider, model);
if let Some(format) = capabilities.preferred_tool_format.as_deref() {
if matches!(format, "native" | "text") {
return format.to_string();
}
}
let capability_matrix_native = capabilities.native_tools;
let legacy_provider_native = config
.providers
.get(provider)
.map(|p| p.features.iter().any(|f| f == "native_tools"))
.unwrap_or(false);
if capability_matrix_native || legacy_provider_native {
"native".to_string()
} else {
"text".to_string()
}
}
fn with_effective_capability_tags(
model_id: String,
provider: String,
mut model: ModelDef,
) -> ModelDef {
model.capabilities = effective_model_capability_tags(&provider, &model_id);
model
}
pub fn effective_model_capability_tags(provider: &str, model_id: &str) -> Vec<String> {
let caps = crate::llm::capabilities::lookup(provider, model_id);
capability_tags_from_capabilities(&caps)
}
pub(crate) fn capability_tags_from_capabilities(
caps: &crate::llm::capabilities::Capabilities,
) -> Vec<String> {
let mut tags = Vec::new();
tags.push("streaming".to_string());
if caps.native_tools || caps.text_tool_wire_format_supported {
tags.push("tools".to_string());
}
if !caps.tool_search.is_empty() {
tags.push("tool_search".to_string());
}
if caps.vision || caps.vision_supported {
tags.push("vision".to_string());
}
if caps.audio {
tags.push("audio".to_string());
}
if caps.pdf {
tags.push("pdf".to_string());
}
if caps.files_api_supported {
tags.push("files".to_string());
}
if caps.prompt_caching {
tags.push("prompt_caching".to_string());
}
if !caps.thinking_modes.is_empty() {
tags.push("thinking".to_string());
}
if caps.interleaved_thinking_supported
|| caps
.thinking_modes
.iter()
.any(|mode| mode == "adaptive" || mode == "effort")
{
tags.push("extended_thinking".to_string());
}
if caps.json_schema.is_some() {
tags.push("structured_output".to_string());
}
tags
}
pub fn resolve_tier_model(
target: &str,
preferred_provider: Option<&str>,
) -> Option<(String, String)> {
let config = effective_config();
if let Some(alias) = config.aliases.get(target) {
return Some((alias.id.clone(), alias.provider.clone()));
}
let candidate_aliases = if let Some(provider) = preferred_provider {
vec![
format!("{provider}/{target}"),
format!("{provider}:{target}"),
format!("tier/{target}"),
target.to_string(),
]
} else {
vec![format!("tier/{target}"), target.to_string()]
};
for alias_name in candidate_aliases {
if let Some(alias) = config.aliases.get(&alias_name) {
return Some((alias.id.clone(), alias.provider.clone()));
}
}
None
}
pub fn tier_candidates(target: &str) -> Vec<(String, String)> {
let config = effective_config();
let mut seen = std::collections::BTreeSet::new();
let mut candidates = Vec::new();
for alias in config.aliases.values() {
let pair = (alias.id.clone(), alias.provider.clone());
if seen.contains(&pair) {
continue;
}
if model_tier(&alias.id) == target {
seen.insert(pair.clone());
candidates.push(pair);
}
}
candidates.sort_by(|(model_a, provider_a), (model_b, provider_b)| {
provider_a
.cmp(provider_b)
.then_with(|| model_a.cmp(model_b))
});
candidates
}
pub fn all_model_candidates() -> Vec<(String, String)> {
let config = effective_config();
let mut seen = std::collections::BTreeSet::new();
let mut candidates = Vec::new();
for alias in config.aliases.values() {
let pair = (alias.id.clone(), alias.provider.clone());
if seen.insert(pair.clone()) {
candidates.push(pair);
}
}
candidates.sort_by(|(model_a, provider_a), (model_b, provider_b)| {
provider_a
.cmp(provider_b)
.then_with(|| model_a.cmp(model_b))
});
candidates
}
pub fn pick_complementary_reviewer(
options: ComplementaryReviewerOptions,
) -> ComplementaryReviewerSelection {
let config = effective_config();
let mut author = resolve_model_info(&options.author_model);
if let Some(provider) = options
.author_provider
.as_deref()
.map(str::trim)
.filter(|provider| !provider.is_empty())
{
author.provider = provider.to_string();
author.family = model_family_with_config(&config, &author.provider, &author.id);
author.lineage = model_lineage_with_config(&config, &author.provider, &author.id);
author.tool_format = default_tool_format_with_config(&config, &author.id, &author.provider);
}
let author_entry = config.models.get(&author.id);
let author_identity = complementary_identity(
author.id.clone(),
author.provider.clone(),
author.family.clone(),
author.lineage.clone(),
author.tier.clone(),
author_entry.and_then(|model| model.pricing.clone()),
);
let fallback = |fallback_reason: String| ComplementaryReviewerSelection {
intent: options.intent.as_str().to_string(),
reviewer: author_identity.clone(),
estimated_incremental_cost: cost_estimate(
author_identity.pricing.as_ref(),
author_identity.pricing.as_ref(),
),
author: author_identity.clone(),
fallback: true,
reason: format!(
"using author model {} because {fallback_reason}",
author_identity.id
),
fallback_reason: Some(fallback_reason),
};
if author_identity.family == "unknown" {
return fallback("author model family is unknown".to_string());
}
let preferred_families = author_entry
.map(|model| model.complementary_with.clone())
.unwrap_or_default();
let author_refs = reviewer_match_refs(&author_identity);
let mut rejected_by_price = 0usize;
let mut diff_family_seen = 0usize;
let mut candidates = Vec::new();
for (id, model) in config.models.iter() {
if id == &author_identity.id && model.provider == author_identity.provider {
continue;
}
if model.deprecated || model.availability != ModelAvailability::Serverless {
continue;
}
let family = model_family_with_config(&config, &model.provider, id);
if family == "unknown" || family == author_identity.family {
continue;
}
diff_family_seen += 1;
let lineage = model_lineage_with_config(&config, &model.provider, id);
let candidate_identity = complementary_identity(
id.clone(),
model.provider.clone(),
family,
lineage,
model_tier_with_config(&config, id),
model.pricing.clone(),
);
if model
.avoid_as_reviewer_for
.iter()
.any(|selector| refs_contain_selector(&author_refs, selector))
{
continue;
}
if exceeds_price_cap(
author_identity.pricing.as_ref(),
candidate_identity.pricing.as_ref(),
options.max_price_multiplier,
) {
rejected_by_price += 1;
continue;
}
let score = reviewer_score(
&options,
&author_identity,
&candidate_identity,
model,
&preferred_families,
);
candidates.push(ReviewerCandidate {
identity: candidate_identity,
score,
});
}
candidates.sort_by(|left, right| {
right
.score
.partial_cmp(&left.score)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| left.identity.provider.cmp(&right.identity.provider))
.then_with(|| left.identity.id.cmp(&right.identity.id))
});
let Some(best) = candidates.into_iter().next() else {
if rejected_by_price > 0 {
let cap = options.max_price_multiplier.unwrap_or_default();
return fallback(format!(
"no different-family reviewer satisfied max_price_multiplier {cap}"
));
}
if diff_family_seen == 0 {
return fallback(
"no active serverless different-family reviewer is cataloged".to_string(),
);
}
return fallback("all different-family reviewer candidates were excluded".to_string());
};
let estimate = cost_estimate(
best.identity.pricing.as_ref(),
author_identity.pricing.as_ref(),
);
ComplementaryReviewerSelection {
intent: options.intent.as_str().to_string(),
reason: reviewer_reason(&author_identity, &best.identity, estimate.as_ref()),
estimated_incremental_cost: estimate,
author: author_identity,
reviewer: best.identity,
fallback: false,
fallback_reason: None,
}
}
#[derive(Debug, Clone)]
struct ReviewerCandidate {
identity: ComplementaryModelIdentity,
score: f64,
}
fn complementary_identity(
id: String,
provider: String,
family: String,
lineage: String,
tier: String,
pricing: Option<ModelPricing>,
) -> ComplementaryModelIdentity {
ComplementaryModelIdentity {
id,
provider,
family,
lineage,
tier,
pricing,
}
}
fn reviewer_score(
options: &ComplementaryReviewerOptions,
author: &ComplementaryModelIdentity,
candidate: &ComplementaryModelIdentity,
model: &ModelDef,
preferred_families: &[String],
) -> f64 {
let candidate_refs = reviewer_match_refs(candidate);
let mut score = 0.0;
if let Some(rank) = preferred_families
.iter()
.position(|selector| refs_contain_selector(&candidate_refs, selector))
{
score += 1_000.0 - rank as f64;
}
if candidate.provider != author.provider {
score += 100.0;
}
score += match tier_distance(&author.tier, &candidate.tier) {
0 => 80.0,
1 => 45.0,
2 => 15.0,
_ => 0.0,
};
for strength in intent_strengths(options.intent) {
if model.strengths.iter().any(|tag| tag == strength) {
score += 8.0;
}
}
if model.capabilities.iter().any(|tag| tag == "tools") {
score += 4.0;
}
if let (Some(author_total), Some(candidate_total)) = (
pricing_total(author.pricing.as_ref()),
pricing_total(candidate.pricing.as_ref()),
) {
if author_total > 0.0 {
let ratio = candidate_total / author_total;
if ratio <= 1.0 {
score += 20.0;
}
score -= (ratio - 1.0).abs().min(10.0) * 8.0;
}
}
score
}
fn intent_strengths(intent: ComplementaryReviewerIntent) -> &'static [&'static str] {
match intent {
ComplementaryReviewerIntent::Review => &["reasoning", "coding", "tool_use"],
ComplementaryReviewerIntent::Critique => &["reasoning", "long_context", "tool_use"],
ComplementaryReviewerIntent::PlanReview => {
&["reasoning", "coding", "agentic", "long_context", "tool_use"]
}
}
}
fn tier_distance(left: &str, right: &str) -> u8 {
let left = tier_rank(left);
let right = tier_rank(right);
left.abs_diff(right)
}
fn tier_rank(tier: &str) -> u8 {
match tier {
"small" => 0,
"mid" => 1,
"frontier" | "reasoning" => 2,
_ => 1,
}
}
fn exceeds_price_cap(
author_pricing: Option<&ModelPricing>,
candidate_pricing: Option<&ModelPricing>,
max_price_multiplier: Option<f64>,
) -> bool {
let Some(max_price_multiplier) = max_price_multiplier else {
return false;
};
let Some(author_total) = pricing_total(author_pricing) else {
return false;
};
let Some(candidate_total) = pricing_total(candidate_pricing) else {
return true;
};
author_total > 0.0 && candidate_total > author_total * max_price_multiplier
}
fn cost_estimate(
reviewer_pricing: Option<&ModelPricing>,
author_pricing: Option<&ModelPricing>,
) -> Option<ComplementaryCostEstimate> {
let reviewer_pricing = reviewer_pricing?;
let total_per_mtok = reviewer_pricing.input_per_mtok + reviewer_pricing.output_per_mtok;
let multiplier_vs_author = pricing_total(author_pricing)
.filter(|author_total| *author_total > 0.0)
.map(|author_total| total_per_mtok / author_total);
Some(ComplementaryCostEstimate {
input_per_mtok: reviewer_pricing.input_per_mtok,
output_per_mtok: reviewer_pricing.output_per_mtok,
total_per_mtok,
multiplier_vs_author,
})
}
fn pricing_total(pricing: Option<&ModelPricing>) -> Option<f64> {
pricing.map(|pricing| pricing.input_per_mtok + pricing.output_per_mtok)
}
fn reviewer_reason(
author: &ComplementaryModelIdentity,
reviewer: &ComplementaryModelIdentity,
estimate: Option<&ComplementaryCostEstimate>,
) -> String {
let cost = estimate
.and_then(|estimate| estimate.multiplier_vs_author)
.map(|multiplier| format!("{multiplier:.2}x the author model price"))
.unwrap_or_else(|| "price ratio unavailable".to_string());
format!(
"selected {} via {} because family {} differs from author family {}, tier {} matches author tier {}, and {}",
reviewer.id,
reviewer.provider,
reviewer.family,
author.family,
reviewer.tier,
author.tier,
cost
)
}
fn reviewer_match_refs(identity: &ComplementaryModelIdentity) -> BTreeSet<String> {
BTreeSet::from([
identity.id.to_ascii_lowercase(),
identity.provider.to_ascii_lowercase(),
format!("{}/{}", identity.provider, identity.id).to_ascii_lowercase(),
format!("{}:{}", identity.provider, identity.id).to_ascii_lowercase(),
identity.family.to_ascii_lowercase(),
identity.lineage.to_ascii_lowercase(),
])
}
fn refs_contain_selector(refs: &BTreeSet<String>, selector: &str) -> bool {
normalized_catalog_token(Some(selector))
.or_else(|| Some(selector.trim().to_ascii_lowercase()))
.is_some_and(|selector| refs.contains(&selector))
}
fn glob_match(pattern: &str, input: &str) -> bool {
if let Some(prefix) = pattern.strip_suffix('*') {
input.starts_with(prefix)
} else if let Some(suffix) = pattern.strip_prefix('*') {
input.ends_with(suffix)
} else if pattern.contains('*') {
let parts: Vec<&str> = pattern.split('*').collect();
if parts.len() == 2 {
input.starts_with(parts[0]) && input.ends_with(parts[1])
} else {
input == pattern
}
} else {
input == pattern
}
}
fn dirs_or_home() -> Option<String> {
std::env::var("HOME").ok()
}
pub fn resolve_base_url(pdef: &ProviderDef) -> String {
if let Some(env_name) = &pdef.base_url_env {
if let Ok(val) = std::env::var(env_name) {
let trimmed = val.trim().trim_matches('"').trim_matches('\'');
if !trimmed.is_empty() {
return trimmed.to_string();
}
}
}
pdef.base_url.clone()
}
const EMBEDDED_PROVIDERS_TOML: &str = include_str!("llm/providers.toml");
fn default_config() -> ProvidersConfig {
parse_config_toml(EMBEDDED_PROVIDERS_TOML)
.expect("embedded providers.toml must parse — invariant checked by harn-vm tests")
}
#[cfg(test)]
fn merge_global_config(overlay: ProvidersConfig) -> ProvidersConfig {
let mut config = default_config();
config.merge_from(&overlay);
config
}
#[cfg(test)]
mod tests {
use super::*;
fn reset_overrides() {
clear_user_overrides();
}
#[test]
fn test_glob_match_prefix() {
assert!(glob_match("claude-*", "claude-sonnet-4-20250514"));
assert!(glob_match("gpt-*", "gpt-4o"));
assert!(!glob_match("claude-*", "gpt-4o"));
}
#[test]
fn test_glob_match_suffix() {
assert!(glob_match("*-latest", "llama3.2-latest"));
assert!(!glob_match("*-latest", "llama3.2"));
}
#[test]
fn test_glob_match_middle() {
assert!(glob_match("claude-*-latest", "claude-sonnet-latest"));
assert!(!glob_match("claude-*-latest", "claude-sonnet-beta"));
}
#[test]
fn test_glob_match_exact() {
assert!(glob_match("gpt-4o", "gpt-4o"));
assert!(!glob_match("gpt-4o", "gpt-4o-mini"));
}
#[test]
fn test_infer_provider_from_defaults() {
let _guard = crate::llm::env_lock().lock().expect("env lock");
let prev_default_provider = std::env::var("HARN_DEFAULT_PROVIDER").ok();
unsafe {
std::env::remove_var("HARN_DEFAULT_PROVIDER");
}
assert_eq!(infer_provider("claude-sonnet-4-20250514"), "anthropic");
assert_eq!(infer_provider("gpt-4o"), "openai");
assert_eq!(infer_provider("o1-preview"), "openai");
assert_eq!(infer_provider("o3-mini"), "openai");
assert_eq!(infer_provider("o4-mini"), "openai");
assert_eq!(infer_provider("gemini-2.5-pro"), "gemini");
assert_eq!(infer_provider("qwen/qwen3-coder"), "openrouter");
assert_eq!(infer_provider("llama3.2:latest"), "ollama");
assert_eq!(infer_provider("unknown-model"), "anthropic");
unsafe {
match prev_default_provider {
Some(value) => std::env::set_var("HARN_DEFAULT_PROVIDER", value),
None => std::env::remove_var("HARN_DEFAULT_PROVIDER"),
}
}
}
#[test]
fn test_infer_provider_prefix_rules() {
assert_eq!(infer_provider("local:gemma-4-e4b-it"), "ollama");
assert_eq!(infer_provider("ollama:qwen3:30b-a3b"), "ollama");
assert_eq!(infer_provider("local:owner/model"), "ollama");
assert_eq!(infer_provider("hf:Qwen/Qwen3.6-35B-A3B"), "huggingface");
}
#[test]
fn test_openrouter_inference_requires_one_slash() {
let _guard = crate::llm::env_lock().lock().expect("env lock");
let prev_default_provider = std::env::var("HARN_DEFAULT_PROVIDER").ok();
unsafe {
std::env::remove_var("HARN_DEFAULT_PROVIDER");
}
assert_eq!(infer_provider("org/model"), "openrouter");
assert_eq!(infer_provider("org/team/model"), "anthropic");
unsafe {
match prev_default_provider {
Some(value) => std::env::set_var("HARN_DEFAULT_PROVIDER", value),
None => std::env::remove_var("HARN_DEFAULT_PROVIDER"),
}
}
}
#[test]
fn test_cerebras_inference_beats_openrouter_slash_fallback() {
let _guard = crate::llm::env_lock().lock().expect("env lock");
let prev_default_provider = std::env::var("HARN_DEFAULT_PROVIDER").ok();
unsafe {
std::env::remove_var("HARN_DEFAULT_PROVIDER");
}
assert_eq!(infer_provider("cerebras/gpt-oss-120b"), "cerebras");
assert_eq!(infer_provider("cerebras/llama-3.3-70b"), "cerebras");
unsafe {
match prev_default_provider {
Some(value) => std::env::set_var("HARN_DEFAULT_PROVIDER", value),
None => std::env::remove_var("HARN_DEFAULT_PROVIDER"),
}
}
}
#[test]
fn test_direct_catalog_model_id_resolves_to_catalog_provider() {
let _guard = crate::llm::env_lock().lock().expect("env lock");
let prev_default_provider = std::env::var("HARN_DEFAULT_PROVIDER").ok();
unsafe {
std::env::remove_var("HARN_DEFAULT_PROVIDER");
}
for model in ["gpt-oss-120b", "llama-3.3-70b"] {
assert_eq!(
infer_provider(model),
"cerebras",
"{model} should route to its catalog provider"
);
let resolved = resolve_model_info(model);
assert_eq!(resolved.id, model);
assert_eq!(resolved.provider, "cerebras");
}
unsafe {
match prev_default_provider {
Some(value) => std::env::set_var("HARN_DEFAULT_PROVIDER", value),
None => std::env::remove_var("HARN_DEFAULT_PROVIDER"),
}
}
}
#[test]
fn test_user_catalog_overlay_re_homes_model_provider() {
reset_overrides();
let mut overlay = ProvidersConfig::default();
overlay.models.insert(
"gpt-4o".to_string(),
ModelDef {
name: "GPT-4o via OpenRouter".to_string(),
provider: "openrouter".to_string(),
context_window: 128_000,
runtime_context_window: None,
stream_timeout: None,
capabilities: Vec::new(),
pricing: None,
deprecated: false,
deprecation_note: None,
superseded_by: None,
fast_mode: None,
quality_tags: Vec::new(),
availability: ModelAvailability::default(),
tier: None,
open_weight: None,
strengths: Vec::new(),
benchmarks: std::collections::BTreeMap::new(),
family: None,
lineage: None,
complementary_with: Vec::new(),
avoid_as_reviewer_for: Vec::new(),
},
);
set_user_overrides(Some(overlay));
assert_eq!(infer_provider("gpt-4o"), "openrouter");
reset_overrides();
}
#[test]
fn test_resolve_model_info_normalizes_provider_prefixes() {
let local = resolve_model_info("local:gemma-4-e4b-it");
assert_eq!(local.id, "gemma-4-e4b-it");
assert_eq!(local.provider, "ollama");
let ollama = resolve_model_info("ollama:qwen3:30b-a3b");
assert_eq!(ollama.id, "qwen3:30b-a3b");
assert_eq!(ollama.provider, "ollama");
let hf = resolve_model_info("hf:Qwen/Qwen3.6-35B-A3B");
assert_eq!(hf.id, "Qwen/Qwen3.6-35B-A3B");
assert_eq!(hf.provider, "huggingface");
let cerebras = resolve_model_info("cerebras/gpt-oss-120b");
assert_eq!(cerebras.id, "gpt-oss-120b");
assert_eq!(cerebras.provider, "cerebras");
}
#[test]
fn test_model_tier_from_defaults() {
assert_eq!(model_tier("claude-sonnet-4-20250514"), "frontier");
assert_eq!(model_tier("gpt-4o"), "frontier");
assert_eq!(model_tier("Qwen/Qwen3.5-9B"), "small");
assert_eq!(model_tier("deepseek-v4-flash"), "mid");
assert_eq!(model_tier("deepseek-v4-pro"), "frontier");
assert_eq!(model_tier("MiniMax-M2.7"), "frontier");
assert_eq!(model_tier("glm-5.1"), "frontier");
assert_eq!(model_tier("definitely-not-a-real-model"), "mid");
}
#[test]
fn test_model_family_preserves_underlying_hosted_lineage() {
assert_eq!(
model_family("openrouter", "anthropic/claude-sonnet-4-6"),
"anthropic-claude"
);
assert_eq!(
model_family("openrouter", "google/gemini-2.5-flash"),
"google-gemini"
);
assert_eq!(
model_family("openrouter", "openai/o3-mini"),
"openai-reasoning"
);
assert_eq!(model_lineage("openrouter", "openai/gpt-5.5"), "openai-gpt5");
assert_eq!(
model_lineage("openrouter", "openai/o3-mini"),
"openai-reasoning"
);
assert_eq!(
model_lineage("anthropic", "claude-opus-4-8"),
"claude-opus-adaptive"
);
assert_eq!(
model_lineage("ollama", "qwen3.6:35b-a3b-coding-nvfp4"),
"qwen3"
);
}
#[test]
fn test_complementary_reviewer_uses_different_family() {
let selection = pick_complementary_reviewer(ComplementaryReviewerOptions {
author_model: "claude-sonnet-4-6".to_string(),
author_provider: None,
intent: ComplementaryReviewerIntent::PlanReview,
max_price_multiplier: Some(3.0),
});
assert!(!selection.fallback, "{selection:?}");
assert_eq!(selection.author.family, "anthropic-claude");
assert_ne!(selection.reviewer.family, selection.author.family);
assert_eq!(selection.reviewer.tier, "frontier");
assert!(selection.estimated_incremental_cost.is_some());
}
#[test]
fn test_complementary_reviewer_falls_back_deterministically_on_price_cap() {
let selection = pick_complementary_reviewer(ComplementaryReviewerOptions {
author_model: "gpt-4o-mini".to_string(),
author_provider: Some("openai".to_string()),
intent: ComplementaryReviewerIntent::Review,
max_price_multiplier: Some(0.01),
});
assert!(selection.fallback, "{selection:?}");
assert_eq!(selection.reviewer.id, "gpt-4o-mini");
assert_eq!(selection.reviewer.family, selection.author.family);
assert!(selection
.fallback_reason
.as_deref()
.is_some_and(|reason| reason.contains("max_price_multiplier")));
}
#[test]
fn test_resolve_model_unknown_alias() {
let (id, provider) = resolve_model("gpt-4o");
assert_eq!(id, "gpt-4o");
assert!(provider.is_none());
}
#[test]
fn test_provider_names() {
let names = provider_names();
assert!(names.len() >= 7);
assert!(names.contains(&"anthropic".to_string()));
assert!(names.contains(&"together".to_string()));
assert!(names.contains(&"local".to_string()));
assert!(names.contains(&"mlx".to_string()));
assert!(names.contains(&"openai".to_string()));
assert!(names.contains(&"ollama".to_string()));
assert!(names.contains(&"bedrock".to_string()));
assert!(names.contains(&"azure_openai".to_string()));
assert!(names.contains(&"vertex".to_string()));
}
#[test]
fn global_provider_file_is_an_overlay_on_builtin_defaults() {
let mut overlay = ProvidersConfig {
default_provider: Some("ollama".to_string()),
..Default::default()
};
overlay.aliases.insert(
"quickstart".to_string(),
AliasDef {
id: "llama3.2".to_string(),
provider: "ollama".to_string(),
tool_format: None,
},
);
let merged = merge_global_config(overlay);
assert_eq!(merged.default_provider.as_deref(), Some("ollama"));
assert!(merged.providers.contains_key("anthropic"));
assert!(merged.providers.contains_key("ollama"));
assert_eq!(merged.aliases["quickstart"].id, "llama3.2");
}
#[test]
fn partial_provider_overlay_preserves_builtin_provider_metadata() {
let overlay = parse_config_toml(
r#"
[providers.ollama]
base_url = "http://localhost:11435"
extra_headers = { "x-local" = "1" }
"#,
)
.expect("provider overlay parses");
let merged = merge_global_config(overlay);
let ollama = merged
.providers
.get("ollama")
.expect("ollama remains configured");
assert_eq!(ollama.base_url, "http://localhost:11435");
assert_eq!(ollama.auth_style, "none");
assert_eq!(ollama.chat_endpoint, "/api/chat");
assert_eq!(ollama.completion_endpoint.as_deref(), Some("/api/generate"));
assert_eq!(ollama.cost_per_1k_in, Some(0.0));
assert_eq!(ollama.cost_per_1k_out, Some(0.0));
assert_eq!(
ollama
.healthcheck
.as_ref()
.and_then(|healthcheck| healthcheck.path.as_deref()),
Some("/api/tags")
);
assert_eq!(
ollama.extra_headers.get("x-local").map(String::as_str),
Some("1")
);
}
#[test]
fn partial_provider_overlay_can_explicitly_replace_default_auth_style() {
let overlay = parse_config_toml(
r#"
[providers.ollama]
auth_style = "bearer"
auth_env = "OLLAMA_API_KEY"
"#,
)
.expect("provider overlay parses");
let merged = merge_global_config(overlay);
let ollama = merged
.providers
.get("ollama")
.expect("ollama remains configured");
assert_eq!(ollama.auth_style, "bearer");
assert_eq!(auth_env_names(&ollama.auth_env), vec!["OLLAMA_API_KEY"]);
assert_eq!(ollama.chat_endpoint, "/api/chat");
}
#[test]
fn test_resolve_tier_model_default_aliases() {
let (model, provider) = resolve_tier_model("frontier", None)
.expect("frontier alias must resolve from the embedded catalog");
assert_eq!(provider, "anthropic");
assert!(
model_catalog_entry(&model)
.is_some_and(|entry| entry.provider == "anthropic" && !entry.deprecated),
"frontier alias must point at a registered, non-deprecated anthropic model (got {model})"
);
let (model, provider) = resolve_tier_model("small", None)
.expect("small alias must resolve from the embedded catalog");
assert!(
[
"openrouter",
"huggingface",
"local",
"llamacpp",
"mlx",
"ollama"
]
.contains(&provider.as_str()),
"small tier should resolve to an open-weight provider (got {provider} / {model})"
);
}
#[test]
fn test_resolve_tier_model_prefers_provider_scoped_aliases() {
let (model, provider) = resolve_tier_model("mid", Some("openai"))
.expect("mid tier scoped to openai must resolve");
assert_eq!(provider, "openai");
assert!(
model_catalog_entry(&model).is_some(),
"mid/openai alias must point at a registered model (got {model})"
);
}
#[test]
fn test_provider_config_anthropic() {
let pdef = provider_config("anthropic").unwrap();
assert_eq!(pdef.auth_style, "header");
assert_eq!(pdef.auth_header.as_deref(), Some("x-api-key"));
}
#[test]
fn test_provider_config_mlx() {
let pdef = provider_config("mlx").unwrap();
assert_eq!(pdef.base_url, "http://127.0.0.1:8002");
assert_eq!(pdef.base_url_env.as_deref(), Some("MLX_BASE_URL"));
assert_eq!(
pdef.healthcheck.unwrap().path.as_deref(),
Some("/v1/models")
);
let (model, provider) = resolve_model("mlx-qwen36-27b");
assert_eq!(model, "unsloth/Qwen3.6-27B-UD-MLX-4bit");
assert_eq!(provider.as_deref(), Some("mlx"));
}
#[test]
fn test_enterprise_provider_defaults_and_inference() {
let bedrock = provider_config("bedrock").unwrap();
assert_eq!(bedrock.auth_style, "aws_sigv4");
assert_eq!(bedrock.base_url_env.as_deref(), Some("BEDROCK_BASE_URL"));
assert_eq!(
infer_provider("anthropic.claude-3-5-sonnet-20240620-v1:0"),
"bedrock"
);
assert_eq!(infer_provider("meta.llama3-70b-instruct-v1:0"), "bedrock");
let azure = provider_config("azure_openai").unwrap();
assert_eq!(azure.base_url_env.as_deref(), Some("AZURE_OPENAI_ENDPOINT"));
assert_eq!(
auth_env_names(&azure.auth_env),
vec![
"AZURE_OPENAI_API_KEY".to_string(),
"AZURE_OPENAI_AD_TOKEN".to_string(),
"AZURE_OPENAI_BEARER_TOKEN".to_string(),
]
);
let vertex = provider_config("vertex").unwrap();
assert_eq!(vertex.base_url, "https://aiplatform.googleapis.com/v1");
assert_eq!(infer_provider("gemini-1.5-pro-002"), "gemini");
}
#[test]
fn test_default_provider_env_override_for_unknown_model() {
let _guard = crate::llm::env_lock().lock().expect("env lock");
let prev_default_provider = std::env::var("HARN_DEFAULT_PROVIDER").ok();
unsafe {
std::env::set_var("HARN_DEFAULT_PROVIDER", "openai");
}
let inference = infer_provider_detail("unknown-model");
unsafe {
match prev_default_provider {
Some(value) => std::env::set_var("HARN_DEFAULT_PROVIDER", value),
None => std::env::remove_var("HARN_DEFAULT_PROVIDER"),
}
}
assert_eq!(inference.provider, "openai");
assert_eq!(
inference.source,
crate::llm::provider::ProviderInferenceSource::DefaultFallback
);
}
#[test]
fn test_unknown_model_family_ignores_default_provider_fallback() {
let _guard = crate::llm::env_lock().lock().expect("env lock");
let prev_default_provider = std::env::var("HARN_DEFAULT_PROVIDER").ok();
unsafe {
std::env::set_var("HARN_DEFAULT_PROVIDER", "ollama");
}
let unknown = resolve_model_info("mystery-model-xyz");
let known_family = resolve_model_info("deepseek-mystery-model");
unsafe {
match prev_default_provider {
Some(value) => std::env::set_var("HARN_DEFAULT_PROVIDER", value),
None => std::env::remove_var("HARN_DEFAULT_PROVIDER"),
}
}
assert_eq!(unknown.provider, "ollama");
assert_eq!(unknown.family, "unknown");
assert_eq!(unknown.lineage, "unknown");
assert_eq!(known_family.family, "deepseek");
assert_eq!(known_family.lineage, "deepseek");
}
#[test]
fn test_resolve_base_url_no_env() {
let pdef = ProviderDef {
base_url: "https://example.com".to_string(),
..Default::default()
};
assert_eq!(resolve_base_url(&pdef), "https://example.com");
}
#[test]
fn test_default_config_roundtrip() {
let config = default_config();
assert!(!config.providers.is_empty());
assert!(!config.inference_rules.is_empty());
assert_eq!(config.tier_defaults.default, "mid");
let frontiers = config
.models
.iter()
.filter(|(_, m)| m.tier.as_deref() == Some("frontier"))
.count();
assert!(
frontiers >= 4,
"expected at least 4 frontier-tagged models, got {frontiers}"
);
}
#[test]
fn test_local_ollama_catalog_metadata() {
reset_overrides();
let qwen_coding = model_catalog_entry("qwen3.6:35b-a3b-coding-nvfp4")
.expect("qwen3.6 coding catalog entry");
assert_eq!(qwen_coding.context_window, 262_144);
assert!(!qwen_coding.capabilities.iter().any(|cap| cap == "vision"));
let gemma4 = model_catalog_entry("gemma4:26b").expect("gemma4 catalog entry");
assert_eq!(gemma4.context_window, 262_144);
assert!(gemma4.capabilities.iter().any(|cap| cap == "vision"));
}
#[test]
fn test_external_config_overlays_default_catalog() {
let mut config = default_config();
let mut overlay = ProvidersConfig {
default_provider: Some("ollama".to_string()),
..Default::default()
};
overlay.providers.insert(
"custom".to_string(),
ProviderDef {
base_url: "https://llm.example.test/v1".to_string(),
chat_endpoint: "/chat/completions".to_string(),
..Default::default()
},
);
config.merge_from(&overlay);
assert_eq!(config.default_provider.as_deref(), Some("ollama"));
assert!(config.providers.contains_key("custom"));
assert!(config.providers.contains_key("anthropic"));
assert!(config.providers.contains_key("ollama"));
}
#[test]
fn test_model_params_empty() {
let params = model_params("claude-sonnet-4-20250514");
assert!(params.is_empty());
}
#[test]
fn test_user_overrides_add_provider_and_alias() {
reset_overrides();
let mut overlay = ProvidersConfig::default();
overlay.providers.insert(
"acme".to_string(),
ProviderDef {
base_url: "https://llm.acme.test/v1".to_string(),
chat_endpoint: "/chat/completions".to_string(),
..Default::default()
},
);
overlay.aliases.insert(
"acme-fast".to_string(),
AliasDef {
id: "acme/model-fast".to_string(),
provider: "acme".to_string(),
tool_format: Some("native".to_string()),
},
);
set_user_overrides(Some(overlay));
let (model, provider) = resolve_model("acme-fast");
assert_eq!(model, "acme/model-fast");
assert_eq!(provider.as_deref(), Some("acme"));
assert!(provider_names().contains(&"acme".to_string()));
assert_eq!(
provider_config("acme").map(|provider| provider.base_url),
Some("https://llm.acme.test/v1".to_string())
);
reset_overrides();
}
#[test]
fn test_default_tool_format_uses_capability_matrix() {
reset_overrides();
assert_eq!(
default_tool_format("qwen3.6-35b-a3b-ud-q4-k-xl", "llamacpp"),
"text"
);
assert_eq!(
default_tool_format("devstral-small-2:24b", "ollama"),
"text"
);
assert_eq!(
default_tool_format("ollama-devstral-small-2-native", "ollama"),
"native"
);
assert_eq!(default_tool_format("gemma-4-26b-a4b-it", "local"), "text");
assert_eq!(
default_tool_format("deepseek/deepseek-v3.2", "openrouter"),
"text"
);
assert_eq!(
default_tool_format("qwen/qwen3-coder-flash", "openrouter"),
"text"
);
}
#[test]
fn test_user_overrides_add_model_catalog_pricing_and_qc_defaults() {
reset_overrides();
let mut overlay = ProvidersConfig::default();
overlay.models.insert(
"acme/model-fast".to_string(),
ModelDef {
name: "Acme Fast".to_string(),
provider: "acme".to_string(),
context_window: 65_536,
runtime_context_window: None,
stream_timeout: Some(42.0),
capabilities: vec!["tools".to_string(), "streaming".to_string()],
pricing: Some(ModelPricing {
input_per_mtok: 1.25,
output_per_mtok: 2.5,
cache_read_per_mtok: Some(0.25),
cache_write_per_mtok: None,
}),
deprecated: false,
deprecation_note: None,
superseded_by: None,
fast_mode: None,
quality_tags: Vec::new(),
availability: ModelAvailability::default(),
tier: None,
open_weight: None,
strengths: Vec::new(),
benchmarks: std::collections::BTreeMap::new(),
family: None,
lineage: None,
complementary_with: Vec::new(),
avoid_as_reviewer_for: Vec::new(),
},
);
overlay
.qc_defaults
.insert("acme".to_string(), "acme/model-cheap".to_string());
set_user_overrides(Some(overlay));
let entry = model_catalog_entry("acme/model-fast").expect("catalog entry");
assert_eq!(entry.context_window, 65_536);
assert_eq!(
entry.capabilities,
vec!["streaming".to_string(), "tools".to_string()]
);
assert_eq!(
entry.pricing.as_ref().map(|pricing| pricing.input_per_mtok),
Some(1.25)
);
assert_eq!(
pricing_per_1k_for("acme", "acme/model-fast"),
Some((0.00125, 0.0025))
);
assert_eq!(
qc_default_model("acme").as_deref(),
Some("acme/model-cheap")
);
reset_overrides();
}
#[test]
fn test_user_overrides_prepend_inference_rules() {
reset_overrides();
let mut overlay = ProvidersConfig::default();
overlay.inference_rules.push(InferenceRule {
pattern: Some("internal-*".to_string()),
contains: None,
exact: None,
provider: "openai".to_string(),
});
set_user_overrides(Some(overlay));
assert_eq!(infer_provider("internal-foo"), "openai");
reset_overrides();
}
#[test]
fn embedded_providers_toml_parses_and_is_not_trivially_empty() {
let config = default_config();
assert!(
config.providers.len() >= 10,
"expected >=10 providers in embedded catalog, got {}",
config.providers.len()
);
assert!(
config.models.len() >= 20,
"expected >=20 models in embedded catalog, got {}",
config.models.len()
);
assert!(
config.aliases.len() >= 15,
"expected >=15 aliases in embedded catalog, got {}",
config.aliases.len()
);
assert_eq!(config.default_provider.as_deref(), Some("anthropic"));
}
#[test]
fn embedded_catalog_every_deprecated_model_has_a_note() {
let config = default_config();
let offenders: Vec<&str> = config
.models
.iter()
.filter(|(_, model)| {
model.deprecated
&& model
.deprecation_note
.as_deref()
.unwrap_or("")
.trim()
.is_empty()
})
.map(|(id, _)| id.as_str())
.collect();
assert!(
offenders.is_empty(),
"deprecated models missing a deprecation_note: {offenders:?}"
);
}
#[test]
fn embedded_catalog_every_model_targets_a_registered_provider() {
let config = default_config();
let known: std::collections::BTreeSet<&str> =
config.providers.keys().map(String::as_str).collect();
let orphans: Vec<(&str, &str)> = config
.models
.iter()
.filter(|(_, model)| !known.contains(model.provider.as_str()))
.map(|(id, model)| (id.as_str(), model.provider.as_str()))
.collect();
assert!(
orphans.is_empty(),
"models reference unknown providers: {orphans:?}"
);
}
#[test]
fn embedded_catalog_every_alias_targets_a_registered_provider() {
let config = default_config();
let known: std::collections::BTreeSet<&str> =
config.providers.keys().map(String::as_str).collect();
let orphans: Vec<(&str, &str)> = config
.aliases
.iter()
.filter(|(_, alias)| !known.contains(alias.provider.as_str()))
.map(|(name, alias)| (name.as_str(), alias.provider.as_str()))
.collect();
assert!(
orphans.is_empty(),
"aliases reference unknown providers: {orphans:?}"
);
}
#[test]
fn embedded_catalog_every_qc_default_targets_a_known_model() {
let config = default_config();
let orphans: Vec<(&str, &str)> = config
.qc_defaults
.iter()
.filter(|(_, model_id)| !config.models.contains_key(model_id.as_str()))
.map(|(provider, model_id)| (provider.as_str(), model_id.as_str()))
.collect();
assert!(
orphans.is_empty(),
"qc_defaults reference unknown models: {orphans:?}"
);
}
#[test]
fn embedded_catalog_pricing_rates_are_non_negative() {
let config = default_config();
for (id, model) in &config.models {
let Some(pricing) = &model.pricing else {
continue;
};
assert!(
pricing.input_per_mtok >= 0.0 && pricing.output_per_mtok >= 0.0,
"{id}: negative pricing — in={} out={}",
pricing.input_per_mtok,
pricing.output_per_mtok
);
if let Some(rate) = pricing.cache_read_per_mtok {
assert!(rate >= 0.0, "{id}: negative cache_read rate {rate}");
}
if let Some(rate) = pricing.cache_write_per_mtok {
assert!(rate >= 0.0, "{id}: negative cache_write rate {rate}");
}
}
}
#[test]
fn model_availability_parses_known_strings() {
assert_eq!(
ModelAvailability::parse("serverless"),
Some(ModelAvailability::Serverless)
);
assert_eq!(
ModelAvailability::parse("dedicated"),
Some(ModelAvailability::Dedicated)
);
assert_eq!(
ModelAvailability::parse("unknown"),
Some(ModelAvailability::Unknown)
);
assert_eq!(ModelAvailability::parse("provisioned"), None);
for value in [
ModelAvailability::Serverless,
ModelAvailability::Dedicated,
ModelAvailability::Unknown,
] {
assert_eq!(ModelAvailability::parse(value.as_str()), Some(value));
}
}
#[test]
fn embedded_catalog_marks_together_dedicated_route_as_dedicated() {
let config = default_config();
let model = config
.models
.get("Qwen/Qwen3-Coder-Next-FP8")
.expect("Together Qwen3 Coder Next FP8 is cataloged");
assert_eq!(model.provider, "together");
assert_eq!(model.availability, ModelAvailability::Dedicated);
}
#[test]
fn embedded_catalog_dedicated_models_are_not_targeted_by_tier_aliases() {
let config = default_config();
let dedicated: std::collections::BTreeSet<(&str, &str)> = config
.models
.iter()
.filter(|(_, model)| model.availability == ModelAvailability::Dedicated)
.map(|(id, model)| (model.provider.as_str(), id.as_str()))
.collect();
for (name, alias) in &config.aliases {
if matches!(
name.as_str(),
"frontier"
| "mid"
| "small"
| "tier/frontier"
| "tier/mid"
| "tier/small"
| "sonnet"
| "opus"
| "haiku"
) {
assert!(
!dedicated.contains(&(alias.provider.as_str(), alias.id.as_str())),
"tier alias `{name}` targets dedicated-only route `{}/{}`",
alias.provider,
alias.id,
);
}
}
}
#[test]
fn embedded_catalog_tier_aliases_resolve_to_active_models() {
for alias in ["frontier", "mid", "small"] {
let (model, _provider) = resolve_tier_model(alias, None)
.unwrap_or_else(|| panic!("tier alias `{alias}` must resolve"));
let entry = model_catalog_entry(&model).unwrap_or_else(|| {
panic!("tier alias `{alias}` -> `{model}` must be a registered catalog entry")
});
assert!(
!entry.deprecated,
"tier alias `{alias}` resolves to deprecated model `{model}` ({:?})",
entry.deprecation_note
);
}
}
#[test]
fn opus_alias_tracks_claude_opus_4_8_with_fast_mode() {
let (model, provider) = resolve_model("opus");
assert_eq!(model, "claude-opus-4-8");
assert_eq!(provider.as_deref(), Some("anthropic"));
let opus48 = model_catalog_entry("claude-opus-4-8").expect("opus 4.8 catalog entry");
assert!(!opus48.deprecated, "newest Opus must not be deprecated");
let fast = opus48.fast_mode.expect("opus 4.8 advertises fast mode");
assert_eq!(fast.param, "speed");
assert_eq!(fast.value, "fast");
assert_eq!(fast.status.as_deref(), Some("research_preview"));
let fast_pricing = fast.pricing.expect("fast mode carries premium pricing");
let standard = opus48.pricing.expect("opus 4.8 standard pricing");
assert!(
fast_pricing.input_per_mtok > standard.input_per_mtok,
"fast mode must be premium-priced relative to standard"
);
}
#[test]
fn superseded_opus_models_point_at_claude_opus_4_8() {
for model in ["claude-opus-4-7", "claude-opus-4-6"] {
let entry =
model_catalog_entry(model).unwrap_or_else(|| panic!("{model} catalog entry"));
assert!(entry.deprecated, "{model} should be deprecated");
assert_eq!(
entry.superseded_by.as_deref(),
Some("claude-opus-4-8"),
"{model} should be superseded by claude-opus-4-8"
);
}
}
#[test]
fn gpt_5_5_fast_mode_rides_service_tier() {
let entry = model_catalog_entry("gpt-5.5").expect("gpt-5.5 catalog entry");
let fast = entry.fast_mode.expect("gpt-5.5 advertises a fast tier");
assert_eq!(fast.param, "service_tier");
assert_eq!(fast.status.as_deref(), Some("ga"));
}
}