Skip to main content

lean_ctx/core/knowledge_router/
manifest_builder.rs

1//! Provider-aware source-manifest construction.
2use std::fs;
3
4use super::source_manifest::{CostClass, SourceCapabilities, SourceManifestEntry};
5
6#[derive(Debug, Clone, Default, PartialEq, Eq)]
7pub struct ProviderConfig {
8    pub jira_configured: bool,
9    pub github_configured: bool,
10    pub gitlab_configured: bool,
11    pub postgres_configured: bool,
12    pub custom_providers: Vec<String>,
13}
14
15pub fn build_manifests_from_config(config: &ProviderConfig) -> Vec<SourceManifestEntry> {
16    let mut provider_ids = config.custom_providers.clone();
17    provider_ids.extend(
18        [
19            (config.jira_configured, "jira"),
20            (config.github_configured, "github"),
21            (config.gitlab_configured, "gitlab"),
22            (config.postgres_configured, "postgres"),
23        ]
24        .into_iter()
25        .filter_map(|(enabled, id)| enabled.then_some(id.to_string())),
26    );
27    build_manifests_for_provider_ids(&provider_ids)
28}
29
30/// Build manifests for every registered provider, including dynamic providers.
31pub fn build_manifests_for_provider_ids(provider_ids: &[String]) -> Vec<SourceManifestEntry> {
32    let mut manifests = vec![entry(
33        "local_files",
34        "LocalFiles",
35        &["file"],
36        CostClass::Negligible,
37    )];
38    let mut provider_ids = provider_ids.to_vec();
39    provider_ids.sort();
40    provider_ids.dedup();
41    manifests.extend(provider_ids.iter().map(|id| provider_entry(id)));
42    manifests
43}
44
45pub fn detect_config() -> ProviderConfig {
46    let providers = crate::core::config::Config::load().providers;
47    let file = crate::core::config::Config::path()
48        .and_then(|path| fs::read_to_string(path).ok())
49        .unwrap_or_default();
50    let configured = |env: &str, value: bool| std::env::var_os(env).is_some() || value;
51    ProviderConfig {
52        jira_configured: configured("JIRA_URL", file.contains("[providers.jira]")),
53        github_configured: configured(
54            "GITHUB_TOKEN",
55            providers.github.token.is_some() || providers.github.api_url.is_some(),
56        ),
57        gitlab_configured: configured(
58            "GITLAB_TOKEN",
59            providers.gitlab.token.is_some() || providers.gitlab.api_url.is_some(),
60        ),
61        postgres_configured: std::env::var_os("DATABASE_URL").is_some()
62            || std::env::var_os("PGDATABASE").is_some(),
63        custom_providers: providers.mcp_bridges.keys().cloned().collect(),
64    }
65}
66
67fn provider_entry(id: &str) -> SourceManifestEntry {
68    match id {
69        "jira" => entry(id, "Jira", &["issue"], CostClass::Low),
70        "github" => entry(id, "GitHub", &["issue", "pull_request"], CostClass::Low),
71        "gitlab" => entry(id, "GitLab", &["issue", "pull_request"], CostClass::Low),
72        "postgres" => entry(id, "PostgreSQL", &["schema", "table"], CostClass::Medium),
73        "emspace" => entry(id, "Emspace", &["page", "task"], CostClass::Low),
74        _ => entry(id, id, &["external"], CostClass::Medium),
75    }
76}
77
78fn entry(id: &str, name: &str, kinds: &[&str], cost_class: CostClass) -> SourceManifestEntry {
79    SourceManifestEntry {
80        source_id: id.into(),
81        display_name: name.into(),
82        kinds: kinds.iter().map(|kind| (*kind).into()).collect(),
83        capabilities: SourceCapabilities {
84            search: true,
85            exact_get: true,
86            delta_sync: true,
87            live_query: id != "local_files",
88            graph_edges: true,
89        },
90        freshness_typical_ms: 60_000,
91        cost_class,
92    }
93}