//! Skill definition and types use serde::{Deserialize, Serialize}; use serde_json::Value; use zclaw_types::{SkillId, Result}; /// Skill manifest definition #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SkillManifest { /// Unique skill identifier pub id: SkillId, /// Human-readable name pub name: String, /// Skill description pub description: String, /// Skill version pub version: String, /// Skill author #[serde(default)] pub author: Option, /// Execution mode pub mode: SkillMode, /// Required capabilities #[serde(default)] pub capabilities: Vec, /// Input schema (JSON Schema) #[serde(default)] pub input_schema: Option, /// Output schema (JSON Schema) #[serde(default)] pub output_schema: Option, /// Tags for categorization #[serde(default)] pub tags: Vec, /// Category for skill grouping (e.g., "开发工程", "数据分析") /// If not specified, will be auto-detected from skill ID #[serde(default)] pub category: Option, /// Trigger words for skill activation #[serde(default)] pub triggers: Vec, /// Whether the skill is enabled #[serde(default = "default_enabled")] pub enabled: bool, } fn default_enabled() -> bool { true } /// Skill execution mode #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum SkillMode { /// Prompt-only skill (no code execution) PromptOnly, /// Python script execution Python, /// Shell command execution Shell, /// WebAssembly execution (not yet implemented, falls back to PromptOnly) Wasm, /// Native Rust execution (not yet implemented, falls back to PromptOnly) Native, } /// Skill execution context #[derive(Debug, Clone)] pub struct SkillContext { /// Agent ID executing the skill pub agent_id: String, /// Session ID for the execution pub session_id: String, /// Working directory for execution pub working_dir: Option, /// Environment variables pub env: std::collections::HashMap, /// Timeout in seconds pub timeout_secs: u64, /// Whether to allow network access pub network_allowed: bool, /// Whether to allow file system access pub file_access_allowed: bool, } impl Default for SkillContext { fn default() -> Self { Self { agent_id: String::new(), session_id: String::new(), working_dir: None, env: std::collections::HashMap::new(), timeout_secs: 60, network_allowed: false, file_access_allowed: false, } } } /// Skill execution result #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SkillResult { /// Whether execution succeeded pub success: bool, /// Output data pub output: Value, /// Error message if failed #[serde(default)] pub error: Option, /// Execution duration in milliseconds #[serde(default)] pub duration_ms: Option, /// Token usage if LLM was #[serde(default)] pub tokens_used: Option, } impl SkillResult { pub fn success(output: Value) -> Self { Self { success: true, output, error: None, duration_ms: None, tokens_used: None, } } pub fn error(message: impl Into) -> Self { Self { success: false, output: Value::Null, error: Some(message.into()), duration_ms: None, tokens_used: None, } } } /// Skill definition with execution logic #[async_trait::async_trait] pub trait Skill: Send + Sync { /// Get the skill manifest fn manifest(&self) -> &SkillManifest; /// Execute the skill with given input async fn execute(&self, context: &SkillContext, input: Value) -> Result; /// Validate input against schema fn validate_input(&self, input: &Value) -> Result<()> { // Basic validation - can be overridden if input.is_null() { return Err(zclaw_types::ZclawError::InvalidInput("Input cannot be null".into())); } Ok(()) } }