1pub mod convert;
18mod registry;
19#[cfg(feature = "remote")]
20pub mod remote;
21pub(crate) mod tier;
22pub mod types;
23
24pub use aither_core::llm::model::Ability;
25pub use registry::ModelRegistry;
26pub use tier::ModelTier;
27pub use types::{ModelEntry, ModelMode, Pricing, Provider};
28
29#[must_use]
34pub fn lookup(model_id: &str) -> Option<&'static ModelEntry> {
35 ModelRegistry::bundled().lookup(model_id)
36}
37
38pub fn models_for_provider(provider: &Provider) -> impl Iterator<Item = &'static ModelEntry> + '_ {
40 ModelRegistry::bundled().models_for_provider(provider)
41}
42
43pub fn models_with_ability(ability: Ability) -> impl Iterator<Item = &'static ModelEntry> {
45 ModelRegistry::bundled().models_with_ability(ability)
46}
47
48pub fn models_by_mode(mode: ModelMode) -> impl Iterator<Item = &'static ModelEntry> {
50 ModelRegistry::bundled().models_by_mode(mode)
51}
52
53pub fn all_models() -> impl Iterator<Item = &'static ModelEntry> {
55 ModelRegistry::bundled().all()
56}
57
58#[must_use]
62pub fn tier(model_id: &str) -> ModelTier {
63 ModelRegistry::bundled().tier(model_id)
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
73
74 #[test]
75 fn bundled_matches_runtime_conversion_of_snapshot() {
76 let runtime =
77 ModelRegistry::from_litellm_json(include_bytes!("../data/litellm_snapshot.json"));
78 let bundled: Vec<_> = all_models().map(ModelEntry::litellm_id).collect();
79 let converted: Vec<_> = runtime.all().map(ModelEntry::litellm_id).collect();
80 assert_eq!(bundled, converted);
81 }
82
83 #[test]
84 fn bundled_registry_is_populated() {
85 let registry = ModelRegistry::bundled();
86 assert!(
87 registry.len() > 100,
88 "Expected 100+ models, got {}",
89 registry.len()
90 );
91 assert_ne!(models_by_mode(ModelMode::Chat).count(), 0);
92 }
93
94 #[test]
95 fn every_entry_is_reachable_by_its_litellm_id() {
96 for entry in all_models() {
97 assert!(
98 lookup(entry.litellm_id()).is_some(),
99 "{} is not reachable by its own key",
100 entry.litellm_id()
101 );
102 assert!(
103 entry.litellm_id().ends_with(entry.id()),
104 "{} does not end with its canonical id {}",
105 entry.litellm_id(),
106 entry.id()
107 );
108 }
109 }
110
111 #[test]
112 fn query_helpers_return_only_matching_entries() {
113 assert!(models_with_ability(Ability::Vision).all(|m| m.has_ability(Ability::Vision)));
114 assert!(models_by_mode(ModelMode::Chat).all(|m| m.mode() == ModelMode::Chat));
115 assert!(models_for_provider(&Provider::OpenAI).all(|m| *m.provider() == Provider::OpenAI));
116 }
117
118 #[test]
119 fn image_generation_entries_classify_as_image_tier() {
120 for entry in models_by_mode(ModelMode::ImageGeneration) {
121 assert!(entry.has_ability(Ability::ImageGeneration));
122 assert_eq!(
123 ModelRegistry::bundled().tier_of(entry),
124 ModelTier::ImageGeneration
125 );
126 }
127 }
128}