use rskit_validation::Validator;
use rskit_validation::input::validate_safe_path;
use serde::{Deserialize, Serialize};
use crate::SkillError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
pub enum Safety {
#[default]
ReadOnly,
Mutating,
Destructive,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Manifest {
#[serde(rename = "schema_version")]
pub schema_version: String,
pub name: String,
pub version: String,
pub description: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub license: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub authors: Vec<String>,
pub references: References,
#[serde(default)]
pub requires: Requires,
#[serde(
default,
rename = "human_approval",
skip_serializing_if = "Vec::is_empty"
)]
pub human_approval: Vec<HumanApprovalStep>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub budgets: Option<Budgets>,
#[serde(
default,
rename = "model_hints",
skip_serializing_if = "Option::is_none"
)]
pub model_hints: Option<ModelHints>,
#[serde(
default,
rename = "progressive_disclosure",
skip_serializing_if = "Option::is_none"
)]
pub progressive_disclosure: Option<ProgressiveDisclosure>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub scripts: Vec<ScriptAsset>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signature: Option<Signature>,
pub safety: Safety,
}
impl Manifest {
pub fn validate(&self) -> Result<(), SkillError> {
Validator::new()
.required("schema_version", &self.schema_version)
.required("name", &self.name)
.required("version", &self.version)
.required("description", &self.description)
.validate()
.map_err(|error| SkillError::InvalidManifest(error.to_string()))?;
for script in &self.scripts {
validate_safe_path(&script.path)
.map_err(|error| SkillError::InvalidManifest(error.to_string()))?;
}
Ok(())
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PromptRef {
pub name: String,
pub version: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct References {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub prompts: Vec<PromptRef>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub resources: Vec<String>,
#[serde(default, rename = "mcp_servers", skip_serializing_if = "Vec::is_empty")]
pub mcp_servers: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Requires {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub scopes: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub capabilities: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct HumanApprovalStep {
pub step: String,
pub when: String,
pub rationale: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Budgets {
#[serde(
default,
rename = "max_tokens",
skip_serializing_if = "Option::is_none"
)]
pub max_tokens: Option<u64>,
#[serde(default, rename = "max_calls", skip_serializing_if = "Option::is_none")]
pub max_calls: Option<u32>,
#[serde(default, rename = "max_cost", skip_serializing_if = "Option::is_none")]
pub max_cost: Option<MaxCost>,
#[serde(
default,
rename = "wall_clock",
skip_serializing_if = "Option::is_none"
)]
pub wall_clock: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MaxCost {
pub amount: f64,
pub currency: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ModelHints {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub preferred: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub reject: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Signature {
pub algorithm: String,
pub value: String,
#[serde(rename = "key_id")]
pub key_id: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProgressiveDisclosure {
pub summary: String,
pub detail: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ScriptAsset {
pub path: String,
pub description: String,
}