Search discoverable instruction/tool skills by name, tag, description, or content.
(&self, query: &str, limit: usize)
| 289 | |
| 290 | /// Search discoverable instruction/tool skills by name, tag, description, or content. |
| 291 | pub fn search(&self, query: &str, limit: usize) -> Vec<Arc<Skill>> { |
| 292 | let skills = self.skills.read().unwrap(); |
| 293 | let query_lower = query.to_lowercase(); |
| 294 | let query_tokens: Vec<&str> = query_lower |
| 295 | .split_whitespace() |
| 296 | .map(|w| w.trim_matches(|c: char| !c.is_alphanumeric())) |
| 297 | .filter(|w| w.len() >= 2) |
| 298 | .collect(); |
| 299 | |
| 300 | let mut scored: Vec<(u32, String, Arc<Skill>)> = skills |
| 301 | .values() |
| 302 | .filter(|s| Self::is_discoverable_skill(s)) |
| 303 | .filter_map(|skill| { |
| 304 | let score = Self::skill_search_score(skill, &query_lower, &query_tokens); |
| 305 | if score == 0 { |
| 306 | None |
| 307 | } else { |
| 308 | Some((score, skill.name.clone(), Arc::clone(skill))) |
| 309 | } |
| 310 | }) |
| 311 | .collect(); |
| 312 | |
| 313 | scored.sort_by(|a, b| b.0.cmp(&a.0).then_with(|| a.1.cmp(&b.1))); |
| 314 | scored |
| 315 | .into_iter() |
| 316 | .take(limit.max(1)) |
| 317 | .map(|(_, _, skill)| skill) |
| 318 | .collect() |
| 319 | } |
| 320 | |
| 321 | fn is_discoverable_skill(skill: &Skill) -> bool { |
| 322 | skill.kind == super::SkillKind::Instruction || skill.kind == super::SkillKind::Tool |