Skip to main content

codetether_agent/provider/
init_vault.rs

1//! Build a [`ProviderRegistry`](super::ProviderRegistry) from HashiCorp Vault.
2//!
3//! Iterates all providers configured in Vault, delegates each to
4//! [`super::init_dispatch::dispatch`], then falls back to env-var / AWS
5//! auto-detection when `CODETETHER_DISABLE_ENV_FALLBACK` is not set.
6
7use super::bedrock;
8use super::init_dispatch;
9use super::init_env;
10use super::registry::ProviderRegistry;
11use anyhow::Result;
12use std::sync::Arc;
13
14impl ProviderRegistry {
15    /// Initialize providers from HashiCorp Vault with env-var fallback.
16    ///
17    /// See [module-level docs](super) for the security model and fallback order.
18    ///
19    /// # Examples
20    ///
21    /// ```rust,no_run
22    /// use codetether_agent::provider::ProviderRegistry;
23    /// # async fn demo() {
24    /// let registry = ProviderRegistry::from_vault().await.unwrap();
25    /// # }
26    /// ```
27    pub async fn from_vault() -> Result<Self> {
28        let mut registry = Self::new();
29        let disable_env = std::env::var("CODETETHER_DISABLE_ENV_FALLBACK")
30            .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
31            .unwrap_or(false);
32
33        if let Some(mgr) = crate::secrets::secrets_manager() {
34            let providers = mgr.list_configured_providers().await?;
35            tracing::info!("Found {} providers configured in Vault", providers.len());
36
37            // Fetch every provider's secrets concurrently; each fetch is an
38            // independent Vault HTTP round-trip so there's no reason to
39            // serialize them. With ~10 providers this turns ~10 * RTT
40            // latency into ~1 * RTT.
41            let fetches = providers.into_iter().map(|pid| async move {
42                let secrets = mgr.get_provider_secrets(&pid).await;
43                (pid, secrets)
44            });
45            let results = futures::future::join_all(fetches).await;
46
47            for (pid, secrets) in results {
48                let secrets = match secrets {
49                    Ok(Some(s)) => s,
50                    Ok(None) => continue,
51                    Err(err) => {
52                        tracing::warn!(provider = %pid, %err, "vault fetch failed; skipping");
53                        continue;
54                    }
55                };
56                if let Some(provider) = init_dispatch::dispatch(&pid, &secrets) {
57                    registry.register(provider);
58                }
59            }
60        } else {
61            tracing::warn!("Vault not configured, no providers loaded from Vault");
62        }
63
64        // Bedrock auto-detect from local AWS creds if Vault didn't register it
65        if !registry.providers.contains_key("bedrock") && !disable_env {
66            if let Some(creds) = bedrock::AwsCredentials::from_environment() {
67                let region =
68                    bedrock::AwsCredentials::detect_region().unwrap_or_else(|| "us-east-1".into());
69                match bedrock::BedrockProvider::with_credentials(creds, region) {
70                    Ok(p) => {
71                        tracing::info!("Registered Bedrock from local AWS credentials");
72                        registry.register(Arc::new(p));
73                    }
74                    Err(e) => tracing::warn!("Failed to init bedrock: {e}"),
75                }
76            }
77        }
78
79        if !disable_env {
80            init_env::register_env_fallbacks(&mut registry);
81        } else {
82            tracing::info!(
83                "Environment variable fallback disabled (CODETETHER_DISABLE_ENV_FALLBACK=1)"
84            );
85        }
86
87        tracing::info!(
88            "Registered {} providers{}",
89            registry.providers.len(),
90            if disable_env {
91                " (Vault only)"
92            } else {
93                " (Vault + env fallback)"
94            }
95        );
96        Ok(registry)
97    }
98}