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