1use crate::types::{Layer3Result, ToolRequest, ToolResponse};
6use async_trait::async_trait;
7use serde::{Deserialize, Serialize};
8
9#[async_trait]
13pub trait Skill: Send + Sync {
14 fn name(&self) -> &str;
16
17 fn description(&self) -> &str;
19
20 fn version(&self) -> &str {
22 "1.0.0"
23 }
24
25 async fn execute(&self, input: SkillInput) -> Layer3Result<SkillOutput>;
27
28 fn required_tools(&self) -> Vec<String>;
30
31 fn required_permissions(&self) -> Vec<String>;
33
34 fn config_schema(&self) -> serde_json::Value;
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct SkillInput {
41 pub params: serde_json::Value,
43 pub context: SkillContext,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct SkillOutput {
50 pub result: serde_json::Value,
52 pub status: SkillStatus,
54 pub tool_calls: Vec<ToolRequest>,
56 pub tool_results: Vec<ToolResponse>,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
62#[serde(rename_all = "snake_case")]
63pub enum SkillStatus {
64 Success,
65 Failed,
66 Pending,
67 NeedsApproval,
68 ToolCalling,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct SkillContext {
74 pub session_id: String,
76 pub user_id: Option<String>,
78 pub working_dir: String,
80 pub env_vars: std::collections::HashMap<String, String>,
82}
83
84impl Default for SkillContext {
85 fn default() -> Self {
86 Self {
87 session_id: String::new(),
88 user_id: None,
89 working_dir: ".".to_string(),
90 env_vars: std::collections::HashMap::new(),
91 }
92 }
93}
94
95pub struct SkillRegistry {
97 skills: std::collections::HashMap<String, Box<dyn Skill>>,
98}
99
100impl SkillRegistry {
101 pub fn new() -> Self {
102 Self {
103 skills: std::collections::HashMap::new(),
104 }
105 }
106
107 pub fn register(&mut self, skill: Box<dyn Skill>) {
108 self.skills.insert(skill.name().to_string(), skill);
109 }
110
111 pub fn get(&self, name: &str) -> Option<&dyn Skill> {
112 self.skills.get(name).map(|s| s.as_ref())
113 }
114
115 pub fn list(&self) -> Vec<&dyn Skill> {
116 self.skills.values().map(|s| s.as_ref()).collect()
117 }
118}
119
120impl Default for SkillRegistry {
121 fn default() -> Self {
122 Self::new()
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn test_skill_context_default() {
132 let ctx = SkillContext::default();
133 assert_eq!(ctx.working_dir, ".");
134 }
135
136 #[test]
137 fn test_skill_registry() {
138 let registry = SkillRegistry::new();
139 assert!(registry.list().is_empty());
140 }
141}