1use rskit_validation::Validator;
4use rskit_validation::input::validate_safe_path;
5use serde::{Deserialize, Serialize};
6
7use crate::SkillError;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
11#[serde(rename_all = "kebab-case")]
12pub enum Safety {
13 #[default]
15 ReadOnly,
16 Mutating,
18 Destructive,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(deny_unknown_fields)]
25pub struct Manifest {
26 #[serde(rename = "schema_version")]
28 pub schema_version: String,
29 pub name: String,
31 pub version: String,
33 pub description: String,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub license: Option<String>,
38 #[serde(default, skip_serializing_if = "Vec::is_empty")]
40 pub authors: Vec<String>,
41 pub references: References,
43 #[serde(default)]
45 pub requires: Requires,
46 #[serde(
48 default,
49 rename = "human_approval",
50 skip_serializing_if = "Vec::is_empty"
51 )]
52 pub human_approval: Vec<HumanApprovalStep>,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub budgets: Option<Budgets>,
56 #[serde(
58 default,
59 rename = "model_hints",
60 skip_serializing_if = "Option::is_none"
61 )]
62 pub model_hints: Option<ModelHints>,
63 #[serde(
65 default,
66 rename = "progressive_disclosure",
67 skip_serializing_if = "Option::is_none"
68 )]
69 pub progressive_disclosure: Option<ProgressiveDisclosure>,
70 #[serde(default, skip_serializing_if = "Vec::is_empty")]
72 pub scripts: Vec<ScriptAsset>,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub signature: Option<Signature>,
76 pub safety: Safety,
78}
79
80impl Manifest {
81 pub fn validate(&self) -> Result<(), SkillError> {
83 Validator::new()
84 .required("schema_version", &self.schema_version)
85 .required("name", &self.name)
86 .required("version", &self.version)
87 .required("description", &self.description)
88 .validate()
89 .map_err(|error| SkillError::InvalidManifest(error.to_string()))?;
90
91 for script in &self.scripts {
92 validate_safe_path(&script.path)
93 .map_err(|error| SkillError::InvalidManifest(error.to_string()))?;
94 }
95 Ok(())
96 }
97}
98
99#[derive(Debug, Clone, Default, Serialize, Deserialize)]
101#[serde(deny_unknown_fields)]
102pub struct PromptRef {
103 pub name: String,
105 pub version: String,
107}
108
109#[derive(Debug, Clone, Default, Serialize, Deserialize)]
111#[serde(deny_unknown_fields)]
112pub struct References {
113 #[serde(default, skip_serializing_if = "Vec::is_empty")]
115 pub tools: Vec<String>,
116 #[serde(default, skip_serializing_if = "Vec::is_empty")]
118 pub prompts: Vec<PromptRef>,
119 #[serde(default, skip_serializing_if = "Vec::is_empty")]
121 pub resources: Vec<String>,
122 #[serde(default, rename = "mcp_servers", skip_serializing_if = "Vec::is_empty")]
124 pub mcp_servers: Vec<String>,
125}
126
127#[derive(Debug, Clone, Default, Serialize, Deserialize)]
129#[serde(deny_unknown_fields)]
130pub struct Requires {
131 #[serde(default, skip_serializing_if = "Vec::is_empty")]
133 pub scopes: Vec<String>,
134 #[serde(default, skip_serializing_if = "Vec::is_empty")]
136 pub capabilities: Vec<String>,
137}
138
139#[derive(Debug, Clone, Default, Serialize, Deserialize)]
141#[serde(deny_unknown_fields)]
142pub struct HumanApprovalStep {
143 pub step: String,
145 pub when: String,
147 pub rationale: String,
149}
150
151#[derive(Debug, Clone, Default, Serialize, Deserialize)]
153#[serde(deny_unknown_fields)]
154pub struct Budgets {
155 #[serde(
157 default,
158 rename = "max_tokens",
159 skip_serializing_if = "Option::is_none"
160 )]
161 pub max_tokens: Option<u64>,
162 #[serde(default, rename = "max_calls", skip_serializing_if = "Option::is_none")]
164 pub max_calls: Option<u32>,
165 #[serde(default, rename = "max_cost", skip_serializing_if = "Option::is_none")]
167 pub max_cost: Option<MaxCost>,
168 #[serde(
170 default,
171 rename = "wall_clock",
172 skip_serializing_if = "Option::is_none"
173 )]
174 pub wall_clock: Option<String>,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
179#[serde(deny_unknown_fields)]
180pub struct MaxCost {
181 pub amount: f64,
183 pub currency: String,
185}
186
187#[derive(Debug, Clone, Default, Serialize, Deserialize)]
189#[serde(deny_unknown_fields)]
190pub struct ModelHints {
191 #[serde(default, skip_serializing_if = "Vec::is_empty")]
193 pub preferred: Vec<String>,
194 #[serde(default, skip_serializing_if = "Vec::is_empty")]
196 pub reject: Vec<String>,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201#[serde(deny_unknown_fields)]
202pub struct Signature {
203 pub algorithm: String,
205 pub value: String,
207 #[serde(rename = "key_id")]
209 pub key_id: String,
210}
211
212#[derive(Debug, Clone, Default, Serialize, Deserialize)]
214#[serde(deny_unknown_fields)]
215pub struct ProgressiveDisclosure {
216 pub summary: String,
218 pub detail: String,
220}
221
222#[derive(Debug, Clone, Default, Serialize, Deserialize)]
224#[serde(deny_unknown_fields)]
225pub struct ScriptAsset {
226 pub path: String,
228 pub description: String,
230}