160 lines
4.7 KiB
Rust
160 lines
4.7 KiB
Rust
use std::sync::Arc;
|
|
|
|
use async_trait::async_trait;
|
|
use serde_json::json;
|
|
|
|
use crate::skills::{Skill, SkillsLoader};
|
|
use crate::tools::traits::{Tool, ToolResult};
|
|
|
|
pub struct GetSkillTool {
|
|
skills_loader: Arc<SkillsLoader>,
|
|
}
|
|
|
|
impl GetSkillTool {
|
|
pub fn new(skills_loader: Arc<SkillsLoader>) -> Self {
|
|
Self { skills_loader }
|
|
}
|
|
|
|
fn format_skill(&self, skill: &Skill) -> String {
|
|
let mut result = format!("# Skill: {}\n\n{}", skill.name, skill.description);
|
|
|
|
if let Some(path) = &skill.path {
|
|
result.push_str(&format!(
|
|
"\n\n**Skill Root Directory:** `{}`\n\nAll files and references in this skill are relative to this directory.",
|
|
path.to_string_lossy()
|
|
));
|
|
}
|
|
|
|
result.push_str(&format!("\n\n---\n\n{}", skill.content));
|
|
result
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Tool for GetSkillTool {
|
|
fn name(&self) -> &str {
|
|
"get_skill"
|
|
}
|
|
|
|
fn description(&self) -> &str {
|
|
"Get complete content and guidance for a specified skill. Use this when you need detailed instructions for a specific type of task."
|
|
}
|
|
|
|
fn parameters_schema(&self) -> serde_json::Value {
|
|
json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"skill_name": {
|
|
"type": "string",
|
|
"description": "Name of the skill to retrieve"
|
|
}
|
|
},
|
|
"required": ["skill_name"]
|
|
})
|
|
}
|
|
|
|
fn read_only(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
|
|
let skill_name = match args.get("skill_name").and_then(|v| v.as_str()) {
|
|
Some(name) => name,
|
|
None => {
|
|
return Ok(ToolResult {
|
|
success: false,
|
|
output: String::new(),
|
|
error: Some("Missing required parameter: skill_name".to_string()),
|
|
});
|
|
}
|
|
};
|
|
|
|
match self.skills_loader.get_skill(skill_name) {
|
|
Some(skill) => {
|
|
let formatted = self.format_skill(&skill);
|
|
Ok(ToolResult {
|
|
success: true,
|
|
output: formatted,
|
|
error: None,
|
|
})
|
|
}
|
|
None => {
|
|
let available = self.skills_loader.list_skills();
|
|
let available_str = if available.is_empty() {
|
|
"No skills available".to_string()
|
|
} else {
|
|
available
|
|
.iter()
|
|
.map(|(name, _)| name.as_str())
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
};
|
|
Ok(ToolResult {
|
|
success: false,
|
|
output: String::new(),
|
|
error: Some(format!(
|
|
"Skill '{}' not found. Available skills: {}",
|
|
skill_name, available_str
|
|
)),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use tempfile::tempdir;
|
|
use std::fs::File;
|
|
use std::io::Write;
|
|
use std::path::PathBuf;
|
|
|
|
#[tokio::test]
|
|
async fn test_get_existing_skill() {
|
|
let temp_dir = tempdir().unwrap();
|
|
|
|
let skill_dir = temp_dir.path().join("test-skill");
|
|
std::fs::create_dir(&skill_dir).unwrap();
|
|
|
|
let mut skill_file = File::create(skill_dir.join("SKILL.md")).unwrap();
|
|
writeln!(skill_file, "---").unwrap();
|
|
writeln!(skill_file, "name: test-skill").unwrap();
|
|
writeln!(skill_file, "description: A test skill").unwrap();
|
|
writeln!(skill_file, "---").unwrap();
|
|
writeln!(skill_file, "# Test Skill").unwrap();
|
|
writeln!(skill_file, "This is the test content.").unwrap();
|
|
|
|
let mut loader = SkillsLoader::new_for_testing(
|
|
temp_dir.path().to_path_buf(),
|
|
PathBuf::from("/nonexistent"),
|
|
);
|
|
loader.load_skills();
|
|
|
|
let tool = GetSkillTool::new(Arc::new(loader));
|
|
|
|
let result = tool
|
|
.execute(json!({ "skill_name": "test-skill" }))
|
|
.await
|
|
.unwrap();
|
|
|
|
assert!(result.success);
|
|
assert!(result.output.contains("test-skill"));
|
|
assert!(result.output.contains("test content"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_get_nonexistent_skill() {
|
|
let loader = SkillsLoader::new();
|
|
let tool = GetSkillTool::new(Arc::new(loader));
|
|
|
|
let result = tool
|
|
.execute(json!({ "skill_name": "nonexistent" }))
|
|
.await
|
|
.unwrap();
|
|
|
|
assert!(!result.success);
|
|
assert!(result.error.is_some());
|
|
}
|
|
}
|