Skip to main content

codex_skills_extension/
sources.rs

1use std::fmt;
2use std::sync::Arc;
3
4use crate::catalog::SkillCatalog;
5use crate::catalog::SkillProviderError;
6use crate::catalog::SkillProviderResult;
7use crate::catalog::SkillReadResult;
8use crate::catalog::SkillSearchResult;
9use crate::catalog::SkillSourceKind;
10use crate::provider::SkillListQuery;
11use crate::provider::SkillProvider;
12use crate::provider::SkillReadRequest;
13use crate::provider::SkillSearchRequest;
14
15#[derive(Clone)]
16pub struct SkillProviderSource {
17    kind: SkillSourceKind,
18    label: String,
19    provider: Arc<dyn SkillProvider>,
20}
21
22impl SkillProviderSource {
23    pub fn new(
24        kind: SkillSourceKind,
25        label: impl Into<String>,
26        provider: Arc<dyn SkillProvider>,
27    ) -> Self {
28        Self {
29            kind,
30            label: label.into(),
31            provider,
32        }
33    }
34
35    pub fn host(label: impl Into<String>, provider: Arc<dyn SkillProvider>) -> Self {
36        Self::new(SkillSourceKind::Host, label, provider)
37    }
38
39    pub fn executor(label: impl Into<String>, provider: Arc<dyn SkillProvider>) -> Self {
40        Self::new(SkillSourceKind::Executor, label, provider)
41    }
42
43    pub fn orchestrator(label: impl Into<String>, provider: Arc<dyn SkillProvider>) -> Self {
44        Self::new(SkillSourceKind::Orchestrator, label, provider)
45    }
46
47    fn should_list(&self, query: &SkillListQuery) -> bool {
48        match &self.kind {
49            SkillSourceKind::Host => query.include_host_skills,
50            SkillSourceKind::Executor => !query.executor_roots.is_empty(),
51            SkillSourceKind::Orchestrator => query.include_orchestrator_skills,
52            SkillSourceKind::Custom(_) => true,
53        }
54    }
55
56    fn owns_kind(&self, kind: &SkillSourceKind) -> bool {
57        &self.kind == kind
58    }
59}
60
61impl fmt::Debug for SkillProviderSource {
62    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
63        formatter
64            .debug_struct("SkillProviderSource")
65            .field("kind", &self.kind)
66            .field("label", &self.label)
67            .finish()
68    }
69}
70
71#[derive(Clone, Default, Debug)]
72pub struct SkillProviders {
73    sources: Vec<SkillProviderSource>,
74}
75
76impl SkillProviders {
77    pub fn new() -> Self {
78        Self::default()
79    }
80
81    pub fn with_provider(mut self, source: SkillProviderSource) -> Self {
82        self.sources.push(source);
83        self
84    }
85
86    pub fn with_host_provider(mut self, provider: Arc<dyn SkillProvider>) -> Self {
87        self.sources
88            .push(SkillProviderSource::host("host", provider));
89        self
90    }
91
92    pub fn with_executor_provider(mut self, provider: Arc<dyn SkillProvider>) -> Self {
93        self.sources
94            .push(SkillProviderSource::executor("executor", provider));
95        self
96    }
97
98    pub fn with_orchestrator_provider(mut self, provider: Arc<dyn SkillProvider>) -> Self {
99        self.sources
100            .push(SkillProviderSource::orchestrator("orchestrator", provider));
101        self
102    }
103
104    pub(crate) fn has_orchestrator_provider(&self) -> bool {
105        self.sources
106            .iter()
107            .any(|source| source.kind == SkillSourceKind::Orchestrator)
108    }
109
110    pub(crate) fn has_host_provider(&self) -> bool {
111        self.sources
112            .iter()
113            .any(|source| source.kind == SkillSourceKind::Host)
114    }
115
116    pub(crate) async fn list_for_turn(&self, query: SkillListQuery) -> SkillCatalog {
117        self.list_matching(&query, |source| source.should_list(&query))
118            .await
119    }
120
121    pub(crate) async fn list_orchestrator_for_turn(
122        &self,
123        query: SkillListQuery,
124    ) -> SkillProviderResult<SkillCatalog> {
125        let mut catalog = SkillCatalog::default();
126
127        for source in self
128            .sources
129            .iter()
130            .filter(|source| source.kind == SkillSourceKind::Orchestrator)
131        {
132            let source_catalog = source.provider.list(query.clone()).await.map_err(|err| {
133                SkillProviderError::new(format!(
134                    "{} skills unavailable: {}",
135                    source.label, err.message
136                ))
137            })?;
138            catalog.extend(source_catalog);
139        }
140
141        Ok(catalog)
142    }
143
144    pub(crate) async fn list_executor_for_turn(&self, query: SkillListQuery) -> SkillCatalog {
145        self.list_matching(&query, |source| source.kind == SkillSourceKind::Executor)
146            .await
147    }
148
149    pub(crate) async fn list_host_for_turn(&self, query: SkillListQuery) -> SkillCatalog {
150        self.list_matching(&query, |source| source.kind == SkillSourceKind::Host)
151            .await
152    }
153
154    async fn list_matching(
155        &self,
156        query: &SkillListQuery,
157        should_list: impl Fn(&SkillProviderSource) -> bool,
158    ) -> SkillCatalog {
159        let mut catalog = SkillCatalog::default();
160
161        for source in self.sources.iter().filter(|source| should_list(source)) {
162            extend_catalog(
163                &mut catalog,
164                source.provider.list(query.clone()).await,
165                source.label.as_str(),
166            );
167        }
168
169        catalog
170    }
171
172    pub(crate) async fn read(
173        &self,
174        request: SkillReadRequest,
175    ) -> Result<SkillReadResult, SkillProviderError> {
176        let mut last_error = None;
177        for source in self
178            .sources
179            .iter()
180            .filter(|source| source.owns_kind(&request.authority.kind))
181        {
182            match source.provider.read(request.clone()).await {
183                Ok(result) => return Ok(result),
184                Err(err) => last_error = Some(err),
185            }
186        }
187
188        match last_error {
189            Some(err) => Err(err),
190            None => Err(SkillProviderError::new(format!(
191                "{} skill provider is not configured",
192                request.authority.kind
193            ))),
194        }
195    }
196
197    pub async fn search(
198        &self,
199        request: SkillSearchRequest,
200    ) -> Result<SkillSearchResult, SkillProviderError> {
201        let mut last_error = None;
202        for source in self
203            .sources
204            .iter()
205            .filter(|source| source.owns_kind(&request.authority.kind))
206        {
207            match source.provider.search(request.clone()).await {
208                Ok(result) => return Ok(result),
209                Err(err) => last_error = Some(err),
210            }
211        }
212
213        match last_error {
214            Some(err) => Err(err),
215            None => Err(SkillProviderError::new(format!(
216                "{} skill provider is not configured",
217                request.authority.kind
218            ))),
219        }
220    }
221}
222
223fn extend_catalog(
224    catalog: &mut SkillCatalog,
225    result: Result<SkillCatalog, SkillProviderError>,
226    label: &str,
227) {
228    match result {
229        Ok(source_catalog) => catalog.extend(source_catalog),
230        Err(err) => catalog
231            .warnings
232            .push(format!("{label} skills unavailable: {}", err.message)),
233    }
234}