Skip to main content

navi_core/
provider_id.rs

1/// Canonical provider identity. Eliminates string-match routing.
2///
3/// Known providers have predefined constants. Any string is accepted.
4/// New built-in providers only need a constant and a `behavior_for_provider` entry.
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct ProviderId(String);
7
8impl ProviderId {
9    // ── Known provider constants ─────────────────────────────────────────────
10    pub const OPENAI: &'static str = "openai";
11    pub const ANTHROPIC: &'static str = "anthropic";
12    pub const GOOGLE_GEMINI: &'static str = "google-gemini";
13    pub const OPENROUTER: &'static str = "openrouter";
14    pub const GITHUB_COPILOT: &'static str = "github-copilot";
15    pub const OPENCODE: &'static str = "opencode";
16    pub const OPENCODE_ZEN: &'static str = "opencode-zen";
17    pub const OPENCODE_GO: &'static str = "opencode-go";
18    pub const COMMANDCODE: &'static str = "commandcode";
19    pub const CHARM_HYPER: &'static str = "charm-hyper";
20    pub const GROQ: &'static str = "groq";
21    pub const XAI: &'static str = "xai";
22    pub const MIMO_ANTHROPIC_CN: &'static str = "mimo-anthropic-cn";
23    pub const MIMO_ANTHROPIC_SGP: &'static str = "mimo-anthropic-sgp";
24    pub const MIMO_ANTHROPIC_AMS: &'static str = "mimo-anthropic-ams";
25    pub const NVIDIA: &'static str = "nvidia";
26
27    // ── Constructors ─────────────────────────────────────────────────────────
28
29    /// Parses a provider id from the config string form (e.g. `"openai"`,
30    /// `"google-gemini"`). Any string is accepted.
31    pub fn from_config_id(id: &str) -> Self {
32        Self(id.to_string())
33    }
34
35    /// Creates a `ProviderId` from a known constant. Panics in debug if the
36    /// constant is not one of the predefined values (for safety).
37    pub fn known(constant: &str) -> Self {
38        debug_assert!(
39            [
40                Self::OPENAI,
41                Self::ANTHROPIC,
42                Self::GOOGLE_GEMINI,
43                Self::OPENROUTER,
44                Self::GITHUB_COPILOT,
45                Self::OPENCODE,
46                Self::OPENCODE_ZEN,
47                Self::OPENCODE_GO,
48                Self::COMMANDCODE,
49                Self::CHARM_HYPER,
50                Self::GROQ,
51                Self::XAI,
52                Self::MIMO_ANTHROPIC_CN,
53                Self::MIMO_ANTHROPIC_SGP,
54                Self::MIMO_ANTHROPIC_AMS,
55                Self::NVIDIA,
56            ]
57            .contains(&constant),
58            "not a known provider constant: {constant}"
59        );
60        Self(constant.to_string())
61    }
62
63    // ── Accessors ────────────────────────────────────────────────────────────
64
65    /// Returns the canonical string form of this provider id.
66    pub fn as_str(&self) -> &str {
67        &self.0
68    }
69
70    /// Returns `true` if this provider belongs to the opencode family
71    /// (Opencode, OpencodeZen, or OpencodeGo).
72    pub fn is_opencode_family(&self) -> bool {
73        matches!(
74            self.0.as_str(),
75            Self::OPENCODE | Self::OPENCODE_ZEN | Self::OPENCODE_GO
76        )
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn from_config_id_stores_string_verbatim() {
86        assert_eq!(ProviderId::from_config_id("openai").as_str(), "openai");
87        assert_eq!(
88            ProviderId::from_config_id("google-gemini").as_str(),
89            "google-gemini"
90        );
91        assert_eq!(
92            ProviderId::from_config_id("custom-provider").as_str(),
93            "custom-provider"
94        );
95    }
96
97    #[test]
98    fn from_config_id_accepts_empty_string() {
99        assert_eq!(ProviderId::from_config_id("").as_str(), "");
100    }
101
102    #[test]
103    fn as_str_returns_inner_string() {
104        let id = ProviderId::from_config_id("anthropic");
105        assert_eq!(id.as_str(), "anthropic");
106    }
107
108    #[test]
109    fn known_accepts_all_predefined_constants() {
110        let constants = [
111            ProviderId::OPENAI,
112            ProviderId::ANTHROPIC,
113            ProviderId::GOOGLE_GEMINI,
114            ProviderId::OPENROUTER,
115            ProviderId::GITHUB_COPILOT,
116            ProviderId::OPENCODE,
117            ProviderId::OPENCODE_ZEN,
118            ProviderId::OPENCODE_GO,
119            ProviderId::COMMANDCODE,
120            ProviderId::GROQ,
121            ProviderId::XAI,
122            ProviderId::MIMO_ANTHROPIC_CN,
123            ProviderId::MIMO_ANTHROPIC_SGP,
124            ProviderId::MIMO_ANTHROPIC_AMS,
125            ProviderId::NVIDIA,
126        ];
127        for c in constants {
128            let id = ProviderId::known(c);
129            assert_eq!(id.as_str(), c);
130        }
131    }
132
133    #[test]
134    fn is_opencode_family_returns_true_for_opencode_variants() {
135        assert!(ProviderId::from_config_id("opencode").is_opencode_family());
136        assert!(ProviderId::from_config_id("opencode-zen").is_opencode_family());
137        assert!(ProviderId::from_config_id("opencode-go").is_opencode_family());
138    }
139
140    #[test]
141    fn is_opencode_family_returns_false_for_others() {
142        assert!(!ProviderId::from_config_id("openai").is_opencode_family());
143        assert!(!ProviderId::from_config_id("anthropic").is_opencode_family());
144        assert!(!ProviderId::from_config_id("commandcode").is_opencode_family());
145        assert!(!ProviderId::from_config_id("google-gemini").is_opencode_family());
146        assert!(!ProviderId::from_config_id("custom").is_opencode_family());
147    }
148
149    #[test]
150    fn known_equals_from_config_id_for_same_string() {
151        assert_eq!(
152            ProviderId::known(ProviderId::OPENAI),
153            ProviderId::from_config_id("openai")
154        );
155        assert_eq!(
156            ProviderId::known(ProviderId::ANTHROPIC),
157            ProviderId::from_config_id("anthropic")
158        );
159    }
160}