Skip to main content

opencode_provider_manager/auth/
status.rs

1//! API key status detection for providers.
2
3use super::parser::AuthEntry;
4
5/// The authentication status of a provider.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum ProviderAuthStatus {
8    /// API key is configured and has a recognized format.
9    Configured {
10        /// Whether the key format looks valid.
11        format_valid: bool,
12    },
13    /// Auth is configured via environment variable reference.
14    EnvVar {
15        /// The environment variable name (e.g., "ANTHROPIC_API_KEY").
16        var_name: String,
17    },
18    /// OAuth token is configured.
19    OAuth,
20    /// No authentication is configured for this provider.
21    Missing,
22}
23
24impl ProviderAuthStatus {
25    /// Check the auth status for a given provider based on its auth entry
26    /// and provider configuration.
27    pub fn from_provider(provider_id: &str, auth_entry: Option<&AuthEntry>) -> Self {
28        match auth_entry {
29            Some(entry) => {
30                match entry.auth_type.as_str() {
31                    "api" => {
32                        if let Some(key) = &entry.key {
33                            ProviderAuthStatus::Configured {
34                                format_valid: is_valid_key_format(provider_id, key),
35                            }
36                        } else {
37                            ProviderAuthStatus::Missing
38                        }
39                    }
40                    "oauth" => ProviderAuthStatus::OAuth,
41                    _other => {
42                        // Unknown auth type, but something is configured
43                        ProviderAuthStatus::Configured {
44                            format_valid: false,
45                        }
46                    }
47                }
48            }
49            None => {
50                // Check if there's a well-known env var for this provider
51                if let Some(env_var) = provider_env_var(provider_id) {
52                    if std::env::var(env_var).is_ok() {
53                        ProviderAuthStatus::EnvVar {
54                            var_name: env_var.to_string(),
55                        }
56                    } else {
57                        ProviderAuthStatus::Missing
58                    }
59                } else {
60                    ProviderAuthStatus::Missing
61                }
62            }
63        }
64    }
65
66    /// Check auth status using env var fallback.
67    pub fn from_env_var(provider_id: &str) -> Self {
68        if let Some(env_var) = provider_env_var(provider_id) {
69            if std::env::var(env_var).is_ok() {
70                return ProviderAuthStatus::EnvVar {
71                    var_name: env_var.to_string(),
72                };
73            }
74        }
75        ProviderAuthStatus::Missing
76    }
77}
78
79/// Check if an API key matches expected format patterns for a provider.
80fn is_valid_key_format(provider_id: &str, key: &str) -> bool {
81    match provider_id {
82        "openai" => key.starts_with("sk-") && key.len() > 10,
83        "anthropic" => key.starts_with("sk-ant-") && key.len() > 10,
84        "google" | "gemini" => key.len() > 10,
85        "deepseek" => key.len() > 10,
86        "groq" => key.starts_with("gsk_") && key.len() > 10,
87        "openrouter" => key.starts_with("sk-or-") && key.len() > 10,
88        "xai" => key.len() > 10,
89        // For custom/unknown providers, just check it's not empty
90        _ => !key.is_empty(),
91    }
92}
93
94/// Get the environment variable name for a provider's API key.
95pub fn provider_env_var(provider_id: &str) -> Option<&'static str> {
96    match provider_id {
97        "openai" => Some("OPENAI_API_KEY"),
98        "anthropic" => Some("ANTHROPIC_API_KEY"),
99        "google" | "gemini" => Some("GOOGLE_API_KEY"),
100        "deepseek" => Some("DEEPSEEK_API_KEY"),
101        "groq" => Some("GROQ_API_KEY"),
102        "openrouter" => Some("OPENROUTER_API_KEY"),
103        "xai" => Some("XAI_API_KEY"),
104        "together" | "together-ai" => Some("TOGETHER_API_KEY"),
105        "fireworks" | "fireworks-ai" => Some("FIREWORKS_API_KEY"),
106        "cerebras" => Some("CEREBRAS_API_KEY"),
107        "mistral" => Some("MISTRAL_API_KEY"),
108        "perplexity" => Some("PERPLEXITY_API_KEY"),
109        _ => None,
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::super::parser::AuthEntry;
116    use super::*;
117    use std::collections::HashMap;
118
119    #[test]
120    fn test_configured_valid_key() {
121        let entry = AuthEntry {
122            auth_type: "api".to_string(),
123            key: Some("sk-ant-api03-longkey".to_string()),
124            token: None,
125            extra: HashMap::new(),
126        };
127        let status = ProviderAuthStatus::from_provider("anthropic", Some(&entry));
128        assert!(matches!(
129            status,
130            ProviderAuthStatus::Configured { format_valid: true }
131        ));
132    }
133
134    #[test]
135    fn test_configured_invalid_key_format() {
136        let entry = AuthEntry {
137            auth_type: "api".to_string(),
138            key: Some("short".to_string()),
139            token: None,
140            extra: HashMap::new(),
141        };
142        let status = ProviderAuthStatus::from_provider("anthropic", Some(&entry));
143        assert!(matches!(
144            status,
145            ProviderAuthStatus::Configured {
146                format_valid: false
147            }
148        ));
149    }
150
151    #[test]
152    fn test_oauth_status() {
153        let entry = AuthEntry {
154            auth_type: "oauth".to_string(),
155            key: None,
156            token: Some("gho_token".to_string()),
157            extra: HashMap::new(),
158        };
159        let status = ProviderAuthStatus::from_provider("github-copilot", Some(&entry));
160        assert_eq!(status, ProviderAuthStatus::OAuth);
161    }
162
163    #[test]
164    fn test_missing_status() {
165        let status = ProviderAuthStatus::from_provider("unknown-provider", None);
166        assert_eq!(status, ProviderAuthStatus::Missing);
167    }
168
169    #[test]
170    fn test_key_format_openai() {
171        assert!(is_valid_key_format("openai", "sk-proj-abc123def456"));
172        assert!(!is_valid_key_format("openai", "short"));
173    }
174
175    #[test]
176    fn test_key_format_anthropic() {
177        assert!(is_valid_key_format("anthropic", "sk-ant-api03-longkey"));
178        assert!(!is_valid_key_format("anthropic", "sk-wrong-prefix"));
179    }
180
181    #[test]
182    fn test_key_format_groq() {
183        assert!(is_valid_key_format("groq", "gsk_abc123longkey"));
184        assert!(!is_valid_key_format("groq", "short"));
185    }
186}