Skip to main content

codetether_agent/provider/
init_config.rs

1//! Build a [`ProviderRegistry`](super::ProviderRegistry) from the app [`Config`].
2
3use super::anthropic;
4use super::bedrock;
5use super::google;
6use super::openai;
7use super::registry::ProviderRegistry;
8use anyhow::Result;
9use std::sync::Arc;
10
11impl ProviderRegistry {
12    /// Initialize with default providers from the TOML config file.
13    ///
14    /// Reads `[providers.<name>]` sections and common `*_API_KEY` env vars.
15    ///
16    /// # Examples
17    ///
18    /// ```rust,no_run
19    /// use codetether_agent::provider::ProviderRegistry;
20    /// # async fn demo(cfg: &codetether_agent::config::Config) {
21    /// let registry = ProviderRegistry::from_config(cfg).await.unwrap();
22    /// # }
23    /// ```
24    pub async fn from_config(config: &crate::config::Config) -> Result<Self> {
25        let mut registry = Self::new();
26
27        // ---- OpenAI ----
28        if let Some(pc) = config.providers.get("openai") {
29            if let Some(key) = &pc.api_key {
30                registry.register(Arc::new(openai::OpenAIProvider::new(key.clone())?));
31            }
32        } else if let Ok(key) = std::env::var("OPENAI_API_KEY") {
33            registry.register(Arc::new(openai::OpenAIProvider::new(key)?));
34        }
35
36        // ---- Anthropic ----
37        if let Some(pc) = config.providers.get("anthropic") {
38            if let Some(key) = &pc.api_key {
39                let p = if let Some(url) = pc.base_url.clone() {
40                    anthropic::AnthropicProvider::with_base_url(key.clone(), url, "anthropic")?
41                } else {
42                    anthropic::AnthropicProvider::new(key.clone())?
43                };
44                registry.register(Arc::new(p));
45            }
46        } else if let Ok(key) = std::env::var("ANTHROPIC_API_KEY") {
47            registry.register(Arc::new(anthropic::AnthropicProvider::new(key)?));
48        }
49
50        // ---- Google ----
51        if let Some(pc) = config.providers.get("google") {
52            if let Some(key) = &pc.api_key {
53                registry.register(Arc::new(google::GoogleProvider::new(key.clone())?));
54            }
55        } else if let Ok(key) = std::env::var("GOOGLE_API_KEY") {
56            registry.register(Arc::new(google::GoogleProvider::new(key)?));
57        }
58
59        // ---- Novita (OpenAI-compatible) ----
60        if let Some(pc) = config.providers.get("novita")
61            && let Some(key) = &pc.api_key
62        {
63            let url = pc
64                .base_url
65                .clone()
66                .unwrap_or_else(|| "https://api.novita.ai/openai/v1".into());
67            registry.register(Arc::new(openai::OpenAIProvider::with_base_url(
68                key.clone(),
69                url,
70                "novita",
71            )?));
72        }
73
74        // ---- Bedrock (auto-detect from env / ~/.aws) ----
75        if let Some(creds) = bedrock::AwsCredentials::from_environment() {
76            let region = bedrock::AwsCredentials::detect_region()
77                .unwrap_or_else(|| bedrock::DEFAULT_REGION.to_string());
78            match bedrock::BedrockProvider::with_credentials(creds, region) {
79                Ok(p) => registry.register(Arc::new(p)),
80                Err(e) => tracing::warn!("Failed to init bedrock from AWS credentials: {}", e),
81            }
82        }
83
84        // ---- Env-var fallback ----
85        let disable = std::env::var("CODETETHER_DISABLE_ENV_FALLBACK")
86            .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
87            .unwrap_or(false);
88        if !disable {
89            super::init_env::register_env_fallbacks(&mut registry);
90        } else {
91            tracing::info!(
92                "Environment variable fallback disabled (CODETETHER_DISABLE_ENV_FALLBACK=1)"
93            );
94        }
95
96        Ok(registry)
97    }
98}