agent_code_lib/tools/
skill_tool.rs1use async_trait::async_trait;
8use serde_json::json;
9
10use super::{Tool, ToolContext, ToolResult};
11use crate::error::ToolError;
12
13pub struct SkillTool;
14
15#[async_trait]
16impl Tool for SkillTool {
17 fn name(&self) -> &'static str {
18 "Skill"
19 }
20
21 fn description(&self) -> &'static str {
22 "Invoke a user-defined skill by name. Skills are reusable \
23 workflows loaded from .agent/skills/ or ~/.config/agent-code/skills/."
24 }
25
26 fn input_schema(&self) -> serde_json::Value {
27 json!({
28 "type": "object",
29 "required": ["skill"],
30 "properties": {
31 "skill": {
32 "type": "string",
33 "description": "Name of the skill to invoke"
34 },
35 "args": {
36 "type": "string",
37 "description": "Optional arguments passed to the skill template"
38 }
39 }
40 })
41 }
42
43 fn is_read_only(&self) -> bool {
44 true }
46
47 fn is_concurrency_safe(&self) -> bool {
48 false
49 }
50
51 async fn call(
52 &self,
53 input: serde_json::Value,
54 ctx: &ToolContext,
55 ) -> Result<ToolResult, ToolError> {
56 let skill_name = input
57 .get("skill")
58 .and_then(|v| v.as_str())
59 .ok_or_else(|| ToolError::InvalidInput("'skill' is required".into()))?;
60
61 let args = input.get("args").and_then(|v| v.as_str());
62
63 let registry = crate::skills::SkillRegistry::load_all(Some(ctx.cwd.as_path()));
64
65 match registry.find(skill_name) {
66 Some(skill) => {
67 let expanded = skill.expand(args);
68 Ok(ToolResult::success(format!(
69 "Skill '{}' loaded. Execute the following instructions:\n\n{}",
70 skill_name, expanded
71 )))
72 }
73 None => {
74 let available: Vec<&str> = registry.all().iter().map(|s| s.name.as_str()).collect();
75 Err(ToolError::InvalidInput(format!(
76 "Skill '{}' not found. Available: {}",
77 skill_name,
78 if available.is_empty() {
79 "none".to_string()
80 } else {
81 available.join(", ")
82 }
83 )))
84 }
85 }
86 }
87}