1use crate::context::compact::{InvokedSkillsMap, record_skill_invocation};
2use crate::infra::skill::{Skill, resolve_skill_content};
3use crate::tools::{PlanDecision, Tool, ToolResult, parse_tool_args, schema_to_tool_params};
4use schemars::JsonSchema;
5use serde::Deserialize;
6use serde_json::Value;
7use std::borrow::Cow;
8use std::sync::{Arc, atomic::AtomicBool};
9
10#[derive(Deserialize, JsonSchema)]
12struct LoadSkillParams {
13 name: String,
15 #[serde(default)]
17 arguments: Option<String>,
18}
19
20#[derive(Debug)]
24pub struct LoadSkillTool {
25 pub skills: Vec<Skill>,
27 pub invoked_skills: InvokedSkillsMap,
29}
30
31impl LoadSkillTool {
32 pub const NAME: &'static str = "LoadSkill";
33}
34
35impl Tool for LoadSkillTool {
36 fn name(&self) -> &str {
37 Self::NAME
38 }
39
40 fn description(&self) -> Cow<'_, str> {
41 "Load the full content of a specified skill into context for more information, helping you better complete the task. Check the skills list for available skill names and directory paths.".into()
42 }
43
44 fn parameters_schema(&self) -> Value {
45 schema_to_tool_params::<LoadSkillParams>()
46 }
47
48 fn execute(&self, arguments: &str, _cancelled: &Arc<AtomicBool>) -> ToolResult {
49 let params: LoadSkillParams = match parse_tool_args(arguments) {
50 Ok(p) => p,
51 Err(e) => return e,
52 };
53
54 if params.name.is_empty() {
55 return ToolResult {
56 output: "参数缺少 name 字段".to_string(),
57 is_error: true,
58 images: vec![],
59 plan_decision: PlanDecision::None,
60 };
61 }
62
63 let args_str = params.arguments.as_deref().unwrap_or("");
64
65 match self
66 .skills
67 .iter()
68 .find(|s| s.frontmatter.name == params.name)
69 {
70 Some(skill) => {
71 let content = resolve_skill_content(skill);
72 let resolved = content.replace("$ARGUMENTS", args_str);
73 record_skill_invocation(
75 &self.invoked_skills,
76 skill.frontmatter.name.clone(),
77 skill.dir_path.display().to_string(),
78 resolved.clone(),
79 );
80 ToolResult {
81 output: resolved,
82 is_error: false,
83 images: vec![],
84 plan_decision: PlanDecision::None,
85 }
86 }
87 None => {
88 let available: Vec<&str> = self
89 .skills
90 .iter()
91 .map(|s| s.frontmatter.name.as_str())
92 .collect();
93 ToolResult {
94 output: format!(
95 "未找到技能 '{}'。可用技能: {}",
96 params.name,
97 available.join(", ")
98 ),
99 is_error: true,
100 images: vec![],
101 plan_decision: PlanDecision::None,
102 }
103 }
104 }
105 }
106
107 fn requires_confirmation(&self) -> bool {
108 false
109 }
110}