use crate::spec_ai_core::agent::skills::SkillScanner;
use crate::spec_ai_core::tools::{Tool, ToolResult};
use anyhow::Result;
use async_trait::async_trait;
use serde_json::{Value, json};
use std::path::PathBuf;
pub struct ActivateSkillTool {
skills_dirs: Vec<PathBuf>,
}
impl ActivateSkillTool {
pub fn new(skills_dirs: Vec<PathBuf>) -> Self {
Self { skills_dirs }
}
}
#[async_trait]
impl Tool for ActivateSkillTool {
fn name(&self) -> &str {
"activate_skill"
}
fn description(&self) -> &str {
"Activates a specialized agent skill by name. Returns the skill's instructions wrapped in <activated_skill> tags. These provide specialized guidance for the current task. Use this when you identify a task that matches a skill's description. ONLY use names exactly as they appear in the <available_skills> section."
}
fn parameters(&self) -> Value {
json!({
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The exact name of the skill to activate"
}
},
"required": ["name"]
})
}
async fn execute(&self, args: Value) -> Result<ToolResult> {
let name = args["name"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("Missing 'name' argument"))?;
let scanner = SkillScanner::new(self.skills_dirs.clone());
let skills = scanner.scan();
let skill = skills.into_iter().find(|s| s.name == name);
match skill {
Some(skill) => {
let skill_content = std::fs::read_to_string(&skill.path)?;
let dir = skill.path.parent().unwrap_or(&skill.path);
let files: Vec<String> = walkdir::WalkDir::new(dir)
.max_depth(2)
.into_iter()
.filter_map(|e| e.ok())
.map(|e| {
e.path()
.strip_prefix(dir)
.unwrap_or(e.path())
.to_string_lossy()
.to_string()
})
.filter(|s| !s.is_empty())
.collect();
let mut output = format!(
"<activated_skill>\n<instructions>\n{}\n</instructions>\n",
skill_content
);
if !files.is_empty() {
output.push_str("<available_resources>\n");
for file in files {
output.push_str(&format!("- {}\n", file));
}
output.push_str("</available_resources>\n");
}
output.push_str("</activated_skill>");
Ok(ToolResult::success(output))
}
None => Ok(ToolResult::failure(format!("Skill '{}' not found", name))),
}
}
}