use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuiltinModelEntry {
pub id: String,
pub name: String,
pub api: String,
pub provider: String,
#[serde(default)]
pub reasoning: bool,
#[serde(default)]
pub input: Vec<String>,
#[serde(default)]
pub cost_input: f64,
#[serde(default)]
pub cost_output: f64,
#[serde(default)]
pub cost_cache_read: f64,
#[serde(default)]
pub cost_cache_write: f64,
#[serde(default)]
pub context_window: u32,
#[serde(default)]
pub max_tokens: u32,
}
impl BuiltinModelEntry {
pub fn supports_vision(&self) -> bool {
self.input.iter().any(|m| m == "image" || m == "Image")
}
pub fn supports_reasoning(&self) -> bool {
self.reasoning
}
pub fn calculate_cost(
&self,
input_tokens: u64,
output_tokens: u64,
cache_read: u64,
cache_write: u64,
) -> f64 {
let in_cost = (input_tokens as f64 / 1_000_000.0) * self.cost_input;
let out_cost = (output_tokens as f64 / 1_000_000.0) * self.cost_output;
let cr_cost = (cache_read as f64 / 1_000_000.0) * self.cost_cache_read;
let cw_cost = (cache_write as f64 / 1_000_000.0) * self.cost_cache_write;
in_cost + out_cost + cr_cost + cw_cost
}
}
pub fn load_builtin_models() -> &'static std::collections::BTreeMap<String, Vec<BuiltinModelEntry>>
{
&crate::catalog::CatalogRoot::get().models
}
pub fn builtin_model_count() -> usize {
load_builtin_models().values().map(|v| v.len()).sum()
}
pub fn models_index() -> &'static [(&'static str, &'static str)] {
include!(concat!(env!("OUT_DIR"), "/catalog_index.rs"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn models_index_loads_all_providers() {
let idx = models_index();
assert_eq!(idx.len(), 42, "expected 42 files, got {}", idx.len());
for (pid, body) in idx {
assert!(!pid.is_empty(), "empty provider id");
assert!(
body.contains("provider ="),
"{pid}: missing top-level provider field"
);
assert!(body.contains("[[model]]"), "{pid}: missing model entries");
}
}
#[test]
fn all_loaded_models_round_trip() {
let root = crate::catalog::CatalogRoot::get();
let total: usize = root.models.values().map(|v| v.len()).sum();
assert_eq!(
total, 1099,
"model count mismatch (expected 1099, got {total})"
);
}
#[test]
fn sentinel_pricing_counted() {
use crate::model_db::{builtin_model_count_sentinel, get_all_models};
let mut s = 0;
let mut z = 0;
let mut v = 0;
for m in get_all_models() {
if m.pricing_unverified() {
s += 1;
} else if m.cost_input == 0.0 && m.cost_output == 0.0 {
z += 1;
} else {
v += 1;
}
}
let helper = builtin_model_count_sentinel();
assert_eq!(
s, helper,
"helper and direct count disagree: {s} vs {helper}"
);
assert_eq!(s, 34, "sentinel count drift (expected 34, got {s})");
assert_eq!(
v + s + z,
1099,
"category counts don't sum to total (v={v} s={s} z={z})"
);
}
}