Skip to main content

codex_plugin/
manifest.rs

1use codex_config::HooksFile;
2
3/// Parsed plugin metadata parameterized by its resource locator representation.
4///
5/// Host loading uses absolute paths, while resolved packages replace them with
6/// authority-bound locators before exposing the manifest to consumers.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct PluginManifest<Resource> {
9    pub name: String,
10    pub version: Option<String>,
11    pub description: Option<String>,
12    pub keywords: Vec<String>,
13    pub paths: PluginManifestPaths<Resource>,
14    pub interface: Option<PluginManifestInterface<Resource>>,
15}
16
17/// Component resources declared by a plugin manifest.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct PluginManifestPaths<Resource> {
20    pub skills: Vec<Resource>,
21    pub mcp_servers: Option<PluginManifestMcpServers<Resource>>,
22    pub apps: Option<Resource>,
23    pub hooks: Option<PluginManifestHooks<Resource>>,
24}
25
26/// MCP server declarations embedded in or referenced by a plugin manifest.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum PluginManifestMcpServers<Resource> {
29    Path(Resource),
30    Object(String),
31}
32
33/// Hook declarations embedded in or referenced by a plugin manifest.
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum PluginManifestHooks<Resource> {
36    Paths(Vec<Resource>),
37    Inline(Vec<HooksFile>),
38}
39
40/// Optional model- and UI-facing plugin metadata.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct PluginManifestInterface<Resource> {
43    pub display_name: Option<String>,
44    pub short_description: Option<String>,
45    pub long_description: Option<String>,
46    pub developer_name: Option<String>,
47    pub category: Option<String>,
48    pub capabilities: Vec<String>,
49    pub website_url: Option<String>,
50    pub privacy_policy_url: Option<String>,
51    pub terms_of_service_url: Option<String>,
52    pub default_prompt: Option<Vec<String>>,
53    pub brand_color: Option<String>,
54    pub composer_icon: Option<Resource>,
55    pub logo: Option<Resource>,
56    pub logo_dark: Option<Resource>,
57    pub screenshots: Vec<Resource>,
58}
59
60impl<Resource> Default for PluginManifestInterface<Resource> {
61    fn default() -> Self {
62        Self {
63            display_name: None,
64            short_description: None,
65            long_description: None,
66            developer_name: None,
67            category: None,
68            capabilities: Vec::new(),
69            website_url: None,
70            privacy_policy_url: None,
71            terms_of_service_url: None,
72            default_prompt: None,
73            brand_color: None,
74            composer_icon: None,
75            logo: None,
76            logo_dark: None,
77            screenshots: Vec::new(),
78        }
79    }
80}
81
82impl<Resource> PluginManifest<Resource> {
83    /// Returns the model- and UI-facing package name, falling back to the manifest name.
84    pub fn display_name(&self) -> &str {
85        self.interface
86            .as_ref()
87            .and_then(|interface| interface.display_name.as_deref())
88            .map(str::trim)
89            .filter(|display_name| !display_name.is_empty())
90            .unwrap_or(&self.name)
91    }
92
93    /// Maps every path-bearing resource in the manifest.
94    pub fn try_map_resources<Mapped, Error>(
95        self,
96        mut map: impl FnMut(Resource) -> Result<Mapped, Error>,
97    ) -> Result<PluginManifest<Mapped>, Error> {
98        let PluginManifest {
99            name,
100            version,
101            description,
102            keywords,
103            paths,
104            interface,
105        } = self;
106        let PluginManifestPaths {
107            skills,
108            mcp_servers,
109            apps,
110            hooks,
111        } = paths;
112        let hooks = match hooks {
113            Some(PluginManifestHooks::Paths(paths)) => Some(PluginManifestHooks::Paths(
114                paths
115                    .into_iter()
116                    .map(&mut map)
117                    .collect::<Result<Vec<_>, _>>()?,
118            )),
119            Some(PluginManifestHooks::Inline(hooks)) => Some(PluginManifestHooks::Inline(hooks)),
120            None => None,
121        };
122        let mcp_servers = match mcp_servers {
123            Some(PluginManifestMcpServers::Path(path)) => {
124                Some(PluginManifestMcpServers::Path(map(path)?))
125            }
126            Some(PluginManifestMcpServers::Object(servers)) => {
127                Some(PluginManifestMcpServers::Object(servers))
128            }
129            None => None,
130        };
131        let interface = match interface {
132            Some(interface) => {
133                let PluginManifestInterface {
134                    display_name,
135                    short_description,
136                    long_description,
137                    developer_name,
138                    category,
139                    capabilities,
140                    website_url,
141                    privacy_policy_url,
142                    terms_of_service_url,
143                    default_prompt,
144                    brand_color,
145                    composer_icon,
146                    logo,
147                    logo_dark,
148                    screenshots,
149                } = interface;
150                Some(PluginManifestInterface {
151                    display_name,
152                    short_description,
153                    long_description,
154                    developer_name,
155                    category,
156                    capabilities,
157                    website_url,
158                    privacy_policy_url,
159                    terms_of_service_url,
160                    default_prompt,
161                    brand_color,
162                    composer_icon: composer_icon.map(&mut map).transpose()?,
163                    logo: logo.map(&mut map).transpose()?,
164                    logo_dark: logo_dark.map(&mut map).transpose()?,
165                    screenshots: screenshots
166                        .into_iter()
167                        .map(&mut map)
168                        .collect::<Result<Vec<_>, _>>()?,
169                })
170            }
171            None => None,
172        };
173
174        Ok(PluginManifest {
175            name,
176            version,
177            description,
178            keywords,
179            paths: PluginManifestPaths {
180                skills: skills
181                    .into_iter()
182                    .map(&mut map)
183                    .collect::<Result<Vec<_>, _>>()?,
184                mcp_servers,
185                apps: apps.map(&mut map).transpose()?,
186                hooks,
187            },
188            interface,
189        })
190    }
191}