Skip to main content

lean_ctx/core/providers/
init.rs

1//! Provider initialization — auto-registers all built-in + config-based providers.
2//!
3//! Called once at startup. Respects the `[providers]` config section:
4//! - `providers.enabled = false` → skip all registration
5//! - `providers.github.enabled = false` → skip GitHub
6//! - `providers.gitlab.enabled = false` → skip GitLab
7//!
8//! After built-in providers, scans well-known directories for user-defined
9//! TOML/JSON provider configs and registers them automatically.
10
11use std::path::Path;
12use std::sync::Arc;
13
14use super::config_provider::ConfigProvider;
15use super::config_provider::discovery::discover_configs;
16use super::github::GitHubProvider;
17use super::gitlab::GitLabProvider;
18use super::jira::JiraProvider;
19use super::mcp_bridge::McpBridgeProvider;
20use super::postgres::PostgresProvider;
21use super::provider_trait::ContextProvider;
22use super::registry::global_registry;
23
24/// Register all built-in providers with the global registry.
25/// Respects `[providers]` config for enabling/disabling individual providers.
26/// Safe to call multiple times (idempotent — overwrites existing entries).
27pub fn init_builtin_providers() {
28    init_with_project_root(None);
29}
30
31/// Register all providers, including config-based ones scoped to `project_root`.
32pub fn init_with_project_root(project_root: Option<&Path>) {
33    let cfg = crate::core::config::Config::load();
34
35    if !cfg.providers.enabled {
36        tracing::debug!("[providers] subsystem disabled via config");
37        return;
38    }
39
40    let registry = global_registry();
41
42    // --- Built-in providers ---
43    if cfg.providers.github.enabled {
44        registry.register(Arc::new(GitHubProvider::new()));
45    }
46
47    if cfg.providers.gitlab.enabled {
48        registry.register(Arc::new(GitLabProvider::new()));
49    }
50
51    registry.register(Arc::new(JiraProvider::new()));
52    registry.register(Arc::new(PostgresProvider::new()));
53
54    // --- MCP Bridge providers (user-defined external MCP servers) ---
55    for (name, entry) in &cfg.providers.mcp_bridges {
56        if let Some(url) = &entry.url
57            && !url.is_empty()
58        {
59            registry.register(Arc::new(McpBridgeProvider::new(name, url)));
60            continue;
61        }
62        if let Some(command) = &entry.command
63            && !command.is_empty()
64        {
65            registry.register(Arc::new(McpBridgeProvider::new_stdio(
66                name,
67                command,
68                &entry.args,
69            )));
70            continue;
71        }
72        tracing::warn!("[providers] MCP bridge '{name}' has neither url nor command — skipping");
73    }
74
75    // --- Config-based providers (user-defined) ---
76    let discovered = discover_configs(project_root);
77    let mut config_count = 0;
78    for entry in discovered {
79        match ConfigProvider::from_config(entry.config) {
80            Ok(provider) => {
81                tracing::info!(
82                    "[providers] registered config provider '{}' from {}",
83                    provider.id(),
84                    entry.source_path.display()
85                );
86                registry.register(Arc::new(provider));
87                config_count += 1;
88            }
89            Err(e) => {
90                tracing::warn!("[providers] skipping {}: {e}", entry.source_path.display());
91            }
92        }
93    }
94
95    // --- WASM providers (opt-in, EPIC 12.10) ---
96    // Discovered from `LEAN_CTX_WASM_DIR`; each `<name>.wasm` may ship a
97    // `<name>.provider.json` sidecar declaring id/display/actions.
98    #[cfg(feature = "wasm")]
99    if let Ok(dir) = std::env::var("LEAN_CTX_WASM_DIR") {
100        let ids = crate::core::wasm_ext::register_providers_from_dir(registry, &dir);
101        if !ids.is_empty() {
102            tracing::info!(
103                "[providers] registered {} WASM provider(s): {ids:?}",
104                ids.len()
105            );
106        }
107    }
108
109    tracing::debug!(
110        "[providers] initialized {} provider(s) ({} config-based), {} available",
111        registry.provider_count(),
112        config_count,
113        registry.available_provider_ids().len(),
114    );
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn init_registers_github_when_enabled() {
123        init_builtin_providers();
124        let reg = global_registry();
125        assert!(reg.get("github").is_some());
126    }
127}