1use crate::core::service::{ServiceError, SkillId};
4use async_trait::async_trait;
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::PathBuf;
9use std::sync::Arc;
10use tokio::sync::RwLock;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
16pub enum SourceType {
17 GitUrl,
18 LocalPath,
19 ZipFile,
20 Source,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct SkillDefinition {
25 pub id: SkillId,
26 pub name: String,
27 pub description: String,
28 pub version: String,
29 pub author: Option<String>,
30 pub enabled: bool,
31 pub created_at: DateTime<Utc>,
32 pub updated_at: DateTime<Utc>,
33
34 pub skill_file: std::path::PathBuf,
36 pub reference_files: Option<Vec<std::path::PathBuf>>,
37 pub script_files: Option<Vec<std::path::PathBuf>>,
38 pub asset_files: Option<Vec<std::path::PathBuf>>,
39
40 pub execution_environment: Option<String>,
42 pub dependencies: Option<Vec<String>>,
43 pub timeout: Option<u64>,
44
45 pub source_url: Option<String>,
47 pub source_type: Option<SourceType>,
48 pub source_branch: Option<String>,
49 pub source_tag: Option<String>,
50 pub source_subdir: Option<PathBuf>,
51 pub installed_from: Option<String>,
52 pub commit_hash: Option<String>,
53 pub fetched_at: Option<DateTime<Utc>>,
54 pub editable: bool,
55}
56
57impl SkillDefinition {
58 pub fn new(id: SkillId, name: String, description: String, version: String) -> Self {
59 let now = Utc::now();
60 Self {
61 id: id.clone(),
62 name,
63 description,
64 version,
65 author: None,
66 enabled: true,
67 created_at: now,
68 updated_at: now,
69 skill_file: std::path::PathBuf::from(format!("./skills/{}/SKILL.md", id)),
70 reference_files: None,
71 script_files: None,
72 asset_files: None,
73 execution_environment: None,
74 dependencies: None,
75 timeout: None,
76 source_url: None,
77 source_type: None,
78 source_branch: None,
79 source_tag: None,
80 source_subdir: None,
81 installed_from: None,
82 commit_hash: None,
83 fetched_at: None,
84 editable: false,
85 }
86 }
87
88 }
90
91#[async_trait]
92pub trait SkillManagementService: Send + Sync {
93 async fn register_skill(&self, skill: SkillDefinition) -> Result<SkillId, ServiceError>;
94 async fn force_register_skill(&self, skill: SkillDefinition) -> Result<SkillId, ServiceError>;
95 async fn get_skill(&self, skill_id: &SkillId) -> Result<Option<SkillDefinition>, ServiceError>;
96 async fn update_skill(
97 &self,
98 skill_id: &SkillId,
99 updates: SkillUpdate,
100 ) -> Result<(), ServiceError>;
101 async fn unregister_skill(&self, skill_id: &SkillId) -> Result<(), ServiceError>;
102 async fn list_skills(
103 &self,
104 filters: Option<SkillFilters>,
105 ) -> Result<Vec<SkillDefinition>, ServiceError>;
106 async fn enable_skill(&self, skill_id: &SkillId) -> Result<(), ServiceError>;
107 async fn disable_skill(&self, skill_id: &SkillId) -> Result<(), ServiceError>;
108}
109
110#[derive(Debug)]
111pub struct SkillManager {
112 skills: Arc<RwLock<HashMap<SkillId, SkillDefinition>>>,
113}
114
115impl Default for SkillManager {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121impl SkillManager {
122 pub fn new() -> Self {
123 Self {
124 skills: Arc::new(RwLock::new(HashMap::new())),
125 }
126 }
127}
128
129#[derive(Debug, Clone)]
130pub struct SkillFilters {
131 pub enabled: Option<bool>,
132}
133
134#[derive(Debug, Clone, Default)]
135pub struct SkillUpdate {
136 pub name: Option<String>,
137 pub description: Option<String>,
138 pub version: Option<String>,
139 pub author: Option<String>,
140 pub enabled: Option<bool>,
141 pub source_url: Option<String>,
142 pub source_type: Option<SourceType>,
143 pub source_branch: Option<String>,
144 pub source_tag: Option<String>,
145 pub source_subdir: Option<PathBuf>,
146 pub installed_from: Option<String>,
147 pub commit_hash: Option<String>,
148 pub fetched_at: Option<DateTime<Utc>>,
149 pub editable: Option<bool>,
150}
151
152#[async_trait]
153impl SkillManagementService for SkillManager {
154 async fn register_skill(&self, skill: SkillDefinition) -> Result<SkillId, ServiceError> {
155 let mut skills = self.skills.write().await;
156
157 if skills.contains_key(&skill.id) {
159 return Err(ServiceError::Custom(format!(
160 "Skill {} already exists",
161 skill.id
162 )));
163 }
164
165 let skill_id = skill.id.clone();
166 skills.insert(skill_id.clone(), skill);
167 Ok(skill_id)
168 }
169
170 async fn force_register_skill(&self, skill: SkillDefinition) -> Result<SkillId, ServiceError> {
171 let mut skills = self.skills.write().await;
172
173 let skill_id = skill.id.clone();
174 skills.insert(skill_id.clone(), skill);
175 Ok(skill_id)
176 }
177
178 async fn get_skill(&self, skill_id: &SkillId) -> Result<Option<SkillDefinition>, ServiceError> {
179 let skills = self.skills.read().await;
180 Ok(skills.get(skill_id).cloned())
181 }
182
183 async fn update_skill(
184 &self,
185 skill_id: &SkillId,
186 updates: SkillUpdate,
187 ) -> Result<(), ServiceError> {
188 let mut skills = self.skills.write().await;
189
190 if let Some(skill) = skills.get_mut(skill_id) {
191 if let Some(name) = updates.name {
192 skill.name = name;
193 }
194 if let Some(description) = updates.description {
195 skill.description = description;
196 }
197 if let Some(version) = updates.version {
198 skill.version = version;
199 }
200 if let Some(author) = updates.author {
201 skill.author = Some(author);
202 }
203 if let Some(enabled) = updates.enabled {
204 skill.enabled = enabled;
205 }
206 if let Some(source_url) = updates.source_url {
207 skill.source_url = Some(source_url);
208 }
209 if let Some(source_type) = updates.source_type {
210 skill.source_type = Some(source_type);
211 }
212 if let Some(source_branch) = updates.source_branch {
213 skill.source_branch = Some(source_branch);
214 }
215 if let Some(source_tag) = updates.source_tag {
216 skill.source_tag = Some(source_tag);
217 }
218 if let Some(source_subdir) = updates.source_subdir {
219 skill.source_subdir = Some(source_subdir);
220 }
221 if let Some(installed_from) = updates.installed_from {
222 skill.installed_from = Some(installed_from);
223 }
224 if let Some(commit_hash) = updates.commit_hash {
225 skill.commit_hash = Some(commit_hash);
226 }
227 if let Some(fetched_at) = updates.fetched_at {
228 skill.fetched_at = Some(fetched_at);
229 }
230 if let Some(editable) = updates.editable {
231 skill.editable = editable;
232 }
233 skill.updated_at = Utc::now();
234 Ok(())
235 } else {
236 Err(ServiceError::SkillNotFound(skill_id.to_string()))
237 }
238 }
239
240 async fn unregister_skill(&self, skill_id: &SkillId) -> Result<(), ServiceError> {
241 let mut skills = self.skills.write().await;
242
243 if skills.remove(skill_id).is_some() {
244 Ok(())
245 } else {
246 Err(ServiceError::SkillNotFound(skill_id.to_string()))
247 }
248 }
249
250 async fn list_skills(
251 &self,
252 filters: Option<SkillFilters>,
253 ) -> Result<Vec<SkillDefinition>, ServiceError> {
254 let skills = self.skills.read().await;
255 let mut filtered_skills: Vec<SkillDefinition> = skills.values().cloned().collect();
256
257 if let Some(filters) = filters {
258 if let Some(enabled) = filters.enabled {
260 filtered_skills.retain(|skill| skill.enabled == enabled);
261 }
262 }
263
264 Ok(filtered_skills)
265 }
266
267 async fn enable_skill(&self, skill_id: &SkillId) -> Result<(), ServiceError> {
268 self.update_skill(
269 skill_id,
270 SkillUpdate {
271 enabled: Some(true),
272 ..Default::default()
273 },
274 )
275 .await
276 }
277
278 async fn disable_skill(&self, skill_id: &SkillId) -> Result<(), ServiceError> {
279 self.update_skill(
280 skill_id,
281 SkillUpdate {
282 enabled: Some(false),
283 ..Default::default()
284 },
285 )
286 .await
287 }
288}