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