lean_ctx/core/knowledge_router/
provider_bridge.rs1use crate::core::providers::registry::ProviderRegistry;
3use crate::core::providers::{ProviderParams, ProviderResult};
4
5use super::reference_resolver::{ReferenceType, ResolvedReference};
6
7#[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, ®istered, "jira", "issue_tracker")
50 }
51 ReferenceType::GitHubPR | ReferenceType::GitHubIssue => {
52 provider(available_providers, ®istered, "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 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}