Skip to main content

lean_ctx/core/knowledge_router/
provider_bridge.rs

1//! Translation between parsed references and registered context providers.
2use crate::core::providers::registry::ProviderRegistry;
3use crate::core::providers::{ProviderParams, ProviderResult};
4
5use super::reference_resolver::{ReferenceType, ResolvedReference};
6
7/// Registry-backed resolver used by the knowledge router.
8#[derive(Clone)]
9pub struct ProviderBridge<'a> {
10    registry: &'a ProviderRegistry,
11}
12
13impl std::fmt::Debug for ProviderBridge<'_> {
14    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15        formatter
16            .debug_struct("ProviderBridge")
17            .finish_non_exhaustive()
18    }
19}
20
21#[derive(Debug, Clone, Default, PartialEq, Eq)]
22pub struct ProviderResolution {
23    pub reference: String,
24    pub provider_id: String,
25    pub resolved: bool,
26    pub estimated_tokens: u64,
27}
28
29impl<'a> ProviderBridge<'a> {
30    pub fn new(registry: &'a ProviderRegistry) -> Self {
31        Self { registry }
32    }
33
34    pub fn available_provider_ids(&self) -> Vec<String> {
35        self.registry.available_provider_ids()
36    }
37
38    pub fn resolve_from_providers(
39        &self,
40        references: &[ResolvedReference],
41        available_providers: &[&str],
42    ) -> Vec<ProviderResolution> {
43        let registered = self.available_provider_ids();
44        references
45            .iter()
46            .filter_map(|reference| {
47                let provider_id = match reference.ref_type {
48                    ReferenceType::JiraIssue => {
49                        provider(available_providers, &registered, "jira", "issue_tracker")
50                    }
51                    ReferenceType::GitHubPR | ReferenceType::GitHubIssue => {
52                        provider(available_providers, &registered, "github", "vcs")
53                    }
54                    ReferenceType::FilePath => Some("local_files"),
55                    ReferenceType::Function | ReferenceType::Url => None,
56                };
57                let provider_id = provider_id?;
58                let resolved = {
59                    provider_id == "local_files"
60                        || available_providers.contains(&provider_id)
61                        || registered
62                            .iter()
63                            .any(|registered_id| registered_id == provider_id)
64                };
65                Some(ProviderResolution {
66                    reference: reference.identifier.clone(),
67                    provider_id: provider_id.to_string(),
68                    resolved,
69                    estimated_tokens: tokens(reference),
70                })
71            })
72            .collect()
73    }
74
75    /// Fetch the provider record represented by a resolved reference.
76    pub fn fetch_reference(
77        &self,
78        reference: &ResolvedReference,
79        resolution: &ProviderResolution,
80    ) -> Result<ProviderResult, String> {
81        if !resolution.resolved {
82            return Err(format!(
83                "Provider '{}' is not available for '{}'",
84                resolution.provider_id, resolution.reference
85            ));
86        }
87
88        let action = action_for(reference.ref_type, &resolution.provider_id)?;
89        let provider = self
90            .registry
91            .get(&resolution.provider_id)
92            .ok_or_else(|| format!("Provider '{}' not registered", resolution.provider_id))?;
93        if !provider.is_available() {
94            return Err(format!(
95                "Provider '{}' is not available",
96                resolution.provider_id
97            ));
98        }
99        if !provider.supported_actions().contains(&action) {
100            return Err(format!(
101                "Provider '{}' does not support action '{action}'",
102                resolution.provider_id
103            ));
104        }
105
106        provider.execute(
107            action,
108            &ProviderParams {
109                id: Some(reference.identifier.trim_start_matches('#').into()),
110                query: Some(reference.identifier.clone()),
111                limit: Some(1),
112                ..ProviderParams::default()
113            },
114        )
115    }
116}
117
118fn provider<'a>(
119    available: &[&'a str],
120    registered: &[String],
121    id: &'a str,
122    kind: &'a str,
123) -> Option<&'a str> {
124    if available.contains(&id) || registered.iter().any(|value| value == id) {
125        Some(id)
126    } else if available.contains(&kind) {
127        Some(kind)
128    } else {
129        Some(id)
130    }
131}
132
133fn action_for(reference_type: ReferenceType, provider_id: &str) -> Result<&'static str, String> {
134    match (reference_type, provider_id) {
135        (ReferenceType::GitHubPR, "github") => Ok("pull_requests"),
136        (ReferenceType::JiraIssue, "jira") | (ReferenceType::GitHubIssue, "github") => Ok("issues"),
137        _ => Err(format!(
138            "Provider '{provider_id}' cannot fetch {reference_type:?} references"
139        )),
140    }
141}
142
143fn tokens(reference: &ResolvedReference) -> u64 {
144    match reference.ref_type {
145        ReferenceType::GitHubPR => 1_000,
146        ReferenceType::JiraIssue | ReferenceType::GitHubIssue => 500,
147        ReferenceType::FilePath => 128 + (reference.identifier.len() as u64).saturating_mul(8),
148        ReferenceType::Function | ReferenceType::Url => 0,
149    }
150}