Skip to main content

apollo/providers/
defaults.rs

1//! Default model per provider.
2//!
3//! A provider configured with no model has to land somewhere. Picking the
4//! wrong entry is a billing decision, not a cosmetic one, so these track the
5//! defaults published by NousResearch/hermes-agent — the first entry of each
6//! provider's curated list, which is what its
7//! `get_default_model_for_provider()` returns.
8//!
9//! Only providers apollo can actually construct appear here. An unknown
10//! provider yields `None` and the caller keeps whatever the config already
11//! had, rather than being sent to a model that may not exist on it.
12
13/// Provider name (and common aliases) to the model it defaults to.
14const DEFAULTS: &[(&str, &str)] = &[
15    ("anthropic", "claude-fable-5"),
16    ("claude", "claude-fable-5"),
17    ("chatgpt", "gpt-5.6-sol"),
18    ("openai", "gpt-5.4"),
19    ("copilot", "gpt-5.4"),
20    ("github-copilot", "gpt-5.4"),
21    ("xai", "grok-build-0.1"),
22    ("grok", "grok-build-0.1"),
23    ("gemini", "gemini-3.1-pro-preview"),
24    ("deepseek", "deepseek-v4-pro"),
25    ("moonshot", "kimi-k3"),
26    ("kimi", "kimi-k3"),
27    ("minimax", "MiniMax-M3"),
28    ("huggingface", "moonshotai/Kimi-K2.5"),
29    // Metered aggregator: hermes routes this one through its catalog-labeled
30    // default rather than the head of the list, because that list is ordered
31    // most-capable-first and defaulting to the head would silently pick the
32    // most expensive model.
33    ("openrouter", "z-ai/glm-5.2"),
34];
35
36/// The model a provider falls back to when none was configured.
37pub fn default_model_for_provider(provider: &str) -> Option<&'static str> {
38    let key = provider.trim();
39    DEFAULTS
40        .iter()
41        .find(|(name, _)| name.eq_ignore_ascii_case(key))
42        .map(|(_, model)| *model)
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn oauth_providers_get_their_own_defaults() {
51        assert_eq!(default_model_for_provider("chatgpt"), Some("gpt-5.6-sol"));
52        assert_eq!(default_model_for_provider("xai"), Some("grok-build-0.1"));
53        assert_eq!(
54            default_model_for_provider("anthropic"),
55            Some("claude-fable-5")
56        );
57    }
58
59    #[test]
60    fn aliases_resolve_to_the_same_model() {
61        for (a, b) in [
62            ("claude", "anthropic"),
63            ("grok", "xai"),
64            ("kimi", "moonshot"),
65        ] {
66            assert_eq!(
67                default_model_for_provider(a),
68                default_model_for_provider(b),
69                "{a} and {b} should agree"
70            );
71        }
72    }
73
74    #[test]
75    fn lookup_ignores_case_and_padding() {
76        assert_eq!(
77            default_model_for_provider("  AnThRoPiC "),
78            Some("claude-fable-5")
79        );
80    }
81
82    #[test]
83    fn an_unknown_provider_picks_nothing() {
84        assert_eq!(default_model_for_provider("not-a-provider"), None);
85        assert_eq!(default_model_for_provider(""), None);
86    }
87}