bamboo_agent/agent/skill/
types.rs1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6pub type SkillId = String;
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
11#[serde(rename_all = "snake_case")]
12#[derive(Default)]
13pub enum SkillVisibility {
14 #[default]
16 Public,
17 Private,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct SkillDefinition {
24 pub id: SkillId,
26
27 pub name: String,
29
30 pub description: String,
32
33 pub category: String,
35
36 #[serde(default)]
38 pub tags: Vec<String>,
39
40 pub prompt: String,
42
43 #[serde(default)]
45 pub tool_refs: Vec<String>,
46
47 #[serde(default)]
49 pub workflow_refs: Vec<String>,
50
51 #[serde(default)]
53 pub visibility: SkillVisibility,
54
55 #[serde(default = "default_version")]
57 pub version: String,
58
59 pub created_at: DateTime<Utc>,
61
62 pub updated_at: DateTime<Utc>,
64}
65
66fn default_version() -> String {
67 "1.0.0".to_string()
68}
69
70impl SkillDefinition {
71 pub fn new(
73 id: impl Into<String>,
74 name: impl Into<String>,
75 description: impl Into<String>,
76 category: impl Into<String>,
77 prompt: impl Into<String>,
78 ) -> Self {
79 let now = Utc::now();
80 Self {
81 id: id.into(),
82 name: name.into(),
83 description: description.into(),
84 category: category.into(),
85 tags: Vec::new(),
86 prompt: prompt.into(),
87 tool_refs: Vec::new(),
88 workflow_refs: Vec::new(),
89 visibility: SkillVisibility::default(),
90 version: default_version(),
91 created_at: now,
92 updated_at: now,
93 }
94 }
95
96 pub fn with_tool_ref(mut self, tool_ref: impl Into<String>) -> Self {
98 self.tool_refs.push(tool_ref.into());
99 self
100 }
101
102 pub fn with_workflow_ref(mut self, workflow_ref: impl Into<String>) -> Self {
104 self.workflow_refs.push(workflow_ref.into());
105 self
106 }
107
108 pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
110 self.tags.push(tag.into());
111 self
112 }
113
114 pub fn with_visibility(mut self, visibility: SkillVisibility) -> Self {
116 self.visibility = visibility;
117 self
118 }
119
120 pub fn touch(&mut self) {
122 self.updated_at = Utc::now();
123 }
124
125 pub fn is_builtin(&self) -> bool {
127 self.id.starts_with("builtin-") || self.id.starts_with("system-")
128 }
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct SkillStoreConfig {
134 pub skills_dir: std::path::PathBuf,
135}
136
137impl Default for SkillStoreConfig {
138 fn default() -> Self {
139 let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("."));
140 Self {
141 skills_dir: home.join(".bamboo").join("skills"),
142 }
143 }
144}
145
146#[derive(Debug, Clone, Default)]
148pub struct SkillFilter {
149 pub category: Option<String>,
151
152 pub tags: Vec<String>,
154
155 pub search: Option<String>,
157
158 pub visibility: Option<SkillVisibility>,
160}
161
162impl SkillFilter {
163 pub fn new() -> Self {
165 Self::default()
166 }
167
168 pub fn with_category(mut self, category: impl Into<String>) -> Self {
170 self.category = Some(category.into());
171 self
172 }
173
174 pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
176 self.tags.push(tag.into());
177 self
178 }
179
180 pub fn with_search(mut self, search: impl Into<String>) -> Self {
182 self.search = Some(search.into());
183 self
184 }
185
186 pub fn matches(&self, skill: &SkillDefinition) -> bool {
188 if let Some(ref category) = self.category {
189 if skill.category != *category {
190 return false;
191 }
192 }
193
194 if !self.tags.is_empty() {
195 let has_matching_tag = self.tags.iter().any(|tag| skill.tags.contains(tag));
196 if !has_matching_tag {
197 return false;
198 }
199 }
200
201 if let Some(ref search) = self.search {
202 let search_lower = search.to_lowercase();
203 if !skill.name.to_lowercase().contains(&search_lower)
204 && !skill.description.to_lowercase().contains(&search_lower)
205 {
206 return false;
207 }
208 }
209
210 if let Some(ref visibility) = self.visibility {
211 if skill.visibility != *visibility {
212 return false;
213 }
214 }
215
216 true
217 }
218}
219
220#[derive(Debug, thiserror::Error)]
222pub enum SkillError {
223 #[error("Skill not found: {0}")]
224 NotFound(SkillId),
225
226 #[error("Skill already exists: {0}")]
227 AlreadyExists(SkillId),
228
229 #[error("Invalid skill ID: {0}")]
230 InvalidId(String),
231
232 #[error("Validation error: {0}")]
233 Validation(String),
234
235 #[error("Storage error: {0}")]
236 Storage(String),
237
238 #[error("Read-only: {0}")]
239 ReadOnly(String),
240
241 #[error("IO error: {0}")]
242 Io(#[from] std::io::Error),
243
244 #[error("YAML error: {0}")]
245 Yaml(#[from] serde_yaml::Error),
246}
247
248pub type SkillResult<T> = Result<T, SkillError>;