use std::sync::{OnceLock, Mutex};
use crate::AgentError;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type")]
pub enum ContentBlock {
#[serde(rename = "text")]
Text { text: String },
#[serde(rename = "image")]
Image { source: ImageSource },
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ImageSource {
#[serde(rename = "type")]
pub source_type: String,
pub data: String,
pub media_type: String,
}
#[derive(Debug, Clone)]
pub struct SkillContext {
pub cwd: String,
}
pub type GetPromptForCommandFn = fn(args: &str, context: &SkillContext) -> Result<Vec<ContentBlock>, AgentError>;
static BUNDLED_SKILLS: OnceLock<Mutex<Vec<BundledSkill>>> = OnceLock::new();
#[derive(Debug, Clone)]
pub struct BundledSkill {
pub name: String,
pub description: String,
pub aliases: Vec<String>,
pub has_user_specified_description: bool,
pub allowed_tools: Vec<String>,
pub argument_hint: Option<String>,
pub when_to_use: Option<String>,
pub model: Option<String>,
pub disable_model_invocation: bool,
pub user_invocable: bool,
pub content_length: usize,
pub source: String,
pub loaded_from: String,
pub skill_root: Option<String>,
pub context: Option<String>,
pub agent: Option<String>,
pub is_enabled: Option<fn() -> bool>,
pub is_hidden: bool,
pub progress_message: String,
pub get_prompt_for_command: GetPromptForCommandFn,
}
pub struct BundledSkillDefinition {
pub name: String,
pub description: String,
pub aliases: Option<Vec<String>>,
pub when_to_use: Option<String>,
pub argument_hint: Option<String>,
pub allowed_tools: Option<Vec<String>>,
pub model: Option<String>,
pub disable_model_invocation: Option<bool>,
pub user_invocable: Option<bool>,
pub is_enabled: Option<fn() -> bool>,
pub context: Option<String>,
pub agent: Option<String>,
pub files: Option<std::collections::HashMap<String, String>>,
pub get_prompt_for_command: GetPromptForCommandFn,
}
pub fn register_bundled_skill(definition: BundledSkillDefinition) -> Result<(), AgentError> {
let mutex = BUNDLED_SKILLS.get_or_init(|| Mutex::new(Vec::new()));
let mut skills = mutex.lock().map_err(|e| AgentError::Internal(format!("Lock error: {}", e)))?;
let get_prompt_for_command = definition.get_prompt_for_command;
let skill = BundledSkill {
name: definition.name.clone(),
description: definition.description.clone(),
aliases: definition.aliases.unwrap_or_default(),
has_user_specified_description: true,
allowed_tools: definition.allowed_tools.unwrap_or_default(),
argument_hint: definition.argument_hint,
when_to_use: definition.when_to_use,
model: definition.model,
disable_model_invocation: definition.disable_model_invocation.unwrap_or(false),
user_invocable: definition.user_invocable.unwrap_or(true),
content_length: 0,
source: "bundled".to_string(),
loaded_from: "bundled".to_string(),
skill_root: None,
context: definition.context,
agent: definition.agent,
is_enabled: definition.is_enabled,
is_hidden: !(definition.user_invocable.unwrap_or(true)),
progress_message: "running".to_string(),
get_prompt_for_command,
};
skills.push(skill);
Ok(())
}
pub fn get_bundled_skills() -> Vec<BundledSkill> {
match BUNDLED_SKILLS.get() {
Some(mutex) => {
match mutex.lock() {
Ok(skills) => skills.clone(),
Err(_) => Vec::new(),
}
}
None => Vec::new(),
}
}
pub fn clear_bundled_skills() {
if let Some(mutex) = BUNDLED_SKILLS.get() {
if let Ok(mut skills) = mutex.lock() {
skills.clear();
}
}
}
pub fn invoke_skill(name: &str, args: &str) -> Result<Vec<ContentBlock>, AgentError> {
let skills = get_bundled_skills();
let skill = skills.iter()
.find(|s| s.name == name || s.aliases.iter().any(|a| a == name))
.ok_or_else(|| AgentError::Skill(format!("Skill '{}' not found", name)))?;
let context = SkillContext {
cwd: std::env::current_dir()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| ".".to_string()),
};
(skill.get_prompt_for_command)(args, &context)
}