1use adk_core::Tool;
2use serde::{Deserialize, Serialize};
3use std::path::PathBuf;
4use std::sync::Arc;
5
6#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8#[serde(default)]
9pub struct SkillFrontmatter {
10 pub name: String,
12 pub description: String,
14 pub version: Option<String>,
16 pub license: Option<String>,
18 pub compatibility: Option<String>,
20 #[serde(default)]
22 pub tags: Vec<String>,
23 #[serde(default, rename = "allowed-tools")]
25 pub allowed_tools: Vec<String>,
26 #[serde(default)]
28 pub references: Vec<String>,
29 pub trigger: Option<bool>,
31 pub hint: Option<String>,
33 #[serde(default)]
35 pub metadata: std::collections::HashMap<String, serde_json::Value>,
36 #[serde(default)]
38 pub triggers: Vec<String>,
39}
40
41#[derive(Debug, Clone)]
43pub struct ParsedSkill {
44 pub name: String,
46 pub description: String,
48 pub version: Option<String>,
50 pub license: Option<String>,
52 pub compatibility: Option<String>,
54 pub tags: Vec<String>,
56 pub allowed_tools: Vec<String>,
58 pub references: Vec<String>,
60 pub trigger: bool,
62 pub hint: Option<String>,
64 pub metadata: std::collections::HashMap<String, serde_json::Value>,
66 pub triggers: Vec<String>,
68 pub body: String,
70}
71
72#[derive(Debug, Clone, Serialize)]
74pub struct SkillDocument {
75 pub id: String,
77 pub name: String,
79 pub description: String,
81 pub version: Option<String>,
83 pub license: Option<String>,
85 pub compatibility: Option<String>,
87 pub tags: Vec<String>,
89 pub allowed_tools: Vec<String>,
91 pub references: Vec<String>,
93 pub trigger: bool,
95 pub hint: Option<String>,
97 pub metadata: std::collections::HashMap<String, serde_json::Value>,
99 pub body: String,
101 pub path: PathBuf,
103 pub hash: String,
105 pub last_modified: Option<i64>,
107 pub triggers: Vec<String>,
109}
110
111impl SkillDocument {
112 pub fn engineer_instruction(&self, max_chars: usize, active_tools: &[Arc<dyn Tool>]) -> String {
115 let mut body = self.body.clone();
116 if body.chars().count() > max_chars {
117 body = body.chars().take(max_chars).collect();
118 body.push_str("\n[... truncated]");
119 }
120
121 let mut parts = Vec::new();
122 parts.push(format!("[skill:{}]", self.name));
123 parts.push(format!("# {}\n{}", self.name, self.description));
124
125 if !active_tools.is_empty() {
127 let names: Vec<_> = active_tools.iter().map(|t: &Arc<dyn Tool>| t.name()).collect();
128 parts.push(format!("You have access to the following tools: {}.", names.join(", ")));
129 }
130
131 parts.push(format!("## Instructions\n{}", body));
132 parts.push("[/skill]".to_string());
133
134 parts.join("\n\n")
135 }
136
137 pub fn engineer_prompt_block(&self, max_chars: usize) -> String {
139 let mut body = self.body.clone();
140 if body.chars().count() > max_chars {
141 body = body.chars().take(max_chars).collect();
142 }
143 format!("[skill:{}]\n{}\n[/skill]", self.name, body)
144 }
145}
146
147#[derive(Debug, Clone, Serialize)]
149pub struct SkillSummary {
150 pub id: String,
152 pub name: String,
154 pub description: String,
156 pub version: Option<String>,
158 pub license: Option<String>,
160 pub compatibility: Option<String>,
162 pub tags: Vec<String>,
164 pub allowed_tools: Vec<String>,
166 pub references: Vec<String>,
168 pub trigger: bool,
170 pub hint: Option<String>,
172 pub metadata: std::collections::HashMap<String, serde_json::Value>,
174 pub path: PathBuf,
176 pub hash: String,
178 pub last_modified: Option<i64>,
180 pub triggers: Vec<String>,
182}
183
184impl From<&SkillDocument> for SkillSummary {
185 fn from(value: &SkillDocument) -> Self {
186 Self {
187 id: value.id.clone(),
188 name: value.name.clone(),
189 description: value.description.clone(),
190 version: value.version.clone(),
191 license: value.license.clone(),
192 compatibility: value.compatibility.clone(),
193 tags: value.tags.clone(),
194 allowed_tools: value.allowed_tools.clone(),
195 references: value.references.clone(),
196 trigger: value.trigger,
197 hint: value.hint.clone(),
198 metadata: value.metadata.clone(),
199 path: value.path.clone(),
200 hash: value.hash.clone(),
201 last_modified: value.last_modified,
202 triggers: value.triggers.clone(),
203 }
204 }
205}
206
207#[derive(Debug, Clone, Default)]
209pub struct SkillIndex {
210 skills: Vec<SkillDocument>,
211}
212
213impl SkillIndex {
214 pub fn new(skills: Vec<SkillDocument>) -> Self {
215 Self { skills }
216 }
217
218 pub fn is_empty(&self) -> bool {
219 self.skills.is_empty()
220 }
221
222 pub fn len(&self) -> usize {
223 self.skills.len()
224 }
225
226 pub fn skills(&self) -> &[SkillDocument] {
228 &self.skills
229 }
230
231 pub fn summaries(&self) -> Vec<SkillSummary> {
233 self.skills.iter().map(SkillSummary::from).collect()
234 }
235
236 pub fn find_by_name(&self, name: &str) -> Option<&SkillDocument> {
238 self.skills.iter().find(|s| s.name == name)
239 }
240
241 pub fn find_by_id(&self, id: &str) -> Option<&SkillDocument> {
243 self.skills.iter().find(|s| s.id == id)
244 }
245}
246
247#[derive(Debug, Clone)]
249pub struct SelectionPolicy {
250 pub top_k: usize,
252 pub min_score: f32,
254 pub include_tags: Vec<String>,
256 pub exclude_tags: Vec<String>,
258}
259
260impl Default for SelectionPolicy {
261 fn default() -> Self {
262 Self { top_k: 1, min_score: 1.0, include_tags: Vec::new(), exclude_tags: Vec::new() }
263 }
264}
265
266#[derive(Debug, Clone, Serialize)]
268pub struct SkillMatch {
269 pub score: f32,
280 pub skill: SkillSummary,
282}