| 156 | } |
| 157 | |
| 158 | async fn execute(&self, args: &Value, _ctx: &ToolContext) -> Result<ToolOutput> { |
| 159 | let args = SearchSkillsArgs::from_tool_args(args)?; |
| 160 | let limit = args.limit.unwrap_or(5).clamp(1, 20); |
| 161 | let matches = self.skill_registry.search(&args.query, limit); |
| 162 | |
| 163 | if matches.is_empty() { |
| 164 | return Ok(ToolOutput::success( |
| 165 | "No matching skills found. Continue with the core tools.".to_string(), |
| 166 | )); |
| 167 | } |
| 168 | |
| 169 | let mut lines = vec![format!( |
| 170 | "Found {} matching skill(s). Invoke one with Skill using its skill_name.", |
| 171 | matches.len() |
| 172 | )]; |
| 173 | let metadata: Vec<_> = matches |
| 174 | .iter() |
| 175 | .map(|skill| { |
| 176 | let kind = format!("{:?}", skill.kind).to_lowercase(); |
| 177 | let allowed_tools = skill.allowed_tools.as_deref().unwrap_or("not specified"); |
| 178 | lines.push(format!( |
| 179 | "- {} ({kind}): {} Allowed tools: {}.", |
| 180 | skill.name, skill.description, allowed_tools |
| 181 | )); |
| 182 | serde_json::json!({ |
| 183 | "name": skill.name, |
| 184 | "description": skill.description, |
| 185 | "kind": kind, |
| 186 | "tags": skill.tags, |
| 187 | "allowed_tools": skill.allowed_tools, |
| 188 | }) |
| 189 | }) |
| 190 | .collect(); |
| 191 | |
| 192 | Ok(ToolOutput { |
| 193 | content: lines.join("\n"), |
| 194 | success: true, |
| 195 | metadata: Some(serde_json::json!({ "skills": metadata })), |
| 196 | images: Vec::new(), |
| 197 | error_kind: None, |
| 198 | }) |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | /// Skill tool - invokes skills with temporary permission grants |