Skip to main content

llm_kernel/provider/
capability.rs

1use super::catalog::ServiceDescriptor;
2
3/// How a provider authenticates API requests.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum AuthStrategy {
6    /// No authentication required (e.g. local Ollama).
7    None,
8    /// Hardcoded token in the catalog (e.g. a free-tier key).
9    Literal,
10    /// Read secret from environment variable.
11    Secret,
12    /// Unknown authentication mode.
13    Unknown,
14}
15
16/// Capability profile for a provider — determines auth strategy and feature support.
17pub trait CapabilityProfile {
18    fn auth_strategy(&self) -> AuthStrategy;
19    fn clears_anthropic_api_key(&self) -> bool;
20    fn supports_model_tiers(&self) -> bool;
21}
22
23fn auth_mode_to_strategy(value: &str) -> AuthStrategy {
24    match value {
25        "none" => AuthStrategy::None,
26        "literal" => AuthStrategy::Literal,
27        "secret" => AuthStrategy::Secret,
28        _ => AuthStrategy::Unknown,
29    }
30}
31
32fn clears_api_key_for_family(family: &str) -> bool {
33    matches!(family, "openrouter" | "local" | "custom_unknown")
34}
35
36fn supports_tiers_for_family(family: &str) -> bool {
37    !matches!(family, "claude_strict")
38}
39
40impl CapabilityProfile for ServiceDescriptor {
41    fn auth_strategy(&self) -> AuthStrategy {
42        auth_mode_to_strategy(&self.auth_mode)
43    }
44
45    fn clears_anthropic_api_key(&self) -> bool {
46        clears_api_key_for_family(&self.family)
47    }
48
49    fn supports_model_tiers(&self) -> bool {
50        supports_tiers_for_family(&self.family)
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use std::collections::HashMap;
58
59    fn make_descriptor(auth_mode: &str, family: &str) -> ServiceDescriptor {
60        ServiceDescriptor {
61            id: "test".to_string(),
62            display_name: "Test".to_string(),
63            description: String::new(),
64            category: "test".to_string(),
65            family: family.to_string(),
66            auth_mode: auth_mode.to_string(),
67            key_var: String::new(),
68            literal_auth_token: String::new(),
69            base_url: String::new(),
70            default_model: String::new(),
71            model_tiers: HashMap::new(),
72            model_choices: vec![],
73            test_url: String::new(),
74            setup: vec![],
75            usage: vec![],
76            api_base_url: None,
77            npm_package: None,
78            doc_url: None,
79            models: vec![],
80        }
81    }
82
83    #[test]
84    fn test_auth_mode_mapping() {
85        assert_eq!(auth_mode_to_strategy("none"), AuthStrategy::None);
86        assert_eq!(auth_mode_to_strategy("literal"), AuthStrategy::Literal);
87        assert_eq!(auth_mode_to_strategy("secret"), AuthStrategy::Secret);
88        assert_eq!(auth_mode_to_strategy("other"), AuthStrategy::Unknown);
89    }
90
91    #[test]
92    fn test_secret_provider_capability() {
93        let desc = make_descriptor("secret", "openrouter");
94        assert_eq!(desc.auth_strategy(), AuthStrategy::Secret);
95        assert!(desc.clears_anthropic_api_key());
96        assert!(desc.supports_model_tiers());
97    }
98
99    #[test]
100    fn test_claude_strict_invariants() {
101        assert!(!clears_api_key_for_family("claude_strict"));
102        assert!(!supports_tiers_for_family("claude_strict"));
103    }
104
105    #[test]
106    fn test_openrouter_invariants() {
107        assert!(clears_api_key_for_family("openrouter"));
108        assert!(supports_tiers_for_family("openrouter"));
109    }
110
111    #[test]
112    fn test_local_family_clears_api_key() {
113        assert!(clears_api_key_for_family("local"));
114    }
115
116    #[test]
117    fn test_none_auth_strategy() {
118        let desc = make_descriptor("none", "local");
119        assert_eq!(desc.auth_strategy(), AuthStrategy::None);
120    }
121}