xz_skill_core/traits/executor.rs
1use crate::error::SkillError;
2use crate::types::skill::ToolDefinition;
3use async_trait::async_trait;
4
5/// Output from a single tool execution.
6///
7/// Contains the textual result and an error flag to distinguish
8/// successful execution from tool-level failures.
9#[derive(Debug, Clone)]
10pub struct ToolOutput {
11 /// The output produced by the tool.
12 pub output: String,
13 /// Whether the tool encountered an error during execution.
14 pub is_error: bool,
15}
16
17/// Skill tool executor.
18///
19/// Executes a single named tool within a skill identified by `skill_id`.
20/// This trait does **not** run an LLM loop — it only handles one tool call
21/// at a time. Callers are responsible for any higher-level orchestration.
22///
23/// # Example
24///
25/// ```ignore
26/// use async_trait::async_trait;
27/// use xz_skill_core::traits::{SkillExecutor, ToolOutput};
28/// use xz_skill_core::SkillError;
29/// use xz_skill_core::types::skill::ToolDefinition;
30///
31/// struct EchoExecutor;
32///
33/// #[async_trait]
34/// impl SkillExecutor for EchoExecutor {
35/// async fn execute_tool(
36/// &self,
37/// _skill_id: &str,
38/// tool_name: &str,
39/// args: serde_json::Value,
40/// ) -> Result<ToolOutput, SkillError> {
41/// Ok(ToolOutput {
42/// output: format!("{}: {}", tool_name, args),
43/// is_error: false,
44/// })
45/// }
46///
47/// fn tools(&self, _skill_id: &str) -> Result<Vec<ToolDefinition>, SkillError> {
48/// Ok(vec![ToolDefinition {
49/// name: "echo".into(),
50/// description: "Echoes input".into(),
51/// input_schema: serde_json::json!({}),
52/// tool_type: crate::types::skill::ToolType::Builtin {
53/// handler: "echo".into(),
54/// },
55/// }])
56/// }
57/// }
58/// ```
59#[async_trait]
60pub trait SkillExecutor: Send + Sync {
61 /// Execute a single named tool with the given arguments.
62 ///
63 /// - `skill_id` — Identifies which skill provides the tool.
64 /// - `tool_name` — The name of the tool to invoke.
65 /// - `args` — JSON arguments passed to the tool.
66 async fn execute_tool(
67 &self,
68 skill_id: &str,
69 tool_name: &str,
70 args: serde_json::Value,
71 ) -> Result<ToolOutput, SkillError>;
72
73 /// List all tools available for a given skill.
74 ///
75 /// Returns the tool definitions so callers can discover capabilities
76 /// without executing anything.
77 fn tools(&self, skill_id: &str) -> Result<Vec<ToolDefinition>, SkillError>;
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 struct TestExecutor;
85
86 #[async_trait]
87 impl SkillExecutor for TestExecutor {
88 async fn execute_tool(
89 &self,
90 _skill_id: &str,
91 tool_name: &str,
92 args: serde_json::Value,
93 ) -> Result<ToolOutput, SkillError> {
94 Ok(ToolOutput { output: format!("{tool_name}: {args}"), is_error: false })
95 }
96
97 fn tools(&self, _skill_id: &str) -> Result<Vec<ToolDefinition>, SkillError> {
98 Ok(vec![])
99 }
100 }
101
102 #[tokio::test]
103 async fn test_execute_tool_returns_output() {
104 let exec = TestExecutor;
105 let result =
106 exec.execute_tool("test-skill", "echo", serde_json::json!("hello")).await.unwrap();
107 assert!(!result.is_error);
108 assert!(result.output.contains("echo"));
109 }
110
111 #[tokio::test]
112 async fn test_tools_returns_definitions() {
113 let exec = TestExecutor;
114 let tools = exec.tools("test-skill").unwrap();
115 assert!(tools.is_empty());
116 }
117}