1pub const SKILL_TOOL_NAME: &str = "Skill";
6
7use crate::skills::loader::{load_skills_from_dir, LoadedSkill};
8use crate::types::*;
9use std::collections::HashMap;
10use std::path::Path;
11use std::sync::{Mutex, OnceLock};
12
13static LOADED_SKILLS: OnceLock<Mutex<HashMap<String, LoadedSkill>>> = OnceLock::new();
15
16fn init_skills_map() -> Mutex<HashMap<String, LoadedSkill>> {
18 let mut skills = HashMap::new();
19 if let Ok(loaded) = load_skills_from_dir(Path::new("examples/skills")) {
20 for skill in loaded {
21 skills.insert(skill.metadata.name.clone(), skill);
22 }
23 }
24 Mutex::new(skills)
25}
26
27fn get_skills_map() -> &'static Mutex<HashMap<String, LoadedSkill>> {
29 LOADED_SKILLS.get_or_init(init_skills_map)
30}
31
32pub fn get_skill(name: &str) -> Option<LoadedSkill> {
34 let guard = get_skills_map().lock().ok()?;
35 guard.get(name).cloned()
36}
37
38pub fn get_all_skill_names() -> Vec<String> {
40 let guard = get_skills_map().lock().ok();
41 guard
42 .map(|g| g.keys().cloned().collect())
43 .unwrap_or_default()
44}
45
46pub fn register_skills_from_dir(dir: &Path) {
48 if dir.as_os_str().is_empty() {
50 return;
51 }
52 if let Ok(loaded) = load_skills_from_dir(dir) {
53 if let Ok(mut skills) = get_skills_map().lock() {
54 for skill in loaded {
55 skills.insert(skill.metadata.name.clone(), skill);
56 }
57 }
58 }
59}
60
61pub fn register_skill(skill: LoadedSkill) {
63 if let Ok(mut skills) = get_skills_map().lock() {
64 skills.insert(skill.metadata.name.clone(), skill);
65 }
66}
67
68pub fn register_skills(skills: Vec<LoadedSkill>) {
70 if let Ok(mut map) = get_skills_map().lock() {
71 for skill in skills {
72 map.insert(skill.metadata.name.clone(), skill);
73 }
74 }
75}
76
77pub struct SkillTool;
79
80impl SkillTool {
81 pub fn new() -> Self {
82 Self
83 }
84
85 pub fn input_schema(&self) -> ToolInputSchema {
86 ToolInputSchema {
87 schema_type: "object".to_string(),
88 properties: serde_json::json!({
89 "skill": {
90 "type": "string",
91 "description": "The name of the skill to invoke"
92 }
93 }),
94 required: Some(vec!["skill".to_string()]),
95 }
96 }
97
98 pub async fn execute(
99 &self,
100 input: serde_json::Value,
101 _context: &ToolContext,
102 ) -> Result<ToolResult, crate::error::AgentError> {
103 let skill_name = input["skill"].as_str().unwrap_or("");
104
105 if let Some(skill) = get_skill(skill_name) {
106 let content = format!(
109 "Skill '{}' loaded. Here is the skill content:\n\n{}\n\nYou can now use tools to complete the task.",
110 skill_name, skill.content
111 );
112
113 Ok(ToolResult {
114 result_type: "text".to_string(),
115 tool_use_id: "skill".to_string(),
116 content,
117 is_error: Some(false),
118 })
119 } else {
120 let available = get_all_skill_names();
122 Ok(ToolResult {
123 result_type: "text".to_string(),
124 tool_use_id: "skill".to_string(),
125 content: format!(
126 "Skill '{}' not found. Available skills: {:?}",
127 skill_name, available
128 ),
129 is_error: Some(true),
130 })
131 }
132 }
133}
134
135impl Default for SkillTool {
136 fn default() -> Self {
137 Self::new()
138 }
139}