Skip to main content

koda_cli/widgets/
provider_menu.rs

1//! Provider picker dropdown — thin wrapper around the generic dropdown.
2//!
3//! Used by both `/provider` (onboarding/model-list) and `/key` (API key mgmt).
4
5use super::dropdown::DropdownItem;
6
7/// A provider item for the dropdown.
8#[derive(Clone, Debug)]
9pub struct ProviderItem {
10    /// Internal key (e.g. "anthropic", "openai").
11    pub key: &'static str,
12    /// Display name (e.g. "Anthropic", "OpenAI").
13    pub name: &'static str,
14    /// Whether this is a local provider (no API key needed).
15    pub local: bool,
16    /// Whether an API key is configured for this provider.
17    /// Only meaningful in the `/key` picker — always `false` in `/provider`.
18    pub key_set: bool,
19}
20
21impl DropdownItem for ProviderItem {
22    fn label(&self) -> &str {
23        self.name
24    }
25    fn description(&self) -> String {
26        let mut desc = if self.local {
27            "Local, no API key".to_string()
28        } else {
29            String::new()
30        };
31        if self.key_set {
32            if !desc.is_empty() {
33                desc.push(' ');
34            }
35            desc.push('\u{2705}');
36        }
37        desc
38    }
39    fn matches_filter(&self, filter: &str) -> bool {
40        let f = filter.to_lowercase();
41        self.name.to_lowercase().contains(&f)
42            || self.key.to_lowercase().contains(&f)
43            || (self.local && "local".contains(&f))
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn key_set_shows_checkmark() {
53        let item = ProviderItem {
54            key: "anthropic",
55            name: "Anthropic",
56            local: false,
57            key_set: true,
58        };
59        assert!(item.description().contains('\u{2705}'));
60    }
61
62    #[test]
63    fn no_key_no_marker() {
64        let item = ProviderItem {
65            key: "openai",
66            name: "OpenAI",
67            local: false,
68            key_set: false,
69        };
70        assert!(item.description().is_empty());
71    }
72
73    #[test]
74    fn local_shows_description() {
75        let item = ProviderItem {
76            key: "ollama",
77            name: "Ollama",
78            local: true,
79            key_set: false,
80        };
81        assert!(item.description().contains("Local, no API key"));
82    }
83
84    #[test]
85    fn filter_matches_key_name_local() {
86        let item = ProviderItem {
87            key: "lmstudio",
88            name: "LM Studio",
89            local: true,
90            key_set: false,
91        };
92        assert!(item.matches_filter("lm"));
93        assert!(item.matches_filter("Studio"));
94        assert!(item.matches_filter("local"));
95        assert!(!item.matches_filter("gemini"));
96    }
97}