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}
37
38#[derive(Debug, Clone)]
40pub struct ParsedSkill {
41 pub name: String,
43 pub description: String,
45 pub version: Option<String>,
47 pub license: Option<String>,
49 pub compatibility: Option<String>,
51 pub tags: Vec<String>,
53 pub allowed_tools: Vec<String>,
55 pub references: Vec<String>,
57 pub trigger: bool,
59 pub hint: Option<String>,
61 pub metadata: std::collections::HashMap<String, serde_json::Value>,
63 pub body: String,
65}
66
67#[derive(Debug, Clone, Serialize)]
69pub struct SkillDocument {
70 pub id: String,
72 pub name: String,
74 pub description: String,
76 pub version: Option<String>,
78 pub license: Option<String>,
80 pub compatibility: Option<String>,
82 pub tags: Vec<String>,
84 pub allowed_tools: Vec<String>,
86 pub references: Vec<String>,
88 pub trigger: bool,
90 pub hint: Option<String>,
92 pub metadata: std::collections::HashMap<String, serde_json::Value>,
94 pub body: String,
96 pub path: PathBuf,
98 pub hash: String,
100 pub last_modified: Option<i64>,
102}
103
104impl SkillDocument {
105 pub fn engineer_instruction(&self, max_chars: usize, active_tools: &[Arc<dyn Tool>]) -> String {
108 let mut body = self.body.clone();
109 if body.chars().count() > max_chars {
110 body = body.chars().take(max_chars).collect();
111 body.push_str("\n[... truncated]");
112 }
113
114 let mut parts = Vec::new();
115 parts.push(format!("[skill:{}]", self.name));
116 parts.push(format!("# {}\n{}", self.name, self.description));
117
118 if !active_tools.is_empty() {
120 let names: Vec<_> = active_tools.iter().map(|t: &Arc<dyn Tool>| t.name()).collect();
121 parts.push(format!("You have access to the following tools: {}.", names.join(", ")));
122 }
123
124 parts.push(format!("## Instructions\n{}", body));
125 parts.push("[/skill]".to_string());
126
127 parts.join("\n\n")
128 }
129
130 pub fn engineer_prompt_block(&self, max_chars: usize) -> String {
132 let mut body = self.body.clone();
133 if body.chars().count() > max_chars {
134 body = body.chars().take(max_chars).collect();
135 }
136 format!("[skill:{}]\n{}\n[/skill]", self.name, body)
137 }
138}
139
140#[derive(Debug, Clone, Serialize)]
142pub struct SkillSummary {
143 pub id: String,
145 pub name: String,
147 pub description: String,
149 pub version: Option<String>,
151 pub license: Option<String>,
153 pub compatibility: Option<String>,
155 pub tags: Vec<String>,
157 pub allowed_tools: Vec<String>,
159 pub references: Vec<String>,
161 pub trigger: bool,
163 pub hint: Option<String>,
165 pub metadata: std::collections::HashMap<String, serde_json::Value>,
167 pub path: PathBuf,
169 pub hash: String,
171 pub last_modified: Option<i64>,
173}
174
175impl From<&SkillDocument> for SkillSummary {
176 fn from(value: &SkillDocument) -> Self {
177 Self {
178 id: value.id.clone(),
179 name: value.name.clone(),
180 description: value.description.clone(),
181 version: value.version.clone(),
182 license: value.license.clone(),
183 compatibility: value.compatibility.clone(),
184 tags: value.tags.clone(),
185 allowed_tools: value.allowed_tools.clone(),
186 references: value.references.clone(),
187 trigger: value.trigger,
188 hint: value.hint.clone(),
189 metadata: value.metadata.clone(),
190 path: value.path.clone(),
191 hash: value.hash.clone(),
192 last_modified: value.last_modified,
193 }
194 }
195}
196
197#[derive(Debug, Clone, Default)]
199pub struct SkillIndex {
200 skills: Vec<SkillDocument>,
201}
202
203impl SkillIndex {
204 pub fn new(skills: Vec<SkillDocument>) -> Self {
205 Self { skills }
206 }
207
208 pub fn is_empty(&self) -> bool {
209 self.skills.is_empty()
210 }
211
212 pub fn len(&self) -> usize {
213 self.skills.len()
214 }
215
216 pub fn skills(&self) -> &[SkillDocument] {
218 &self.skills
219 }
220
221 pub fn summaries(&self) -> Vec<SkillSummary> {
223 self.skills.iter().map(SkillSummary::from).collect()
224 }
225
226 pub fn find_by_name(&self, name: &str) -> Option<&SkillDocument> {
228 self.skills.iter().find(|s| s.name == name)
229 }
230
231 pub fn find_by_id(&self, id: &str) -> Option<&SkillDocument> {
233 self.skills.iter().find(|s| s.id == id)
234 }
235}
236
237#[derive(Debug, Clone)]
239pub struct SelectionPolicy {
240 pub top_k: usize,
242 pub min_score: f32,
244 pub include_tags: Vec<String>,
246 pub exclude_tags: Vec<String>,
248}
249
250impl Default for SelectionPolicy {
251 fn default() -> Self {
252 Self { top_k: 1, min_score: 1.0, include_tags: Vec::new(), exclude_tags: Vec::new() }
253 }
254}
255
256#[derive(Debug, Clone, Serialize)]
258pub struct SkillMatch {
259 pub score: f32,
270 pub skill: SkillSummary,
272}