Skip to main content

roder_api/
extension.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::sync::Arc;
3
4use semver::{Version, VersionReq};
5use serde::{Deserialize, Serialize};
6
7use crate::capabilities::{CapabilityDenial, CapabilityGrant, CapabilityRequest, CapabilityStatus};
8
9pub type ExtensionId = String;
10pub type ApiVersion = String;
11pub type InferenceEngineId = String;
12pub type InferenceRouterId = String;
13pub type ContextProviderId = String;
14pub type ContextPlannerId = String;
15pub type ThreadStoreId = String;
16pub type CheckpointStoreId = String;
17pub type MemoryStoreId = String;
18pub type KnowledgeStoreId = String;
19pub type EmbeddingProviderId = String;
20pub type MediaGeneratorProviderId = String;
21pub type ToolProviderId = String;
22pub type SubagentDispatcherId = String;
23pub type PolicyContributorId = String;
24pub type EventSinkId = String;
25pub type TaskExecutorId = String;
26pub type NotificationSinkId = String;
27pub type InteractiveRegionHandlerId = String;
28pub type SpeechTranscriberId = String;
29pub type SpeechSynthesizerId = String;
30pub type VersionControlProviderId = crate::version_control::VcsProviderId;
31pub type ReviewPublisherId = crate::review::ReviewPublisherId;
32
33pub const SUPPORTED_EXTENSION_API_VERSION: &str = "0.1.0";
34
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
36pub enum ProvidedService {
37    InferenceEngine(InferenceEngineId),
38    InferenceRouter(InferenceRouterId),
39    ContextProvider(ContextProviderId),
40    ContextPlanner(ContextPlannerId),
41    ThreadStore(ThreadStoreId),
42    CheckpointStore(CheckpointStoreId),
43    MemoryStore(MemoryStoreId),
44    KnowledgeStore(KnowledgeStoreId),
45    EmbeddingProvider(EmbeddingProviderId),
46    MediaGenerator(MediaGeneratorProviderId),
47    ToolProvider(ToolProviderId),
48    SubagentDispatcher(SubagentDispatcherId),
49    PolicyContributor(PolicyContributorId),
50    EventSink(EventSinkId),
51    ForkProvider(crate::forks::ForkProviderId),
52    TaskExecutor(TaskExecutorId),
53    NotificationSink(NotificationSinkId),
54    InteractiveRegionHandler(InteractiveRegionHandlerId),
55    SpeechTranscriber(SpeechTranscriberId),
56    SpeechSynthesizer(SpeechSynthesizerId),
57    VersionControlProvider(VersionControlProviderId),
58    RemoteRunnerProvider(crate::remote_runner::RemoteRunnerProviderId),
59    StatusSegment(crate::tui_status::StatusSegmentId),
60    PaletteSource(crate::tui_status::PaletteSourceId),
61    CodeIndexProvider(crate::code_index::CodeIndexProviderId),
62    ReviewPublisher(ReviewPublisherId),
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct ExtensionManifest {
67    pub id: ExtensionId,
68    pub name: String,
69    pub version: Version,
70    pub api_version: ApiVersion,
71    pub description: Option<String>,
72    pub provides: Vec<ProvidedService>,
73    pub required_capabilities: Vec<CapabilityRequest>,
74}
75
76pub trait RoderExtension: Send + Sync + 'static {
77    fn manifest(&self) -> ExtensionManifest;
78
79    fn install(&self, registry: &mut ExtensionRegistryBuilder) -> anyhow::Result<()>;
80}
81
82/// Lets shared extension handles (e.g. distribution-supplied extension lists)
83/// be installed through the same `ExtensionRegistryBuilder::install` path as
84/// concrete extension values.
85impl<E: RoderExtension + ?Sized> RoderExtension for Arc<E> {
86    fn manifest(&self) -> ExtensionManifest {
87        (**self).manifest()
88    }
89
90    fn install(&self, registry: &mut ExtensionRegistryBuilder) -> anyhow::Result<()> {
91        (**self).install(registry)
92    }
93}
94
95#[derive(Clone)]
96pub struct ExtensionRegistry {
97    pub manifests: Vec<ExtensionManifest>,
98    pub capability_statuses: BTreeMap<ExtensionId, Vec<CapabilityStatus>>,
99    pub inference_engines: Vec<Arc<dyn crate::inference::InferenceEngine>>,
100    pub inference_routers: Vec<Arc<dyn crate::inference_routing::InferenceRouter>>,
101    pub context_providers: Vec<Arc<dyn crate::context::ContextProvider>>,
102    pub context_planners: Vec<Arc<dyn crate::context::ContextPlanner>>,
103    pub thread_stores: Vec<Arc<dyn crate::thread::ThreadStoreFactory>>,
104    pub checkpoint_stores: Vec<Arc<dyn crate::thread::CheckpointStoreFactory>>,
105    pub memory_stores: Vec<Arc<dyn crate::memory::MemoryStoreFactory>>,
106    pub knowledge_stores: Vec<Arc<dyn crate::knowledge::KnowledgeStoreFactory>>,
107    pub embedding_providers: Vec<Arc<dyn crate::embeddings::EmbeddingProvider>>,
108    pub media_generator_providers: Vec<Arc<dyn crate::media::MediaGeneratorProvider>>,
109    pub tools: Vec<Arc<dyn crate::tools::ToolContributor>>,
110    pub subagent_dispatchers: Vec<Arc<dyn crate::subagents::SubagentDispatcher>>,
111    pub policy_contributors: Vec<Arc<dyn crate::context::PolicyContributor>>,
112    pub event_sinks: Vec<Arc<dyn crate::extension::EventSink>>,
113    pub fork_providers: Vec<Arc<dyn crate::forks::ForkProvider>>,
114    pub task_executors: Vec<Arc<dyn crate::tasks::TaskExecutor>>,
115    pub notification_sinks: Vec<Arc<dyn crate::notifications::NotificationSink>>,
116    pub interactive_region_handlers: Vec<Arc<dyn crate::interactive::InteractiveRegionHandler>>,
117    pub speech_transcribers: Vec<Arc<dyn crate::speech::SpeechTranscriber>>,
118    pub speech_synthesizers: Vec<Arc<dyn crate::speech::SpeechSynthesizer>>,
119    pub version_control_providers: Vec<Arc<dyn crate::version_control::VcsProvider>>,
120    pub remote_runner_providers: Vec<Arc<dyn crate::remote_runner::RemoteRunnerProvider>>,
121    pub status_segments: Vec<crate::tui_status::StatusSegment>,
122    pub palette_sources: Vec<crate::tui_status::PaletteSourceDescriptor>,
123    pub code_index_providers: Vec<Arc<dyn crate::code_index::CodeIndexProvider>>,
124    pub review_publishers: Vec<Arc<dyn crate::review::ReviewPublisher>>,
125}
126
127impl ExtensionRegistry {
128    pub fn media_generator(
129        &self,
130        id: &str,
131    ) -> Option<Arc<dyn crate::media::MediaGeneratorProvider>> {
132        self.media_generator_providers
133            .iter()
134            .find(|provider| provider.provider_id() == id)
135            .cloned()
136    }
137
138    pub fn inference_engine(&self, id: &str) -> Option<Arc<dyn crate::inference::InferenceEngine>> {
139        self.inference_engines
140            .iter()
141            .find(|engine| engine.id() == id)
142            .cloned()
143    }
144
145    pub fn default_inference_engine(&self) -> Option<Arc<dyn crate::inference::InferenceEngine>> {
146        self.inference_engines.first().cloned()
147    }
148
149    pub fn inference_router(
150        &self,
151        id: &str,
152    ) -> Option<Arc<dyn crate::inference_routing::InferenceRouter>> {
153        self.inference_routers
154            .iter()
155            .find(|router| router.id() == id)
156            .cloned()
157    }
158
159    pub fn speech_transcriber(
160        &self,
161        id: &str,
162    ) -> Option<Arc<dyn crate::speech::SpeechTranscriber>> {
163        self.speech_transcribers
164            .iter()
165            .find(|transcriber| transcriber.id() == id)
166            .cloned()
167    }
168
169    pub fn speech_synthesizer(
170        &self,
171        id: &str,
172    ) -> Option<Arc<dyn crate::speech::SpeechSynthesizer>> {
173        self.speech_synthesizers
174            .iter()
175            .find(|synthesizer| synthesizer.id() == id)
176            .cloned()
177    }
178
179    pub fn fork_provider(&self, id: &str) -> Option<Arc<dyn crate::forks::ForkProvider>> {
180        self.fork_providers
181            .iter()
182            .find(|provider| provider.descriptor().id == id)
183            .cloned()
184    }
185
186    pub fn review_publisher(&self, id: &str) -> Option<Arc<dyn crate::review::ReviewPublisher>> {
187        self.review_publishers
188            .iter()
189            .find(|publisher| publisher.descriptor().id == id)
190            .cloned()
191    }
192
193    pub fn review_publishers(&self) -> &[Arc<dyn crate::review::ReviewPublisher>] {
194        &self.review_publishers
195    }
196
197    pub fn provided_services(&self) -> Vec<ProvidedService> {
198        self.manifests
199            .iter()
200            .flat_map(|manifest| manifest.provides.iter().cloned())
201            .collect()
202    }
203
204    pub fn capability_statuses(&self, extension_id: &str) -> &[CapabilityStatus] {
205        self.capability_statuses
206            .get(extension_id)
207            .map(Vec::as_slice)
208            .unwrap_or(&[])
209    }
210
211    pub fn subagent_dispatcher(
212        &self,
213        id: &str,
214    ) -> Option<Arc<dyn crate::subagents::SubagentDispatcher>> {
215        self.subagent_dispatchers
216            .iter()
217            .find(|dispatcher| dispatcher.id() == id)
218            .cloned()
219    }
220
221    pub fn version_control_provider(
222        &self,
223        id: &str,
224    ) -> Option<Arc<dyn crate::version_control::VcsProvider>> {
225        self.version_control_providers
226            .iter()
227            .find(|provider| provider.id() == id)
228            .cloned()
229    }
230
231    pub fn version_control_resolver(&self) -> crate::version_control::RegistryVcsProviderResolver {
232        crate::version_control::RegistryVcsProviderResolver::new(
233            self.version_control_providers.clone(),
234        )
235    }
236}
237
238pub struct ExtensionRegistryBuilder {
239    manifests: Vec<ExtensionManifest>,
240    granted_capabilities: BTreeMap<ExtensionId, BTreeSet<String>>,
241    denied_capabilities: BTreeMap<ExtensionId, BTreeMap<String, String>>,
242    pub inference_engines: Vec<Arc<dyn crate::inference::InferenceEngine>>,
243    pub inference_routers: Vec<Arc<dyn crate::inference_routing::InferenceRouter>>,
244    pub context_providers: Vec<Arc<dyn crate::context::ContextProvider>>,
245    pub context_planners: Vec<Arc<dyn crate::context::ContextPlanner>>,
246    pub thread_stores: Vec<Arc<dyn crate::thread::ThreadStoreFactory>>,
247    pub checkpoint_stores: Vec<Arc<dyn crate::thread::CheckpointStoreFactory>>,
248    pub memory_stores: Vec<Arc<dyn crate::memory::MemoryStoreFactory>>,
249    pub knowledge_stores: Vec<Arc<dyn crate::knowledge::KnowledgeStoreFactory>>,
250    pub embedding_providers: Vec<Arc<dyn crate::embeddings::EmbeddingProvider>>,
251    pub media_generator_providers: Vec<Arc<dyn crate::media::MediaGeneratorProvider>>,
252    pub tools: Vec<Arc<dyn crate::tools::ToolContributor>>,
253    pub subagent_dispatchers: Vec<Arc<dyn crate::subagents::SubagentDispatcher>>,
254    pub policy_contributors: Vec<Arc<dyn crate::context::PolicyContributor>>,
255    pub event_sinks: Vec<Arc<dyn crate::extension::EventSink>>,
256    pub fork_providers: Vec<Arc<dyn crate::forks::ForkProvider>>,
257    pub task_executors: Vec<Arc<dyn crate::tasks::TaskExecutor>>,
258    pub notification_sinks: Vec<Arc<dyn crate::notifications::NotificationSink>>,
259    pub interactive_region_handlers: Vec<Arc<dyn crate::interactive::InteractiveRegionHandler>>,
260    pub speech_transcribers: Vec<Arc<dyn crate::speech::SpeechTranscriber>>,
261    pub speech_synthesizers: Vec<Arc<dyn crate::speech::SpeechSynthesizer>>,
262    pub version_control_providers: Vec<Arc<dyn crate::version_control::VcsProvider>>,
263    pub remote_runner_providers: Vec<Arc<dyn crate::remote_runner::RemoteRunnerProvider>>,
264    pub status_segments: Vec<crate::tui_status::StatusSegment>,
265    pub palette_sources: Vec<crate::tui_status::PaletteSourceDescriptor>,
266    pub code_index_providers: Vec<Arc<dyn crate::code_index::CodeIndexProvider>>,
267    pub review_publishers: Vec<Arc<dyn crate::review::ReviewPublisher>>,
268}
269
270impl Default for ExtensionRegistryBuilder {
271    fn default() -> Self {
272        Self::new()
273    }
274}
275
276impl ExtensionRegistryBuilder {
277    pub fn new() -> Self {
278        Self {
279            manifests: Vec::new(),
280            granted_capabilities: BTreeMap::new(),
281            denied_capabilities: BTreeMap::new(),
282            inference_engines: Vec::new(),
283            inference_routers: Vec::new(),
284            context_providers: Vec::new(),
285            context_planners: Vec::new(),
286            thread_stores: Vec::new(),
287            checkpoint_stores: Vec::new(),
288            memory_stores: Vec::new(),
289            knowledge_stores: Vec::new(),
290            embedding_providers: Vec::new(),
291            media_generator_providers: Vec::new(),
292            tools: Vec::new(),
293            subagent_dispatchers: Vec::new(),
294            policy_contributors: Vec::new(),
295            event_sinks: Vec::new(),
296            fork_providers: Vec::new(),
297            task_executors: Vec::new(),
298            notification_sinks: Vec::new(),
299            interactive_region_handlers: Vec::new(),
300            speech_transcribers: Vec::new(),
301            speech_synthesizers: Vec::new(),
302            version_control_providers: Vec::new(),
303            remote_runner_providers: Vec::new(),
304            status_segments: Vec::new(),
305            palette_sources: Vec::new(),
306            code_index_providers: Vec::new(),
307            review_publishers: Vec::new(),
308        }
309    }
310
311    /// Installs the extension and checks that every service it registers is
312    /// declared in its manifest `provides` list, so a manifest is a complete
313    /// inventory of what an installed extension contributes. Host code that
314    /// registers services directly on the builder (outside `install`) is not
315    /// subject to this check.
316    pub fn install<E: RoderExtension>(&mut self, ext: E) -> anyhow::Result<()> {
317        let manifest = ext.manifest();
318        if self
319            .manifests
320            .iter()
321            .any(|existing| existing.id == manifest.id)
322        {
323            anyhow::bail!("extension {} is already installed", manifest.id);
324        }
325        let before = service_counts(self)?;
326        ext.install(self)?;
327        let declared: BTreeSet<ProvidedService> = manifest.provides.iter().cloned().collect();
328        for (service, count) in service_counts(self)? {
329            let prior = before.get(&service).copied().unwrap_or(0);
330            if count > prior && !declared.contains(&service) {
331                anyhow::bail!(
332                    "extension {} installed undeclared service {}; declare it in the manifest provides list",
333                    manifest.id,
334                    service_label(&service)
335                );
336            }
337        }
338        self.manifests.push(manifest);
339        Ok(())
340    }
341
342    pub fn build(self) -> anyhow::Result<ExtensionRegistry> {
343        let validation = self.validate()?;
344        Ok(ExtensionRegistry {
345            manifests: self.manifests,
346            capability_statuses: validation.capability_statuses,
347            inference_engines: self.inference_engines,
348            inference_routers: self.inference_routers,
349            context_providers: self.context_providers,
350            context_planners: self.context_planners,
351            thread_stores: self.thread_stores,
352            checkpoint_stores: self.checkpoint_stores,
353            memory_stores: self.memory_stores,
354            knowledge_stores: self.knowledge_stores,
355            embedding_providers: self.embedding_providers,
356            media_generator_providers: self.media_generator_providers,
357            tools: self.tools,
358            subagent_dispatchers: self.subagent_dispatchers,
359            policy_contributors: self.policy_contributors,
360            event_sinks: self.event_sinks,
361            fork_providers: self.fork_providers,
362            task_executors: self.task_executors,
363            notification_sinks: self.notification_sinks,
364            interactive_region_handlers: self.interactive_region_handlers,
365            speech_transcribers: self.speech_transcribers,
366            speech_synthesizers: self.speech_synthesizers,
367            version_control_providers: self.version_control_providers,
368            remote_runner_providers: self.remote_runner_providers,
369            status_segments: self.status_segments,
370            palette_sources: self.palette_sources,
371            code_index_providers: self.code_index_providers,
372            review_publishers: self.review_publishers,
373        })
374    }
375
376    pub fn manifest(&mut self, manifest: ExtensionManifest) {
377        self.manifests.push(manifest);
378    }
379
380    /// Capability grants are advisory metadata surfaced through
381    /// `extensions/list` (statically linked extensions run in-process);
382    /// nothing enforces them at runtime.
383    pub fn grant_capability(&mut self, extension_id: impl Into<String>, grant: CapabilityGrant) {
384        self.granted_capabilities
385            .entry(extension_id.into())
386            .or_default()
387            .insert(grant.id);
388    }
389
390    /// Advisory, like [`Self::grant_capability`]: a denial fails `build()` for
391    /// extensions that require the capability but does not constrain runtime
392    /// behavior.
393    pub fn deny_capability(&mut self, extension_id: impl Into<String>, denial: CapabilityDenial) {
394        self.denied_capabilities
395            .entry(extension_id.into())
396            .or_default()
397            .insert(denial.id, denial.reason);
398    }
399
400    pub fn inference_engine(&mut self, engine: Arc<dyn crate::inference::InferenceEngine>) {
401        self.inference_engines.push(engine);
402    }
403
404    pub fn inference_router(&mut self, router: Arc<dyn crate::inference_routing::InferenceRouter>) {
405        self.inference_routers.push(router);
406    }
407
408    pub fn context_provider(&mut self, provider: Arc<dyn crate::context::ContextProvider>) {
409        self.context_providers.push(provider);
410    }
411
412    pub fn context_planner(&mut self, planner: Arc<dyn crate::context::ContextPlanner>) {
413        self.context_planners.push(planner);
414    }
415
416    pub fn thread_store_factory(&mut self, store: Arc<dyn crate::thread::ThreadStoreFactory>) {
417        self.thread_stores.push(store);
418    }
419
420    pub fn checkpoint_store_factory(
421        &mut self,
422        store: Arc<dyn crate::thread::CheckpointStoreFactory>,
423    ) {
424        self.checkpoint_stores.push(store);
425    }
426
427    pub fn memory_store_factory(&mut self, store: Arc<dyn crate::memory::MemoryStoreFactory>) {
428        self.memory_stores.push(store);
429    }
430
431    pub fn knowledge_store_factory(
432        &mut self,
433        store: Arc<dyn crate::knowledge::KnowledgeStoreFactory>,
434    ) {
435        self.knowledge_stores.push(store);
436    }
437
438    pub fn embedding_provider(&mut self, provider: Arc<dyn crate::embeddings::EmbeddingProvider>) {
439        self.embedding_providers.push(provider);
440    }
441
442    pub fn media_generator_provider(
443        &mut self,
444        provider: Arc<dyn crate::media::MediaGeneratorProvider>,
445    ) {
446        self.media_generator_providers.push(provider);
447    }
448
449    pub fn tool_contributor(&mut self, contributor: Arc<dyn crate::tools::ToolContributor>) {
450        self.tools.push(contributor);
451    }
452
453    pub fn subagent_dispatcher(
454        &mut self,
455        dispatcher: Arc<dyn crate::subagents::SubagentDispatcher>,
456    ) {
457        self.subagent_dispatchers.push(dispatcher);
458    }
459
460    pub fn policy_contributor(&mut self, contributor: Arc<dyn crate::context::PolicyContributor>) {
461        self.policy_contributors.push(contributor);
462    }
463
464    pub fn event_sink(&mut self, sink: Arc<dyn crate::extension::EventSink>) {
465        self.event_sinks.push(sink);
466    }
467
468    pub fn fork_provider(&mut self, provider: Arc<dyn crate::forks::ForkProvider>) {
469        self.fork_providers.push(provider);
470    }
471
472    pub fn task_executor(&mut self, executor: Arc<dyn crate::tasks::TaskExecutor>) {
473        self.task_executors.push(executor);
474    }
475
476    pub fn notification_sink(&mut self, sink: Arc<dyn crate::notifications::NotificationSink>) {
477        self.notification_sinks.push(sink);
478    }
479
480    pub fn interactive_region_handler(
481        &mut self,
482        handler: Arc<dyn crate::interactive::InteractiveRegionHandler>,
483    ) {
484        self.interactive_region_handlers.push(handler);
485    }
486
487    pub fn speech_transcriber(&mut self, transcriber: Arc<dyn crate::speech::SpeechTranscriber>) {
488        self.speech_transcribers.push(transcriber);
489    }
490
491    pub fn speech_synthesizer(&mut self, synthesizer: Arc<dyn crate::speech::SpeechSynthesizer>) {
492        self.speech_synthesizers.push(synthesizer);
493    }
494
495    pub fn version_control_provider(
496        &mut self,
497        provider: Arc<dyn crate::version_control::VcsProvider>,
498    ) {
499        self.version_control_providers.push(provider);
500    }
501
502    pub fn remote_runner_provider(
503        &mut self,
504        provider: Arc<dyn crate::remote_runner::RemoteRunnerProvider>,
505    ) {
506        self.remote_runner_providers.push(provider);
507    }
508
509    pub fn status_segment(&mut self, segment: crate::tui_status::StatusSegment) {
510        self.status_segments.push(segment);
511    }
512
513    pub fn palette_source(&mut self, source: crate::tui_status::PaletteSourceDescriptor) {
514        self.palette_sources.push(source);
515    }
516
517    pub fn code_index_provider(&mut self, provider: Arc<dyn crate::code_index::CodeIndexProvider>) {
518        self.code_index_providers.push(provider);
519    }
520
521    pub fn review_publisher(&mut self, publisher: Arc<dyn crate::review::ReviewPublisher>) {
522        self.review_publishers.push(publisher);
523    }
524
525    fn validate(&self) -> anyhow::Result<RegistryValidation> {
526        validate_manifests(&self.manifests)?;
527        validate_actual_services(self)?;
528        validate_tool_contributors(&self.tools)?;
529        let capability_statuses = validate_capabilities(
530            &self.manifests,
531            &self.granted_capabilities,
532            &self.denied_capabilities,
533        )?;
534        Ok(RegistryValidation {
535            capability_statuses,
536        })
537    }
538}
539
540#[async_trait::async_trait]
541pub trait EventSink: Send + Sync + 'static {
542    fn id(&self) -> EventSinkId;
543
544    async fn handle_event(&self, envelope: &crate::events::EventEnvelope) -> anyhow::Result<()>;
545}
546
547struct RegistryValidation {
548    capability_statuses: BTreeMap<ExtensionId, Vec<CapabilityStatus>>,
549}
550
551fn validate_manifests(manifests: &[ExtensionManifest]) -> anyhow::Result<()> {
552    let mut extension_ids = BTreeSet::new();
553    let mut services = BTreeMap::<ProvidedService, ExtensionId>::new();
554    for manifest in manifests {
555        if manifest.id.trim().is_empty() {
556            anyhow::bail!("extension manifest has an empty id");
557        }
558        if !extension_ids.insert(manifest.id.clone()) {
559            anyhow::bail!("duplicate extension id {}", manifest.id);
560        }
561        validate_api_version(manifest)?;
562        for service in &manifest.provides {
563            if let Some(existing) = services.insert(service.clone(), manifest.id.clone()) {
564                anyhow::bail!(
565                    "duplicate provided service {} declared by {} and {}",
566                    service_label(service),
567                    existing,
568                    manifest.id
569                );
570            }
571        }
572    }
573    Ok(())
574}
575
576fn validate_api_version(manifest: &ExtensionManifest) -> anyhow::Result<()> {
577    let supported = Version::parse(SUPPORTED_EXTENSION_API_VERSION)?;
578    let requirement = VersionReq::parse(&manifest.api_version).or_else(|_| {
579        Version::parse(&manifest.api_version).map(|version| VersionReq {
580            comparators: vec![semver::Comparator {
581                op: semver::Op::Exact,
582                major: version.major,
583                minor: Some(version.minor),
584                patch: Some(version.patch),
585                pre: version.pre,
586            }],
587        })
588    })?;
589    if requirement.matches(&supported) {
590        Ok(())
591    } else {
592        anyhow::bail!(
593            "extension {} requires unsupported API version {}; supported {}",
594            manifest.id,
595            manifest.api_version,
596            SUPPORTED_EXTENSION_API_VERSION
597        )
598    }
599}
600
601fn validate_actual_services(builder: &ExtensionRegistryBuilder) -> anyhow::Result<()> {
602    let declared = builder
603        .manifests
604        .iter()
605        .flat_map(|manifest| manifest.provides.iter().cloned())
606        .collect::<BTreeSet<_>>();
607    let actual = actual_services(builder)?;
608    for service in &declared {
609        if !actual.contains(service) {
610            anyhow::bail!(
611                "manifest declares provided service {} but no matching service was installed",
612                service_label(service)
613            );
614        }
615    }
616    validate_duplicate_actual_services(&actual)
617}
618
619fn validate_duplicate_actual_services(actual: &[ProvidedService]) -> anyhow::Result<()> {
620    let mut seen = BTreeSet::new();
621    for service in actual {
622        if !seen.insert(service.clone()) {
623            anyhow::bail!("duplicate installed service {}", service_label(service));
624        }
625    }
626    Ok(())
627}
628
629/// Multiset of the services currently registered on the builder; diffed
630/// around each extension install to attribute new services to that extension.
631fn service_counts(
632    builder: &ExtensionRegistryBuilder,
633) -> anyhow::Result<BTreeMap<ProvidedService, usize>> {
634    let mut counts = BTreeMap::new();
635    for service in actual_services(builder)? {
636        *counts.entry(service).or_insert(0) += 1;
637    }
638    Ok(counts)
639}
640
641fn actual_services(builder: &ExtensionRegistryBuilder) -> anyhow::Result<Vec<ProvidedService>> {
642    let mut services = Vec::new();
643    services.extend(
644        builder
645            .inference_engines
646            .iter()
647            .map(|service| ProvidedService::InferenceEngine(service.id())),
648    );
649    services.extend(
650        builder
651            .inference_routers
652            .iter()
653            .map(|service| ProvidedService::InferenceRouter(service.id())),
654    );
655    services.extend(
656        builder
657            .context_providers
658            .iter()
659            .map(|service| ProvidedService::ContextProvider(service.id())),
660    );
661    services.extend(
662        builder
663            .context_planners
664            .iter()
665            .map(|service| ProvidedService::ContextPlanner(service.id())),
666    );
667    services.extend(
668        builder
669            .thread_stores
670            .iter()
671            .map(|service| ProvidedService::ThreadStore(service.id())),
672    );
673    services.extend(
674        builder
675            .checkpoint_stores
676            .iter()
677            .map(|service| ProvidedService::CheckpointStore(service.id())),
678    );
679    services.extend(
680        builder
681            .memory_stores
682            .iter()
683            .map(|service| ProvidedService::MemoryStore(service.id())),
684    );
685    services.extend(
686        builder
687            .knowledge_stores
688            .iter()
689            .map(|service| ProvidedService::KnowledgeStore(service.id())),
690    );
691    services.extend(
692        builder
693            .embedding_providers
694            .iter()
695            .map(|service| ProvidedService::EmbeddingProvider(service.descriptor().id)),
696    );
697    services.extend(
698        builder
699            .media_generator_providers
700            .iter()
701            .map(|service| ProvidedService::MediaGenerator(service.provider_id().to_string())),
702    );
703    services.extend(
704        builder
705            .tools
706            .iter()
707            .map(|service| ProvidedService::ToolProvider(service.id())),
708    );
709    services.extend(
710        builder
711            .subagent_dispatchers
712            .iter()
713            .map(|service| ProvidedService::SubagentDispatcher(service.id())),
714    );
715    services.extend(
716        builder
717            .policy_contributors
718            .iter()
719            .map(|service| ProvidedService::PolicyContributor(service.id())),
720    );
721    services.extend(
722        builder
723            .event_sinks
724            .iter()
725            .map(|service| ProvidedService::EventSink(service.id())),
726    );
727    services.extend(
728        builder
729            .fork_providers
730            .iter()
731            .map(|service| ProvidedService::ForkProvider(service.descriptor().id)),
732    );
733    services.extend(
734        builder
735            .task_executors
736            .iter()
737            .map(|service| ProvidedService::TaskExecutor(service.id())),
738    );
739    services.extend(
740        builder
741            .notification_sinks
742            .iter()
743            .map(|service| ProvidedService::NotificationSink(service.id())),
744    );
745    services.extend(
746        builder
747            .interactive_region_handlers
748            .iter()
749            .map(|service| ProvidedService::InteractiveRegionHandler(service.id())),
750    );
751    services.extend(
752        builder
753            .speech_transcribers
754            .iter()
755            .map(|service| ProvidedService::SpeechTranscriber(service.id())),
756    );
757    services.extend(
758        builder
759            .speech_synthesizers
760            .iter()
761            .map(|service| ProvidedService::SpeechSynthesizer(service.id())),
762    );
763    services.extend(
764        builder
765            .version_control_providers
766            .iter()
767            .map(|service| ProvidedService::VersionControlProvider(service.id())),
768    );
769    services.extend(
770        builder
771            .remote_runner_providers
772            .iter()
773            .map(|service| ProvidedService::RemoteRunnerProvider(service.id())),
774    );
775    services.extend(
776        builder
777            .status_segments
778            .iter()
779            .map(|service| ProvidedService::StatusSegment(service.id.clone())),
780    );
781    services.extend(
782        builder
783            .palette_sources
784            .iter()
785            .map(|service| ProvidedService::PaletteSource(service.id.clone())),
786    );
787    services.extend(
788        builder
789            .code_index_providers
790            .iter()
791            .map(|service| ProvidedService::CodeIndexProvider(service.id())),
792    );
793    services.extend(
794        builder
795            .review_publishers
796            .iter()
797            .map(|service| ProvidedService::ReviewPublisher(service.descriptor().id)),
798    );
799    Ok(services)
800}
801
802fn validate_tool_contributors(
803    contributors: &[Arc<dyn crate::tools::ToolContributor>],
804) -> anyhow::Result<()> {
805    let mut registry = crate::tools::ToolRegistry::default();
806    for contributor in contributors {
807        contributor.contribute(&mut registry)?;
808    }
809    Ok(())
810}
811
812fn validate_capabilities(
813    manifests: &[ExtensionManifest],
814    granted: &BTreeMap<ExtensionId, BTreeSet<String>>,
815    denied: &BTreeMap<ExtensionId, BTreeMap<String, String>>,
816) -> anyhow::Result<BTreeMap<ExtensionId, Vec<CapabilityStatus>>> {
817    let mut statuses = BTreeMap::new();
818    for manifest in manifests {
819        let mut seen = BTreeSet::new();
820        let mut extension_statuses = Vec::new();
821        for request in &manifest.required_capabilities {
822            if !seen.insert(request.id.clone()) {
823                anyhow::bail!(
824                    "extension {} declares capability {} more than once",
825                    manifest.id,
826                    request.id
827                );
828            }
829            if let Some(reason) = denied
830                .get(&manifest.id)
831                .and_then(|denials| denials.get(&request.id))
832            {
833                anyhow::bail!(
834                    "extension {} requires denied capability {}: {}",
835                    manifest.id,
836                    request.id,
837                    reason
838                );
839            }
840            let decision = if granted
841                .get(&manifest.id)
842                .is_some_and(|grants| grants.contains(&request.id))
843            {
844                crate::capabilities::CapabilityDecision::Granted
845            } else {
846                crate::capabilities::CapabilityDecision::Requested
847            };
848            extension_statuses.push(CapabilityStatus {
849                id: request.id.clone(),
850                decision,
851                reason: request.reason.clone(),
852            });
853        }
854        statuses.insert(manifest.id.clone(), extension_statuses);
855    }
856    Ok(statuses)
857}
858
859fn service_label(service: &ProvidedService) -> String {
860    match service {
861        ProvidedService::InferenceEngine(id) => format!("InferenceEngine({id})"),
862        ProvidedService::InferenceRouter(id) => format!("InferenceRouter({id})"),
863        ProvidedService::ContextProvider(id) => format!("ContextProvider({id})"),
864        ProvidedService::ContextPlanner(id) => format!("ContextPlanner({id})"),
865        ProvidedService::ThreadStore(id) => format!("ThreadStore({id})"),
866        ProvidedService::CheckpointStore(id) => format!("CheckpointStore({id})"),
867        ProvidedService::MemoryStore(id) => format!("MemoryStore({id})"),
868        ProvidedService::KnowledgeStore(id) => format!("KnowledgeStore({id})"),
869        ProvidedService::EmbeddingProvider(id) => format!("EmbeddingProvider({id})"),
870        ProvidedService::MediaGenerator(id) => format!("MediaGenerator({id})"),
871        ProvidedService::ToolProvider(id) => format!("ToolProvider({id})"),
872        ProvidedService::SubagentDispatcher(id) => format!("SubagentDispatcher({id})"),
873        ProvidedService::PolicyContributor(id) => format!("PolicyContributor({id})"),
874        ProvidedService::EventSink(id) => format!("EventSink({id})"),
875        ProvidedService::ForkProvider(id) => format!("ForkProvider({id})"),
876        ProvidedService::TaskExecutor(id) => format!("TaskExecutor({id})"),
877        ProvidedService::NotificationSink(id) => format!("NotificationSink({id})"),
878        ProvidedService::InteractiveRegionHandler(id) => {
879            format!("InteractiveRegionHandler({id})")
880        }
881        ProvidedService::SpeechTranscriber(id) => format!("SpeechTranscriber({id})"),
882        ProvidedService::SpeechSynthesizer(id) => format!("SpeechSynthesizer({id})"),
883        ProvidedService::VersionControlProvider(id) => {
884            format!("VersionControlProvider({id})")
885        }
886        ProvidedService::RemoteRunnerProvider(id) => format!("RemoteRunnerProvider({id})"),
887        ProvidedService::StatusSegment(id) => format!("StatusSegment({id})"),
888        ProvidedService::PaletteSource(id) => format!("PaletteSource({id})"),
889        ProvidedService::CodeIndexProvider(id) => format!("CodeIndexProvider({id})"),
890        ProvidedService::ReviewPublisher(id) => format!("ReviewPublisher({id})"),
891    }
892}
893
894#[cfg(test)]
895mod tests {
896    use std::path::{Path, PathBuf};
897    use std::sync::Arc;
898
899    use crate::tui_status::{PaletteSourceDescriptor, StatusCell, StatusSegment, StatusStyle};
900    use crate::version_control::{
901        VcsCapabilities, VcsChangedContentPage, VcsChangedFile, VcsDetectionClaim, VcsError,
902        VcsListChangesRequest, VcsProvider, VcsReadChangedContentRequest, VcsStatus,
903        VcsStatusRequest, VcsWorkspace,
904    };
905
906    use super::*;
907
908    #[test]
909    fn provided_service_status_segment_round_trips_json() {
910        let service = ProvidedService::StatusSegment("mode".to_string());
911        let encoded = serde_json::to_value(&service).expect("serialize status segment service");
912        assert_eq!(encoded, serde_json::json!({ "StatusSegment": "mode" }));
913
914        let decoded = serde_json::from_value::<ProvidedService>(encoded)
915            .expect("deserialize status segment service");
916        assert_eq!(decoded, service);
917    }
918
919    #[test]
920    fn provided_service_inference_router_round_trips_json() {
921        let service = ProvidedService::InferenceRouter("adaptive".to_string());
922        let encoded = serde_json::to_value(&service).expect("serialize inference router service");
923        assert_eq!(
924            encoded,
925            serde_json::json!({ "InferenceRouter": "adaptive" })
926        );
927
928        let decoded = serde_json::from_value::<ProvidedService>(encoded)
929            .expect("deserialize inference router service");
930        assert_eq!(decoded, service);
931    }
932
933    #[test]
934    fn provided_service_palette_source_round_trips_json() {
935        let service = ProvidedService::PaletteSource("commands".to_string());
936        let encoded = serde_json::to_value(&service).expect("serialize palette source service");
937        assert_eq!(encoded, serde_json::json!({ "PaletteSource": "commands" }));
938
939        let decoded = serde_json::from_value::<ProvidedService>(encoded)
940            .expect("deserialize palette source service");
941        assert_eq!(decoded, service);
942    }
943
944    #[test]
945    fn provided_service_media_generator_round_trips_json() {
946        let service = ProvidedService::MediaGenerator("openai".to_string());
947        let encoded = serde_json::to_value(&service).expect("serialize media generator service");
948        assert_eq!(encoded, serde_json::json!({ "MediaGenerator": "openai" }));
949
950        let decoded = serde_json::from_value::<ProvidedService>(encoded)
951            .expect("deserialize media generator service");
952        assert_eq!(decoded, service);
953    }
954
955    #[test]
956    fn registering_media_generator_advertises_service_and_resolves_provider() {
957        struct FakeImageExtension;
958
959        struct FakeImageProvider;
960
961        #[async_trait::async_trait]
962        impl crate::media::MediaGeneratorProvider for FakeImageProvider {
963            fn provider_id(&self) -> &str {
964                "fake"
965            }
966
967            fn descriptor(&self) -> crate::media::MediaProviderDescriptor {
968                crate::media::MediaProviderDescriptor {
969                    id: "fake".to_string(),
970                    display_name: "Fake Image Provider".to_string(),
971                    supports_images: true,
972                    configured: true,
973                    ..crate::media::MediaProviderDescriptor::default()
974                }
975            }
976        }
977
978        impl RoderExtension for FakeImageExtension {
979            fn manifest(&self) -> ExtensionManifest {
980                ExtensionManifest {
981                    id: "fake-image-extension".to_string(),
982                    name: "Fake Image".to_string(),
983                    version: Version::new(0, 1, 0),
984                    api_version: SUPPORTED_EXTENSION_API_VERSION.to_string(),
985                    description: None,
986                    provides: vec![ProvidedService::MediaGenerator("fake".to_string())],
987                    required_capabilities: Vec::new(),
988                }
989            }
990
991            fn install(&self, registry: &mut ExtensionRegistryBuilder) -> anyhow::Result<()> {
992                registry.media_generator_provider(Arc::new(FakeImageProvider));
993                Ok(())
994            }
995        }
996
997        let mut builder = ExtensionRegistryBuilder::new();
998        builder
999            .install(FakeImageExtension)
1000            .expect("install image extension");
1001        let registry = builder.build().expect("build registry");
1002
1003        assert!(
1004            registry
1005                .provided_services()
1006                .contains(&ProvidedService::MediaGenerator("fake".to_string()))
1007        );
1008        let provider = registry.media_generator("fake").expect("resolve provider");
1009        assert!(provider.descriptor().supports_images);
1010        assert!(registry.media_generator("missing").is_none());
1011    }
1012
1013    #[test]
1014    fn provided_service_task_executor_round_trips_json() {
1015        let service = ProvidedService::TaskExecutor("process".to_string());
1016        let encoded = serde_json::to_value(&service).expect("serialize task executor service");
1017        assert_eq!(encoded, serde_json::json!({ "TaskExecutor": "process" }));
1018
1019        let decoded = serde_json::from_value::<ProvidedService>(encoded)
1020            .expect("deserialize task executor service");
1021        assert_eq!(decoded, service);
1022    }
1023
1024    #[test]
1025    fn provided_service_code_index_provider_round_trips_json() {
1026        let service = ProvidedService::CodeIndexProvider("local-code-index".to_string());
1027        let encoded =
1028            serde_json::to_value(&service).expect("serialize code index provider service");
1029        assert_eq!(
1030            encoded,
1031            serde_json::json!({ "CodeIndexProvider": "local-code-index" })
1032        );
1033
1034        let decoded = serde_json::from_value::<ProvidedService>(encoded)
1035            .expect("deserialize code index provider service");
1036        assert_eq!(decoded, service);
1037    }
1038
1039    #[test]
1040    fn provided_service_notification_sink_round_trips_json() {
1041        let service = ProvidedService::NotificationSink("terminal-bell".to_string());
1042        let encoded = serde_json::to_value(&service).expect("serialize notification sink service");
1043        assert_eq!(
1044            encoded,
1045            serde_json::json!({ "NotificationSink": "terminal-bell" })
1046        );
1047
1048        let decoded = serde_json::from_value::<ProvidedService>(encoded)
1049            .expect("deserialize notification sink service");
1050        assert_eq!(decoded, service);
1051    }
1052
1053    #[test]
1054    fn provided_service_interactive_region_handler_round_trips_json() {
1055        let service = ProvidedService::InteractiveRegionHandler("links".to_string());
1056        let encoded =
1057            serde_json::to_value(&service).expect("serialize interactive region handler service");
1058        assert_eq!(
1059            encoded,
1060            serde_json::json!({ "InteractiveRegionHandler": "links" })
1061        );
1062
1063        let decoded = serde_json::from_value::<ProvidedService>(encoded)
1064            .expect("deserialize interactive region handler service");
1065        assert_eq!(decoded, service);
1066    }
1067
1068    #[test]
1069    fn provided_service_remote_runner_provider_round_trips_json() {
1070        let service = ProvidedService::RemoteRunnerProvider("unix-local".to_string());
1071        let encoded =
1072            serde_json::to_value(&service).expect("serialize remote runner provider service");
1073        assert_eq!(
1074            encoded,
1075            serde_json::json!({ "RemoteRunnerProvider": "unix-local" })
1076        );
1077
1078        let decoded = serde_json::from_value::<ProvidedService>(encoded)
1079            .expect("deserialize remote runner provider service");
1080        assert_eq!(decoded, service);
1081    }
1082
1083    #[test]
1084    fn provided_service_version_control_provider_round_trips_json() {
1085        let service = ProvidedService::VersionControlProvider("git".to_string());
1086        let encoded =
1087            serde_json::to_value(&service).expect("serialize version control provider service");
1088        assert_eq!(
1089            encoded,
1090            serde_json::json!({ "VersionControlProvider": "git" })
1091        );
1092
1093        let decoded = serde_json::from_value::<ProvidedService>(encoded)
1094            .expect("deserialize version control provider service");
1095        assert_eq!(decoded, service);
1096    }
1097
1098    #[test]
1099    fn registry_builder_records_status_segments() {
1100        let mut builder = ExtensionRegistryBuilder::new();
1101        builder.status_segment(StatusSegment::new("custom", 42, 6, |_| StatusCell {
1102            text: "ready".to_string(),
1103            style: StatusStyle::Accent,
1104            tooltip: None,
1105        }));
1106
1107        let registry = builder.build().expect("build registry");
1108        assert_eq!(registry.status_segments.len(), 1);
1109        assert_eq!(registry.status_segments[0].id, "custom");
1110        assert_eq!(registry.status_segments[0].priority, 42);
1111        assert_eq!(registry.status_segments[0].min_width, 6);
1112    }
1113
1114    #[test]
1115    fn registry_builder_records_palette_sources() {
1116        let mut builder = ExtensionRegistryBuilder::new();
1117        builder.palette_source(PaletteSourceDescriptor {
1118            id: "commands".to_string(),
1119            label: "Commands".to_string(),
1120            priority: 100,
1121        });
1122
1123        let registry = builder.build().expect("build registry");
1124        assert_eq!(registry.palette_sources.len(), 1);
1125        assert_eq!(registry.palette_sources[0].id, "commands");
1126        assert_eq!(registry.palette_sources[0].label, "Commands");
1127        assert_eq!(registry.palette_sources[0].priority, 100);
1128    }
1129
1130    #[test]
1131    fn registering_vcs_provider_advertises_service_and_builds_registry() {
1132        let mut builder = ExtensionRegistryBuilder::new();
1133        builder
1134            .install(FakeVcsExtension::new("git"))
1135            .expect("install vcs extension");
1136
1137        let registry = builder.build().expect("build registry");
1138
1139        assert!(
1140            registry
1141                .provided_services()
1142                .contains(&ProvidedService::VersionControlProvider("git".to_string()))
1143        );
1144        assert!(registry.version_control_provider("git").is_some());
1145    }
1146
1147    #[test]
1148    fn duplicate_vcs_provider_ids_fail_registry_validation() {
1149        let mut builder = ExtensionRegistryBuilder::new();
1150        builder.version_control_provider(Arc::new(FakeVcsProvider::new("git")));
1151        builder.version_control_provider(Arc::new(FakeVcsProvider::new("git")));
1152
1153        let error = match builder.build() {
1154            Ok(_) => panic!("duplicate provider should fail"),
1155            Err(error) => error,
1156        };
1157
1158        assert!(
1159            error
1160                .to_string()
1161                .contains("duplicate installed service VersionControlProvider(git)")
1162        );
1163    }
1164
1165    #[test]
1166    fn installing_an_undeclared_service_fails_install() {
1167        let mut builder = ExtensionRegistryBuilder::new();
1168
1169        let error = match builder.install(UndeclaredServiceExtension) {
1170            Ok(()) => panic!("undeclared service should fail install"),
1171            Err(error) => error,
1172        };
1173
1174        assert!(
1175            error
1176                .to_string()
1177                .contains("installed undeclared service VersionControlProvider(git)"),
1178            "unexpected error: {error}"
1179        );
1180        assert!(builder.manifests.is_empty());
1181    }
1182
1183    struct UndeclaredServiceExtension;
1184
1185    impl RoderExtension for UndeclaredServiceExtension {
1186        fn manifest(&self) -> ExtensionManifest {
1187            ExtensionManifest {
1188                id: "undeclared-service-extension".to_string(),
1189                name: "Undeclared Service".to_string(),
1190                version: Version::new(0, 1, 0),
1191                api_version: SUPPORTED_EXTENSION_API_VERSION.to_string(),
1192                description: None,
1193                provides: Vec::new(),
1194                required_capabilities: Vec::new(),
1195            }
1196        }
1197
1198        fn install(&self, registry: &mut ExtensionRegistryBuilder) -> anyhow::Result<()> {
1199            registry.version_control_provider(Arc::new(FakeVcsProvider::new("git")));
1200            Ok(())
1201        }
1202    }
1203
1204    struct FakeVcsExtension {
1205        id: String,
1206    }
1207
1208    impl FakeVcsExtension {
1209        fn new(id: impl Into<String>) -> Self {
1210            Self { id: id.into() }
1211        }
1212    }
1213
1214    impl RoderExtension for FakeVcsExtension {
1215        fn manifest(&self) -> ExtensionManifest {
1216            ExtensionManifest {
1217                id: format!("{}-extension", self.id),
1218                name: "Fake VCS".to_string(),
1219                version: Version::new(0, 1, 0),
1220                api_version: SUPPORTED_EXTENSION_API_VERSION.to_string(),
1221                description: None,
1222                provides: vec![ProvidedService::VersionControlProvider(self.id.clone())],
1223                required_capabilities: Vec::new(),
1224            }
1225        }
1226
1227        fn install(&self, registry: &mut ExtensionRegistryBuilder) -> anyhow::Result<()> {
1228            registry.version_control_provider(Arc::new(FakeVcsProvider::new(self.id.clone())));
1229            Ok(())
1230        }
1231    }
1232
1233    struct FakeVcsProvider {
1234        id: String,
1235    }
1236
1237    impl FakeVcsProvider {
1238        fn new(id: impl Into<String>) -> Self {
1239            Self { id: id.into() }
1240        }
1241    }
1242
1243    #[async_trait::async_trait]
1244    impl VcsProvider for FakeVcsProvider {
1245        fn id(&self) -> crate::version_control::VcsProviderId {
1246            self.id.clone()
1247        }
1248
1249        fn display_name(&self) -> String {
1250            self.id.clone()
1251        }
1252
1253        async fn detect(
1254            &self,
1255            workspace_root: &Path,
1256        ) -> Result<Option<VcsDetectionClaim>, VcsError> {
1257            Ok(Some(VcsDetectionClaim {
1258                workspace: VcsWorkspace {
1259                    root: workspace_root.to_path_buf(),
1260                    id: None,
1261                },
1262                priority: 0,
1263                metadata: serde_json::Value::Null,
1264            }))
1265        }
1266
1267        async fn status(&self, request: VcsStatusRequest) -> Result<VcsStatus, VcsError> {
1268            Ok(VcsStatus {
1269                provider: crate::version_control::VcsProviderIdentity {
1270                    id: self.id.clone(),
1271                    display_name: self.id.clone(),
1272                },
1273                workspace: VcsWorkspace {
1274                    root: request.workspace_root,
1275                    id: None,
1276                },
1277                active_line: None,
1278                base: None,
1279                capabilities: VcsCapabilities::default(),
1280                changed_file_count: 0,
1281            })
1282        }
1283
1284        async fn list_changes(
1285            &self,
1286            _request: VcsListChangesRequest,
1287        ) -> Result<Vec<VcsChangedFile>, VcsError> {
1288            Ok(Vec::new())
1289        }
1290
1291        async fn read_changed_content(
1292            &self,
1293            request: VcsReadChangedContentRequest,
1294        ) -> Result<VcsChangedContentPage, VcsError> {
1295            Ok(VcsChangedContentPage {
1296                path: PathBuf::from(request.path),
1297                content: Some(String::new()),
1298                offset: request.offset,
1299                total_lines: 0,
1300                next_offset: None,
1301                binary: false,
1302            })
1303        }
1304    }
1305}