opencode_provider_manager/auth/
status.rs1use super::parser::AuthEntry;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum ProviderAuthStatus {
8 Configured {
10 format_valid: bool,
12 },
13 EnvVar {
15 var_name: String,
17 },
18 OAuth,
20 Missing,
22}
23
24impl ProviderAuthStatus {
25 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 ProviderAuthStatus::Configured {
44 format_valid: false,
45 }
46 }
47 }
48 }
49 None => {
50 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 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
79fn 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 _ => !key.is_empty(),
91 }
92}
93
94pub 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}