use bitrouter_ai::types::ProtocolList;
use bitrouter_sdk::config::{Config, Pattern, PatternMap, ProviderConfig};
use crate::providers::builtin;
use crate::providers::entry::ProtocolMapping;
use bitrouter_ai::auth::file::snapshot::CredentialStore;
pub fn zero_config() -> Config {
let mut config = Config::default();
config.server.listen = "127.0.0.1:4356".to_string();
config.server.skip_auth = true;
config.inherit_defaults = true;
for entry in builtin::all() {
let Some(env_var) = entry.auth.env_var() else {
continue;
};
if bitrouter_sdk::config::env_lookup(env_var)
.map(|v| !v.is_empty())
.unwrap_or(false)
{
config
.providers
.insert(entry.id.clone(), ProviderConfig::default());
}
}
config
}
pub fn zero_config_env_var_providers(
catalog: Option<&bitrouter_ai::catalog::types::RegistryData>,
) -> Vec<(String, String)> {
let mut out: Vec<(String, String)> = builtin::all()
.iter()
.filter_map(|e| e.auth.env_var().map(|v| (e.id.clone(), v.to_string())))
.collect();
if let Some(data) = catalog {
for p in &data.providers {
if !p.is_public() {
continue;
}
if let Some(var) = p.env_credential_var()
&& !out.iter().any(|(_, v)| v == &var)
{
out.push((p.name.clone(), var));
}
}
}
out
}
pub fn apply_builtin_defaults(config: &mut Config) {
if !config.inherit_defaults {
return;
}
for (id, provider) in config.providers.iter_mut() {
if bitrouter_ai::providers::retired::provider_message(id).is_some() {
continue;
}
let Some(builtin) = builtin::find(id) else {
continue;
};
if provider.api_base.is_empty() {
provider.api_base = builtin.api_base.clone();
}
if provider.api_protocol.is_empty() {
provider.api_protocol = protocol_mapping_to_pattern_map(&builtin.api_protocol);
}
if provider.protocol_endpoints.is_empty() && !builtin.protocol_endpoints.is_empty() {
provider.protocol_endpoints = builtin
.protocol_endpoints
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
}
if provider.class.is_none() {
provider.class = builtin.class;
}
if !provider.accounts.is_empty() {
continue;
}
if provider.api_key.is_empty()
&& let Some(env_var) = builtin.auth.env_var()
&& let Some(value) = bitrouter_sdk::config::env_lookup(env_var)
&& !value.is_empty()
{
provider.api_key = value;
}
if id != "bitrouter" && provider.api_key.is_empty() && builtin.auth.env_var().is_some() {
provider.active = false;
}
}
}
pub fn activate_stored_credential_providers(config: &mut Config, store: &CredentialStore) {
for (id, provider) in config.providers.iter_mut() {
if bitrouter_ai::providers::retired::provider_message(id).is_some() {
continue;
}
if !provider.active && !store.labels(id).is_empty() {
provider.active = true;
}
}
}
fn protocol_mapping_to_pattern_map(m: &ProtocolMapping) -> PatternMap<ProtocolList> {
let mut map = PatternMap::new();
match m {
ProtocolMapping::Single(list) => map.push(Pattern::Wildcard, list.clone()),
ProtocolMapping::PerModel(items) => {
for (k, v) in items {
map.push(Pattern::parse(k), v.clone());
}
}
}
map
}
#[cfg(test)]
mod tests {
use super::*;
use crate::providers::test_env::with_env;
use anyhow::Context;
use bitrouter_ai::types::ApiProtocol;
use bitrouter_sdk::config::{Config, ProviderConfig};
fn config_with(id: &str, mut p: ProviderConfig) -> Config {
let mut c = Config::default();
p.active = true;
c.providers.insert(id.to_string(), p);
c
}
fn provider_with_base(api_base: &str) -> ProviderConfig {
ProviderConfig {
api_base: api_base.to_string(),
..Default::default()
}
}
#[test]
fn fills_empty_api_base_and_protocol() -> anyhow::Result<()> {
let mut config = config_with("bitrouter", ProviderConfig::default());
apply_builtin_defaults(&mut config);
let p = &config.providers["bitrouter"];
assert_eq!(p.api_base, "https://api.bitrouter.ai/v1");
assert_eq!(
p.api_protocol.resolve("gpt-4o"),
Some(&ProtocolList(vec![ApiProtocol::ChatCompletions]))
);
Ok(())
}
#[test]
fn does_not_overwrite_user_overrides() -> anyhow::Result<()> {
let user = provider_with_base("https://gateway.internal.example/v1");
let mut config = config_with("bitrouter", user);
apply_builtin_defaults(&mut config);
let p = &config.providers["bitrouter"];
assert_eq!(p.api_base, "https://gateway.internal.example/v1");
assert_eq!(
p.api_protocol.resolve("gpt-4o"),
Some(&ProtocolList(vec![ApiProtocol::ChatCompletions]))
);
Ok(())
}
#[test]
fn resolves_env_var_when_present() -> anyhow::Result<()> {
with_env("BITROUTER_API_KEY", Some("br-from-env-xyz"), || {
let mut config = config_with("bitrouter", ProviderConfig::default());
apply_builtin_defaults(&mut config);
assert_eq!(config.providers["bitrouter"].api_key, "br-from-env-xyz");
Ok(())
})?;
Ok(())
}
#[test]
fn leaves_api_key_empty_when_env_unset() -> anyhow::Result<()> {
with_env("BITROUTER_API_KEY", None, || {
let mut config = config_with("bitrouter", ProviderConfig::default());
apply_builtin_defaults(&mut config);
assert!(config.providers["bitrouter"].api_key.is_empty());
Ok(())
})?;
Ok(())
}
#[test]
fn no_op_when_inherit_defaults_false() -> anyhow::Result<()> {
let mut config = config_with("bitrouter", ProviderConfig::default());
config.inherit_defaults = false;
apply_builtin_defaults(&mut config);
let p = &config.providers["bitrouter"];
assert!(p.api_base.is_empty());
assert!(p.api_protocol.is_empty());
Ok(())
}
#[test]
fn ignores_unknown_provider_ids() -> anyhow::Result<()> {
let mut config = config_with("openai", ProviderConfig::default());
apply_builtin_defaults(&mut config);
let p = &config.providers["openai"];
assert!(p.api_base.is_empty());
assert!(p.api_protocol.is_empty());
Ok(())
}
#[test]
fn keeps_cloud_provider_active_when_env_key_missing() -> anyhow::Result<()> {
with_env("BITROUTER_API_KEY", None, || {
let mut config = config_with("bitrouter", ProviderConfig::default());
apply_builtin_defaults(&mut config);
assert!(config.providers["bitrouter"].active);
Ok(())
})?;
Ok(())
}
#[test]
fn keeps_provider_active_when_user_supplied_key() -> anyhow::Result<()> {
with_env("BITROUTER_API_KEY", None, || {
let p = ProviderConfig {
api_key: "br-hardcoded".to_string(),
..ProviderConfig::default()
};
let mut config = config_with("bitrouter", p);
apply_builtin_defaults(&mut config);
assert!(config.providers["bitrouter"].active);
Ok(())
})?;
Ok(())
}
#[test]
fn stored_credential_reactivates_keyless_provider() -> anyhow::Result<()> {
use bitrouter_ai::auth::credentials::Credential;
use bitrouter_ai::auth::file::snapshot::CredentialStore;
use bitrouter_ai::auth::store::DEFAULT_ACCOUNT;
let dir = std::env::temp_dir().join(format!("br-activate-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).context("required fixture value")?;
let mut store = CredentialStore::load(dir.join("oauth-tokens.json"))
.context("required fixture value")?;
store
.set("anthropic", DEFAULT_ACCOUNT, Credential::ClaudeCodeCli)
.context("required fixture value")?;
let mut config = Config::default();
config.providers.insert(
"anthropic".to_string(),
ProviderConfig {
active: false, ..ProviderConfig::default()
},
);
config.providers.insert(
"openai".to_string(),
ProviderConfig {
active: false,
..ProviderConfig::default()
},
);
activate_stored_credential_providers(&mut config, &store);
assert!(
config.providers["anthropic"].active,
"a stored credential must re-activate the provider for routing"
);
assert!(
!config.providers["openai"].active,
"a provider with no stored credential stays inactive"
);
Ok(())
}
#[test]
fn zero_config_skips_the_cloud_gateway_without_its_env_var() -> anyhow::Result<()> {
with_env("BITROUTER_API_KEY", None, || {
let cfg = zero_config();
assert!(cfg.server.skip_auth);
assert!(cfg.inherit_defaults);
assert_eq!(cfg.server.listen, "127.0.0.1:4356");
assert!(
!cfg.providers.contains_key("bitrouter"),
"the cloud gateway must not be auto-enabled without its key"
);
Ok(())
})?;
Ok(())
}
#[test]
fn zero_config_auto_enables_the_cloud_gateway_with_its_env_var() -> anyhow::Result<()> {
with_env("BITROUTER_API_KEY", Some("br-from-env"), || {
let mut cfg = zero_config();
assert!(cfg.providers.contains_key("bitrouter"));
apply_builtin_defaults(&mut cfg);
let p = &cfg.providers["bitrouter"];
assert_eq!(p.api_key, "br-from-env");
assert_eq!(p.api_base, "https://api.bitrouter.ai/v1");
assert!(p.active);
Ok(())
})?;
Ok(())
}
#[test]
fn stored_retired_credentials_do_not_reactivate_providers() -> anyhow::Result<()> {
use bitrouter_ai::auth::credentials::Credential;
let directory = tempfile::tempdir()?;
let path = directory.path().join("credentials.json");
let mut store = CredentialStore::load(&path)?;
let mut config = Config::default();
for id in ["google-ai", "vertex"] {
store.set(
id,
"saved",
Credential::ApiKey {
value: "fixture-private".into(),
},
)?;
config.providers.insert(
id.into(),
bitrouter_sdk::config::ProviderConfig {
active: false,
..Default::default()
},
);
}
let original = std::fs::read(&path)?;
activate_stored_credential_providers(&mut config, &store);
assert!(config.providers.values().all(|provider| !provider.active));
assert_eq!(std::fs::read(&path)?, original);
Ok(())
}
}