ai-agent-sdk 0.4.0

Idiomatic agent sdk inspired by the claude code source leak
Documentation
//! Skill tool - invoke external skills
//!
//! Provides a tool for the agent to invoke external skills.

use crate::types::*;
use crate::skills::loader::{load_skills_from_dir, LoadedSkill};
use std::path::Path;
use std::sync::{OnceLock, Mutex};
use std::collections::HashMap;

/// Global skill registry with Mutex for interior mutability
static LOADED_SKILLS: OnceLock<Mutex<HashMap<String, LoadedSkill>>> = OnceLock::new();

/// Initialize the skills map (lazy initialization)
fn init_skills_map() -> Mutex<HashMap<String, LoadedSkill>> {
    let mut skills = HashMap::new();
    if let Ok(loaded) = load_skills_from_dir(Path::new("examples/skills")) {
        for skill in loaded {
            skills.insert(skill.metadata.name.clone(), skill);
        }
    }
    Mutex::new(skills)
}

/// Get skills map
fn get_skills_map() -> &'static Mutex<HashMap<String, LoadedSkill>> {
    LOADED_SKILLS.get_or_init(init_skills_map)
}

/// Get a skill by name
pub fn get_skill(name: &str) -> Option<LoadedSkill> {
    let guard = get_skills_map().lock().ok()?;
    guard.get(name).cloned()
}

/// Get all skill names
pub fn get_all_skill_names() -> Vec<String> {
    let guard = get_skills_map().lock().ok();
    guard.map(|g| g.keys().cloned().collect()).unwrap_or_default()
}

/// Register skills from a directory
pub fn register_skills_from_dir(dir: &Path) {
    // Skip empty path - used as signal for plugin skills registration
    if dir.as_os_str().is_empty() {
        return;
    }
    if let Ok(loaded) = load_skills_from_dir(dir) {
        if let Ok(mut skills) = get_skills_map().lock() {
            for skill in loaded {
                skills.insert(skill.metadata.name.clone(), skill);
            }
        }
    }
}

/// Register a single skill directly (used for plugin skills)
pub fn register_skill(skill: LoadedSkill) {
    if let Ok(mut skills) = get_skills_map().lock() {
        skills.insert(skill.metadata.name.clone(), skill);
    }
}

/// Register multiple skills directly (used for plugin skills)
pub fn register_skills(skills: Vec<LoadedSkill>) {
    if let Ok(mut map) = get_skills_map().lock() {
        for skill in skills {
            map.insert(skill.metadata.name.clone(), skill);
        }
    }
}

/// Skill tool - invoke a skill by name
pub struct SkillTool;

impl SkillTool {
    pub fn new() -> Self {
        Self
    }

    pub fn input_schema(&self) -> ToolInputSchema {
        ToolInputSchema {
            schema_type: "object".to_string(),
            properties: serde_json::json!({
                "skill": {
                    "type": "string",
                    "description": "The name of the skill to invoke"
                }
            }),
            required: Some(vec!["skill".to_string()]),
        }
    }

    pub async fn execute(&self, input: serde_json::Value, _context: &ToolContext) -> Result<ToolResult, crate::error::AgentError> {
        let skill_name = input["skill"].as_str().unwrap_or("");

        if let Some(skill) = get_skill(skill_name) {
            // Return skill content as a user message so the model can read it and use tools
            // This matches the TypeScript implementation behavior
            let content = format!(
                "Skill '{}' loaded. Here is the skill content:\n\n{}\n\nYou can now use tools to complete the task.",
                skill_name, skill.content
            );

            Ok(ToolResult {
                result_type: "text".to_string(),
                tool_use_id: "skill".to_string(),
                content,
                is_error: Some(false),
            })
        } else {
            // List available skills
            let available = get_all_skill_names();
            Ok(ToolResult {
                result_type: "text".to_string(),
                tool_use_id: "skill".to_string(),
                content: format!("Skill '{}' not found. Available skills: {:?}", skill_name, available),
                is_error: Some(true),
            })
        }
    }
}

impl Default for SkillTool {
    fn default() -> Self {
        Self::new()
    }
}