Skip to content

Commit 02d46ee

Browse files
committed
feat: auto-detect provider from API key env vars
When a user sets an API key env var (e.g. DEEPSEEK_API_KEY) but doesn't explicitly configure a provider, dirge now auto-detects the matching provider instead of falling back to openrouter. Resolution order: CLI flag > config file > env var detection > openrouter Supports: DEEPSEEK_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, GLM_API_KEY, OLLAMA_API_KEY, OPENROUTER_API_KEY Signed-off-by: allen-munsch <james.a.munsch@gmail.com>
1 parent 6d7708f commit 02d46ee

2 files changed

Lines changed: 79 additions & 0 deletions

File tree

src/cli.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ impl Cli {
149149
.as_deref()
150150
.or(cfg.provider.as_deref())
151151
.map(CompactString::new)
152+
.or_else(|| crate::provider::auto_detect_provider().map(CompactString::new))
152153
.unwrap_or_else(|| CompactString::new("openrouter"))
153154
}
154155

src/provider.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,29 @@ fn provider_env_var(kind: ProviderKind) -> &'static str {
133133
}
134134
}
135135

136+
/// Auto-detect provider from environment variables when none is explicitly configured.
137+
/// Returns the provider name string (e.g. "deepseek") for the first matching env var.
138+
pub fn auto_detect_provider() -> Option<&'static str> {
139+
let candidates: &[(&str, &str)] = &[
140+
("DEEPSEEK_API_KEY", "deepseek"),
141+
("OPENAI_API_KEY", "openai"),
142+
("ANTHROPIC_API_KEY", "anthropic"),
143+
("GEMINI_API_KEY", "gemini"),
144+
("GLM_API_KEY", "glm"),
145+
("OLLAMA_API_KEY", "ollama"),
146+
("OPENROUTER_API_KEY", "openrouter"),
147+
];
148+
for (env_var, provider_name) in candidates {
149+
if std::env::var(env_var)
150+
.map(|v| !v.is_empty())
151+
.unwrap_or(false)
152+
{
153+
return Some(provider_name);
154+
}
155+
}
156+
None
157+
}
158+
136159
fn resolve_api_key(
137160
kind: ProviderKind,
138161
api_key_env_override: Option<&str>,
@@ -519,3 +542,58 @@ pub async fn build_agent(
519542
AnyModel::Custom(m) => build_inner!(m, Custom),
520543
}
521544
}
545+
546+
#[cfg(test)]
547+
mod tests {
548+
use super::*;
549+
550+
fn clean_env() {
551+
for var in &[
552+
"DEEPSEEK_API_KEY",
553+
"OPENAI_API_KEY",
554+
"ANTHROPIC_API_KEY",
555+
"GEMINI_API_KEY",
556+
"GLM_API_KEY",
557+
"OLLAMA_API_KEY",
558+
"OPENROUTER_API_KEY",
559+
] {
560+
unsafe { std::env::remove_var(var) };
561+
}
562+
}
563+
564+
#[test]
565+
fn auto_detect_returns_none_when_no_vars_set() {
566+
clean_env();
567+
assert_eq!(auto_detect_provider(), None);
568+
}
569+
570+
#[test]
571+
fn auto_detect_finds_deepseek_when_key_set() {
572+
clean_env();
573+
unsafe { std::env::set_var("DEEPSEEK_API_KEY", "sk-test-123") };
574+
assert_eq!(auto_detect_provider(), Some("deepseek"));
575+
}
576+
577+
#[test]
578+
fn auto_detect_finds_openai_when_key_set() {
579+
clean_env();
580+
unsafe { std::env::set_var("OPENAI_API_KEY", "sk-test-456") };
581+
assert_eq!(auto_detect_provider(), Some("openai"));
582+
}
583+
584+
#[test]
585+
fn auto_detect_skips_empty_var() {
586+
clean_env();
587+
unsafe { std::env::set_var("DEEPSEEK_API_KEY", "") };
588+
unsafe { std::env::set_var("OPENAI_API_KEY", "sk-test-789") };
589+
assert_eq!(auto_detect_provider(), Some("openai"));
590+
}
591+
592+
#[test]
593+
fn auto_detect_returns_first_match_in_order() {
594+
clean_env();
595+
unsafe { std::env::set_var("DEEPSEEK_API_KEY", "sk-ds") };
596+
unsafe { std::env::set_var("OPENAI_API_KEY", "sk-oai") };
597+
assert_eq!(auto_detect_provider(), Some("deepseek"));
598+
}
599+
}

0 commit comments

Comments
 (0)