Skip to main content

systemprompt_models/extension/
mod.rs

1//! Extension manifest types.
2//!
3//! Deserialized shape of an extension's `manifest.toml`:
4//! [`ExtensionManifest`] / [`Extension`] metadata, [`ExtensionType`]
5//! and [`BuildType`] discriminants, declared roles and CLI commands.
6//! [`DiscoveredExtension`] pairs a parsed manifest with its on-disk
7//! location after loader discovery.
8
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::path::PathBuf;
12
13#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum ExtensionType {
16    Mcp,
17    Blog,
18    Cli,
19    #[default]
20    Other,
21}
22
23#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "lowercase")]
25pub enum BuildType {
26    #[default]
27    Workspace,
28    Submodule,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct ManifestRole {
33    pub display_name: String,
34    pub description: String,
35    #[serde(default)]
36    pub permissions: Vec<String>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct CliCommand {
41    pub name: String,
42    #[serde(default)]
43    pub description: String,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct ExtensionManifest {
48    pub extension: Extension,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct Extension {
53    #[serde(rename = "type")]
54    pub type_: ExtensionType,
55
56    pub name: String,
57
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub binary: Option<String>,
60
61    #[serde(default)]
62    pub description: String,
63
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    pub port: Option<u16>,
66
67    #[serde(default)]
68    pub build_type: BuildType,
69
70    #[serde(default = "default_true")]
71    pub enabled: bool,
72
73    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
74    pub roles: HashMap<String, ManifestRole>,
75
76    #[serde(default, skip_serializing_if = "Vec::is_empty")]
77    pub commands: Vec<CliCommand>,
78
79    #[serde(default)]
80    pub supports_json_output: bool,
81}
82
83const fn default_true() -> bool {
84    true
85}
86
87#[derive(Debug, Clone)]
88pub struct DiscoveredExtension {
89    pub manifest: ExtensionManifest,
90    pub path: PathBuf,
91    pub manifest_path: PathBuf,
92}
93
94impl DiscoveredExtension {
95    pub const fn new(manifest: ExtensionManifest, path: PathBuf, manifest_path: PathBuf) -> Self {
96        Self {
97            manifest,
98            path,
99            manifest_path,
100        }
101    }
102
103    pub const fn extension_type(&self) -> ExtensionType {
104        self.manifest.extension.type_
105    }
106
107    pub fn binary_name(&self) -> Option<&str> {
108        self.manifest.extension.binary.as_deref()
109    }
110
111    pub const fn is_enabled(&self) -> bool {
112        self.manifest.extension.enabled
113    }
114
115    pub fn is_mcp(&self) -> bool {
116        self.manifest.extension.type_ == ExtensionType::Mcp
117    }
118
119    pub fn is_cli(&self) -> bool {
120        self.manifest.extension.type_ == ExtensionType::Cli
121    }
122
123    pub fn commands(&self) -> &[CliCommand] {
124        &self.manifest.extension.commands
125    }
126
127    pub const fn build_type(&self) -> BuildType {
128        self.manifest.extension.build_type
129    }
130
131    pub const fn supports_json_output(&self) -> bool {
132        self.manifest.extension.supports_json_output
133    }
134}