Skip to main content

codex_plugin/
provider.rs

1use crate::manifest::PluginManifest;
2use codex_protocol::capabilities::SelectedCapabilityRoot;
3use codex_utils_path_uri::PathUri;
4use std::error::Error as StdError;
5use std::future::Future;
6use thiserror::Error;
7
8/// A plugin resource paired with the environment that owns its filesystem.
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub enum PluginResourceLocator {
11    Environment {
12        /// Environment whose filesystem owns the resource.
13        environment_id: String,
14        /// Resource URI within that filesystem.
15        path: PathUri,
16    },
17}
18
19/// Authority-bound location of a resolved plugin package.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum ResolvedPluginLocation {
22    Environment {
23        /// Environment whose filesystem owns the package.
24        environment_id: String,
25        /// Package root URI within that filesystem.
26        root: PathUri,
27    },
28}
29
30/// An inert plugin descriptor whose resources retain their source authority.
31#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct ResolvedPlugin {
33    selected_root_id: String,
34    location: ResolvedPluginLocation,
35    manifest_path: PluginResourceLocator,
36    manifest: PluginManifest<PluginResourceLocator>,
37}
38
39/// Failure to construct a resolved plugin with internally consistent resources.
40#[derive(Clone, Debug, Error, PartialEq, Eq)]
41pub enum ResolvedPluginError {
42    #[error("plugin resource path `{path}` is outside package root `{root}`")]
43    ResourceOutsideRoot { root: PathUri, path: PathUri },
44}
45
46impl ResolvedPlugin {
47    /// Creates an environment-owned descriptor from a validated plugin manifest.
48    pub fn from_environment(
49        selected_root_id: String,
50        environment_id: String,
51        root: PathUri,
52        manifest_path: PathUri,
53        manifest: PluginManifest<PathUri>,
54    ) -> Result<Self, ResolvedPluginError> {
55        let manifest_path = environment_resource(&environment_id, &root, manifest_path)?;
56        let manifest = manifest
57            .try_map_resources(|path| environment_resource(&environment_id, &root, path))?;
58        Ok(Self {
59            selected_root_id,
60            location: ResolvedPluginLocation::Environment {
61                environment_id,
62                root,
63            },
64            manifest_path,
65            manifest,
66        })
67    }
68
69    /// Returns the opaque ID supplied for the selected capability root.
70    pub fn selected_root_id(&self) -> &str {
71        &self.selected_root_id
72    }
73
74    /// Returns the authority-bound package location.
75    pub fn location(&self) -> &ResolvedPluginLocation {
76        &self.location
77    }
78
79    /// Returns the manifest resource used to resolve this package.
80    pub fn manifest_path(&self) -> &PluginResourceLocator {
81        &self.manifest_path
82    }
83
84    /// Returns package metadata whose resource fields retain their source authority.
85    pub fn manifest(&self) -> &PluginManifest<PluginResourceLocator> {
86        &self.manifest
87    }
88}
89
90fn environment_resource(
91    environment_id: &str,
92    root: &PathUri,
93    path: PathUri,
94) -> Result<PluginResourceLocator, ResolvedPluginError> {
95    if !path.starts_with(root) {
96        return Err(ResolvedPluginError::ResourceOutsideRoot {
97            root: root.clone(),
98            path,
99        });
100    }
101    Ok(PluginResourceLocator::Environment {
102        environment_id: environment_id.to_string(),
103        path,
104    })
105}
106
107/// Resolves source-owned package roots into inert plugin descriptors.
108///
109/// Implementations must perform all filesystem access through the authority
110/// named by the selected root. `None` means the root contains no plugin
111/// manifest and may be handled as another standalone capability.
112pub trait PluginProvider: Send + Sync {
113    /// Source-specific resolution failure.
114    type Error: StdError + Send + Sync + 'static;
115
116    /// Resolves one selected root without activating any of its components.
117    fn resolve(
118        &self,
119        root: &SelectedCapabilityRoot,
120    ) -> impl Future<Output = Result<Option<ResolvedPlugin>, Self::Error>> + Send;
121}
122
123#[cfg(test)]
124#[path = "provider_tests.rs"]
125mod tests;