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::emspace::EmspaceProvider;
17use super::github::GitHubProvider;
18use super::gitlab::GitLabProvider;
19use super::jira::JiraProvider;
20use super::mcp_bridge::McpBridgeProvider;
21use super::postgres::PostgresProvider;
22use super::provider_trait::ContextProvider;
23use super::registry::{ProviderRegistry, global_registry};
24
25/// Register all built-in providers with the global registry.
26/// Respects `[providers]` config for enabling/disabling individual providers.
27/// Safe to call multiple times (idempotent — overwrites existing entries).
28pub fn init_builtin_providers() {
29    init_with_project_root(None);
30}
31
32/// Register all providers, including config-based ones scoped to `project_root`.
33pub fn init_with_project_root(project_root: Option<&Path>) {
34    populate(global_registry(), project_root);
35}
36
37/// Register every provider `project_root` has into `registry`. A caller
38/// that only inspects (a listing) passes its own fresh registry, so the
39/// shared one is never changed and one project's providers never reach
40/// another's.
41pub fn populate(registry: &ProviderRegistry, project_root: Option<&Path>) {
42    let cfg = crate::core::config::Config::load();
43
44    if !cfg.providers.enabled {
45        tracing::debug!("[providers] subsystem disabled via config");
46        return;
47    }
48
49    // --- Built-in providers ---
50    if cfg.providers.github.enabled {
51        registry.register(Arc::new(GitHubProvider::new()));
52    }
53
54    if cfg.providers.gitlab.enabled {
55        registry.register(Arc::new(GitLabProvider::new()));
56    }
57
58    registry.register(Arc::new(JiraProvider::new()));
59    registry.register(Arc::new(PostgresProvider::new()));
60
61    // The space a project is linked to (`.emspace`); needs the project root.
62    if let Some(root) = project_root {
63        registry.register(Arc::new(EmspaceProvider::new(root)));
64    }
65
66    // --- MCP Bridge providers (user-defined external MCP servers) ---
67    for (name, entry) in &cfg.providers.mcp_bridges {
68        if let Some(url) = &entry.url
69            && !url.is_empty()
70        {
71            registry.register(Arc::new(McpBridgeProvider::new(name, url)));
72            continue;
73        }
74        if let Some(command) = &entry.command
75            && !command.is_empty()
76        {
77            registry.register(Arc::new(McpBridgeProvider::new_stdio(
78                name,
79                command,
80                &entry.args,
81            )));
82            continue;
83        }
84        tracing::warn!("[providers] MCP bridge '{name}' has neither url nor command — skipping");
85    }
86
87    // --- Config-based providers (user-defined) ---
88    let discovered = discover_configs(project_root);
89    let mut config_count = 0;
90    for entry in discovered {
91        match ConfigProvider::from_config(entry.config) {
92            Ok(provider) => {
93                tracing::info!(
94                    "[providers] registered config provider '{}' from {}",
95                    provider.id(),
96                    entry.source_path.display()
97                );
98                registry.register(Arc::new(provider));
99                config_count += 1;
100            }
101            Err(e) => {
102                tracing::warn!("[providers] skipping {}: {e}", entry.source_path.display());
103            }
104        }
105    }
106
107    // --- WASM providers (opt-in, EPIC 12.10) ---
108    // Discovered from `LEAN_CTX_WASM_DIR`; each `<name>.wasm` may ship a
109    // `<name>.provider.json` sidecar declaring id/display/actions.
110    #[cfg(feature = "wasm")]
111    if let Ok(dir) = std::env::var("LEAN_CTX_WASM_DIR") {
112        let ids = crate::core::wasm_ext::register_providers_from_dir(registry, &dir);
113        if !ids.is_empty() {
114            tracing::info!(
115                "[providers] registered {} WASM provider(s): {ids:?}",
116                ids.len()
117            );
118        }
119    }
120
121    tracing::debug!(
122        "[providers] initialized {} provider(s) ({} config-based), {} available",
123        registry.provider_count(),
124        config_count,
125        registry.available_provider_ids().len(),
126    );
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn init_registers_github_when_enabled() {
135        init_builtin_providers();
136        let reg = global_registry();
137        assert!(reg.get("github").is_some());
138    }
139}