use crate::types::{Layer3Result, ToolRequest, ToolResponse};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
#[async_trait]
pub trait Skill: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn version(&self) -> &str {
"1.0.0"
}
async fn execute(&self, input: SkillInput) -> Layer3Result<SkillOutput>;
fn required_tools(&self) -> Vec<String>;
fn required_permissions(&self) -> Vec<String>;
fn config_schema(&self) -> serde_json::Value;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillInput {
pub params: serde_json::Value,
pub context: SkillContext,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillOutput {
pub result: serde_json::Value,
pub status: SkillStatus,
pub tool_calls: Vec<ToolRequest>,
pub tool_results: Vec<ToolResponse>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SkillStatus {
Success,
Failed,
Pending,
NeedsApproval,
ToolCalling,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillContext {
pub session_id: String,
pub user_id: Option<String>,
pub working_dir: String,
pub env_vars: std::collections::HashMap<String, String>,
}
impl Default for SkillContext {
fn default() -> Self {
Self {
session_id: String::new(),
user_id: None,
working_dir: ".".to_string(),
env_vars: std::collections::HashMap::new(),
}
}
}
pub struct SkillRegistry {
skills: std::collections::HashMap<String, Box<dyn Skill>>,
}
impl SkillRegistry {
pub fn new() -> Self {
Self {
skills: std::collections::HashMap::new(),
}
}
pub fn register(&mut self, skill: Box<dyn Skill>) {
self.skills.insert(skill.name().to_string(), skill);
}
pub fn get(&self, name: &str) -> Option<&dyn Skill> {
self.skills.get(name).map(|s| s.as_ref())
}
pub fn list(&self) -> Vec<&dyn Skill> {
self.skills.values().map(|s| s.as_ref()).collect()
}
}
impl Default for SkillRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_skill_context_default() {
let ctx = SkillContext::default();
assert_eq!(ctx.working_dir, ".");
}
#[test]
fn test_skill_registry() {
let registry = SkillRegistry::new();
assert!(registry.list().is_empty());
}
}