use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::env;
use std::fs;
static VERTEX_ADC_CHECK: Lazy<bool> = Lazy::new(check_vertex_adc_credentials);
fn check_vertex_adc_credentials() -> bool {
if let Ok(path) = env::var("GOOGLE_APPLICATION_CREDENTIALS") {
return fs::metadata(&path).is_ok();
}
let default_path =
dirs::home_dir().map(|h| h.join(".config/gcloud/application_default_credentials.json"));
default_path
.map(|p| fs::metadata(p).is_ok())
.unwrap_or(false)
}
fn get_env(key: &str) -> Option<String> {
env::var(key).ok().or_else(|| get_proc_env(key))
}
#[allow(dead_code)]
fn get_proc_env(_key: &str) -> Option<String> {
#[allow(unused_imports)]
use std::os::unix::ffi::OsStrExt;
#[cfg(target_os = "linux")]
{
if !env::var("PATH").is_ok() || std::env::vars().count() > 0 {
return None;
}
let Ok(contents) = fs::read_to_string("/proc/self/environ") else {
return None;
};
for segment in contents.split('\0') {
if let Some(pos) = segment.find('=') {
let k = segment[..pos].as_bytes();
let v = &segment[pos + 1..];
if k == key.as_bytes() {
return Some(v.to_string());
}
}
}
}
None
}
pub fn find_env_keys(provider: &str) -> Option<Vec<&'static str>> {
let keys = match provider {
"github-copilot" | "copilot" => vec!["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"],
"anthropic" => vec!["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
"openai" | "openai-responses" => vec![
"OPENAI_API_KEY",
"AZURE_OPENAI_API_KEY", ],
"google" | "gemini" => vec!["GEMINI_API_KEY", "GOOGLE_API_KEY"],
"vertex" | "google-vertex" => vec!["GOOGLE_CLOUD_API_KEY"],
"azure" | "azure-openai" => vec!["AZURE_OPENAI_API_KEY"],
"groq" => vec!["GROQ_API_KEY"],
"cerebras" => vec!["CEREBRAS_API_KEY"],
"xai" => vec!["XAI_API_KEY"],
"openrouter" => vec!["OPENROUTER_API_KEY"],
"vercel-ai-gateway" => vec!["AI_GATEWAY_API_KEY"],
"zai" => vec!["ZAI_API_KEY"],
"mistral" => vec!["MISTRAL_API_KEY"],
"minimax" | "minimax-cn" => vec!["MINIMAX_API_KEY", "MINIMAX_CN_API_KEY"],
"moonshotai" | "moonshotai-cn" | "kimi" | "kimi-coding" => {
vec!["MOONSHOT_API_KEY", "KIMI_API_KEY"]
}
"huggingface" | "hf" => vec!["HF_TOKEN", "HUGGINGFACE_TOKEN"],
"fireworks" => vec!["FIREWORKS_API_KEY"],
"deepseek" => vec!["DEEPSEEK_API_KEY"],
"opencode" => vec!["OPENCODE_API_KEY"],
"xiaomi" => vec!["XIAOMI_API_KEY"],
"cloudflare" | "cloudflare-workers-ai" | "cloudflare-ai-gateway" => {
vec!["CLOUDFLARE_API_KEY", "CLOUDFLARE_AI_GATEWAY_API_KEY"]
}
_ => return None,
};
Some(keys)
}
pub fn has_env_key(provider: &str) -> bool {
find_env_keys(provider)
.map(|keys| keys.iter().any(|k| get_env(k).is_some()))
.unwrap_or(false)
}
fn first_of(keys: &[&str]) -> Option<String> {
for key in keys {
if let Some(value) = get_env(key) {
if !value.is_empty() {
return Some(value);
}
}
}
None
}
pub fn get_env_api_key(provider: &str) -> Option<String> {
let keys = find_env_keys(provider)?;
let key = first_of(&keys)?;
if key == "<authenticated>" || key.starts_with("sk-") && key.len() < 10 {
return None;
}
Some(key)
}
pub fn has_vertex_adc() -> bool {
*VERTEX_ADC_CHECK
}
pub fn has_vertex_adc_full() -> bool {
let has_creds = has_vertex_adc();
let has_project = get_env("GOOGLE_CLOUD_PROJECT")
.or_else(|| get_env("GCLOUD_PROJECT"))
.is_some();
let has_location = get_env("GOOGLE_CLOUD_LOCATION").is_some();
has_creds && has_project && has_location
}
pub fn has_bedrock_creds() -> bool {
if get_env("AWS_ACCESS_KEY_ID").is_some() && get_env("AWS_SECRET_ACCESS_KEY").is_some() {
return true;
}
if get_env("AWS_PROFILE").is_some() {
return true;
}
if get_env("AWS_BEARER_TOKEN_BEDROCK").is_some() {
return true;
}
if get_env("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI").is_some()
|| get_env("AWS_CONTAINER_CREDENTIALS_FULL_URI").is_some()
{
return true;
}
if get_env("AWS_WEB_IDENTITY_TOKEN_FILE").is_some() {
return true;
}
false
}
pub fn has_bedrock_creds_full() -> bool {
if get_env("AWS_ACCESS_KEY_ID").is_some() && get_env("AWS_SECRET_ACCESS_KEY").is_some() {
return true;
}
if get_env("AWS_PROFILE").is_some() {
return true;
}
if get_env("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI").is_some()
|| get_env("AWS_CONTAINER_CREDENTIALS_FULL_URI").is_some()
|| get_env("AWS_WEB_IDENTITY_TOKEN_FILE").is_some()
{
return true;
}
if get_env("AWS_BEARER_TOKEN_BEDROCK").is_some() {
return true;
}
false
}
pub fn get_all_env_keys() -> HashMap<String, String> {
let mut result = HashMap::new();
let mappings: [(&str, fn() -> Option<String>); 17] = [
("anthropic", || {
first_of(&["ANTHROPIC_API_KEY", "ANTHROPIC_OAUTH_TOKEN"])
}),
("openai", || first_of(&["OPENAI_API_KEY"])),
("github-copilot", || {
first_of(&["GITHUB_TOKEN", "GH_TOKEN", "COPILOT_GITHUB_TOKEN"])
}),
("google", || first_of(&["GEMINI_API_KEY"])),
("vertex", || first_of(&["GOOGLE_CLOUD_API_KEY"])),
("groq", || first_of(&["GROQ_API_KEY"])),
("cerebras", || first_of(&["CEREBRAS_API_KEY"])),
("xai", || first_of(&["XAI_API_KEY"])),
("openrouter", || first_of(&["OPENROUTER_API_KEY"])),
("mistral", || first_of(&["MISTRAL_API_KEY"])),
("deepseek", || first_of(&["DEEPSEEK_API_KEY"])),
("azure", || first_of(&["AZURE_OPENAI_API_KEY"])),
("cloudflare", || first_of(&["CLOUDFLARE_API_KEY"])),
("huggingface", || first_of(&["HF_TOKEN"])),
("fireworks", || first_of(&["FIREWORKS_API_KEY"])),
("moonshotai", || first_of(&["MOONSHOT_API_KEY"])),
("bedrock", || {
first_of(&["AWS_ACCESS_KEY_ID", "AWS_PROFILE"])
}),
];
for (provider, get_key) in mappings.iter() {
if let Some(value) = get_key() {
result.insert(provider.to_string(), value);
}
}
result
}
pub fn supports_oauth_env(provider: &str) -> bool {
matches!(provider, "anthropic")
}
pub fn get_oauth_env_token(provider: &str) -> Option<String> {
match provider {
"anthropic" => get_env("ANTHROPIC_OAUTH_TOKEN"),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
#[test]
fn test_find_env_keys_anthropic() {
let keys = find_env_keys("anthropic").unwrap();
assert!(keys.contains(&"ANTHROPIC_API_KEY"));
assert!(keys.contains(&"ANTHROPIC_OAUTH_TOKEN"));
}
#[test]
fn test_find_env_keys_copilot() {
let keys = find_env_keys("github-copilot").unwrap();
assert!(keys.contains(&"COPILOT_GITHUB_TOKEN"));
assert!(keys.contains(&"GH_TOKEN"));
assert!(keys.contains(&"GITHUB_TOKEN"));
}
#[test]
fn test_find_env_keys_unknown() {
assert!(find_env_keys("unknown-provider").is_none());
}
#[test]
fn test_first_of_returns_first() {
env::set_var("TEST_FIRST_OF_1", "value1");
env::set_var("TEST_FIRST_OF_2", "value2");
let result = first_of(&["TEST_FIRST_OF_1", "TEST_FIRST_OF_2"]);
assert_eq!(result, Some("value1".to_string()));
env::remove_var("TEST_FIRST_OF_1");
env::remove_var("TEST_FIRST_OF_2");
}
#[test]
fn test_first_of_skips_empty() {
env::set_var("TEST_FIRST_OF_SKIP", "");
env::set_var("TEST_FIRST_OF_SECOND", "second");
let result = first_of(&["TEST_FIRST_OF_SKIP", "TEST_FIRST_OF_SECOND"]);
assert_eq!(result, Some("second".to_string()));
env::remove_var("TEST_FIRST_OF_SKIP");
env::remove_var("TEST_FIRST_OF_SECOND");
}
#[test]
fn test_get_env_api_key() {
env::set_var("ANTHROPIC_API_KEY", "sk-test-key-123");
let result = get_env_api_key("anthropic");
assert_eq!(result, Some("sk-test-key-123".to_string()));
env::remove_var("ANTHROPIC_API_KEY");
}
#[test]
fn test_has_env_key() {
env::set_var("DEEPSEEK_API_KEY", "test-value");
let result = has_env_key("deepseek"); assert!(result);
env::remove_var("DEEPSEEK_API_KEY");
}
#[test]
fn test_vertex_adc_check_lazy() {
let result = *VERTEX_ADC_CHECK;
assert!(result == true || result == false);
}
#[test]
fn test_get_all_env_keys() {
let key = "OXI_TEST_GET_ALL_ENV_KEYS";
env::set_var(key, "test-value");
let all = get_all_env_keys();
assert!(all.len() <= 17);
env::remove_var(key);
}
#[test]
fn test_oauth_env_token() {
env::set_var("ANTHROPIC_OAUTH_TOKEN", "oauth-token-123");
let result = get_oauth_env_token("anthropic");
assert_eq!(result, Some("oauth-token-123".to_string()));
let not_oauth = get_oauth_env_token("openai");
assert!(not_oauth.is_none());
env::remove_var("ANTHROPIC_OAUTH_TOKEN");
}
#[test]
fn test_bedrock_creds_check() {
let result = has_bedrock_creds();
assert!(result == true || result == false);
}
#[test]
fn test_supports_oauth_env() {
assert!(supports_oauth_env("anthropic"));
assert!(!supports_oauth_env("openai"));
assert!(!supports_oauth_env("deepseek"));
}
#[test]
fn test_google_legacy_alias() {
let keys = find_env_keys("google");
assert!(keys.is_some());
assert!(keys.unwrap().contains(&"GEMINI_API_KEY"));
}
#[test]
fn test_moonshotai_aliases() {
let keys = find_env_keys("moonshotai");
assert!(keys.is_some());
assert!(keys.unwrap().contains(&"MOONSHOT_API_KEY"));
let kimi = find_env_keys("kimi");
assert!(kimi.is_some());
assert!(kimi.unwrap().contains(&"KIMI_API_KEY"));
}
}