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]
33pub fn lookup(model_id: &str) -> Option<&'static ModelEntry> {
34 ModelRegistry::bundled().lookup(model_id)
35}
36
37pub fn models_for_provider(provider: &Provider) -> impl Iterator<Item = &'static ModelEntry> + '_ {
39 ModelRegistry::bundled().models_for_provider(provider)
40}
41
42pub fn models_with_ability(ability: Ability) -> impl Iterator<Item = &'static ModelEntry> {
44 ModelRegistry::bundled().models_with_ability(ability)
45}
46
47pub fn models_by_mode(mode: ModelMode) -> impl Iterator<Item = &'static ModelEntry> {
49 ModelRegistry::bundled().models_by_mode(mode)
50}
51
52pub fn all_models() -> impl Iterator<Item = &'static ModelEntry> {
54 ModelRegistry::bundled().all()
55}
56
57#[must_use]
61pub fn tier(model_id: &str) -> ModelTier {
62 ModelRegistry::bundled().tier(model_id)
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn test_lookup_gpt4o() {
71 let entry = lookup("gpt-4o").expect("gpt-4o should exist");
72 assert_eq!(entry.id(), "gpt-4o");
73 assert!(entry.max_input_tokens().unwrap_or(0) >= 128_000);
74 assert!(entry.has_ability(Ability::Vision));
75 assert!(entry.has_ability(Ability::ToolUse));
76 }
77
78 #[test]
79 fn test_lookup_claude() {
80 let entry = lookup("claude-sonnet-4-20250514").expect("claude-sonnet-4 should exist");
81 assert!(entry.max_input_tokens().unwrap_or(0) >= 200_000);
82 assert!(entry.has_ability(Ability::ToolUse));
83 }
84
85 #[test]
86 fn test_lookup_gemini() {
87 let entry = lookup("gemini/gemini-2.5-flash").expect("gemini-2.5-flash should exist");
88 assert!(entry.max_input_tokens().unwrap_or(0) >= 1_000_000);
89 }
90
91 #[test]
92 fn test_lookup_case_insensitive() {
93 let entry = lookup("GPT-4O").expect("case-insensitive lookup should work");
94 assert_eq!(entry.id(), "gpt-4o");
95 }
96
97 #[test]
98 fn test_lookup_prefix_match() {
99 let entry = lookup("gpt-4o-2024-05-13");
101 assert!(entry.is_some());
102 }
103
104 #[test]
105 fn test_models_with_ability() {
106 let vision_models: Vec<_> = models_with_ability(Ability::Vision).collect();
107 assert!(!vision_models.is_empty());
108 assert!(vision_models.iter().all(|m| m.has_ability(Ability::Vision)));
109 }
110
111 #[test]
112 fn test_models_by_mode() {
113 let chat_models: Vec<_> = models_by_mode(ModelMode::Chat).collect();
114 assert!(!chat_models.is_empty());
115 assert!(chat_models.iter().all(|m| m.mode() == ModelMode::Chat));
116 }
117
118 #[test]
119 fn test_registry_not_empty() {
120 let registry = ModelRegistry::bundled();
121 assert!(
122 registry.len() > 100,
123 "Expected 100+ models, got {}",
124 registry.len()
125 );
126 }
127
128 #[test]
129 fn test_pricing_populated() {
130 let entry = lookup("gpt-4o").expect("gpt-4o should exist");
131 assert!(entry.pricing().input_per_token() > 0.0);
132 assert!(entry.pricing().output_per_token() > 0.0);
133 }
134}