use adk_core::Tool;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::Arc;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct SkillFrontmatter {
pub name: String,
pub description: String,
pub version: Option<String>,
pub license: Option<String>,
pub compatibility: Option<String>,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default, rename = "allowed-tools")]
pub allowed_tools: Vec<String>,
#[serde(default)]
pub references: Vec<String>,
pub trigger: Option<bool>,
pub hint: Option<String>,
#[serde(default)]
pub metadata: std::collections::HashMap<String, serde_json::Value>,
#[serde(default)]
pub triggers: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct ParsedSkill {
pub name: String,
pub description: String,
pub version: Option<String>,
pub license: Option<String>,
pub compatibility: Option<String>,
pub tags: Vec<String>,
pub allowed_tools: Vec<String>,
pub references: Vec<String>,
pub trigger: bool,
pub hint: Option<String>,
pub metadata: std::collections::HashMap<String, serde_json::Value>,
pub triggers: Vec<String>,
pub body: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct SkillDocument {
pub id: String,
pub name: String,
pub description: String,
pub version: Option<String>,
pub license: Option<String>,
pub compatibility: Option<String>,
pub tags: Vec<String>,
pub allowed_tools: Vec<String>,
pub references: Vec<String>,
pub trigger: bool,
pub hint: Option<String>,
pub metadata: std::collections::HashMap<String, serde_json::Value>,
pub body: String,
pub path: PathBuf,
pub hash: String,
pub last_modified: Option<i64>,
pub triggers: Vec<String>,
}
impl SkillDocument {
pub fn engineer_instruction(&self, max_chars: usize, active_tools: &[Arc<dyn Tool>]) -> String {
let mut body = self.body.clone();
if body.chars().count() > max_chars {
body = body.chars().take(max_chars).collect();
body.push_str("\n[... truncated]");
}
let mut parts = Vec::new();
parts.push(format!("[skill:{}]", self.name));
parts.push(format!("# {}\n{}", self.name, self.description));
if !active_tools.is_empty() {
let names: Vec<_> = active_tools.iter().map(|t: &Arc<dyn Tool>| t.name()).collect();
parts.push(format!("You have access to the following tools: {}.", names.join(", ")));
}
parts.push(format!("## Instructions\n{}", body));
parts.push("[/skill]".to_string());
parts.join("\n\n")
}
pub fn engineer_prompt_block(&self, max_chars: usize) -> String {
let mut body = self.body.clone();
if body.chars().count() > max_chars {
body = body.chars().take(max_chars).collect();
}
format!("[skill:{}]\n{}\n[/skill]", self.name, body)
}
}
#[derive(Debug, Clone, Serialize)]
pub struct SkillSummary {
pub id: String,
pub name: String,
pub description: String,
pub version: Option<String>,
pub license: Option<String>,
pub compatibility: Option<String>,
pub tags: Vec<String>,
pub allowed_tools: Vec<String>,
pub references: Vec<String>,
pub trigger: bool,
pub hint: Option<String>,
pub metadata: std::collections::HashMap<String, serde_json::Value>,
pub path: PathBuf,
pub hash: String,
pub last_modified: Option<i64>,
pub triggers: Vec<String>,
}
impl From<&SkillDocument> for SkillSummary {
fn from(value: &SkillDocument) -> Self {
Self {
id: value.id.clone(),
name: value.name.clone(),
description: value.description.clone(),
version: value.version.clone(),
license: value.license.clone(),
compatibility: value.compatibility.clone(),
tags: value.tags.clone(),
allowed_tools: value.allowed_tools.clone(),
references: value.references.clone(),
trigger: value.trigger,
hint: value.hint.clone(),
metadata: value.metadata.clone(),
path: value.path.clone(),
hash: value.hash.clone(),
last_modified: value.last_modified,
triggers: value.triggers.clone(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SkillIndex {
skills: Vec<SkillDocument>,
}
impl SkillIndex {
pub fn new(skills: Vec<SkillDocument>) -> Self {
Self { skills }
}
pub fn is_empty(&self) -> bool {
self.skills.is_empty()
}
pub fn len(&self) -> usize {
self.skills.len()
}
pub fn skills(&self) -> &[SkillDocument] {
&self.skills
}
pub fn summaries(&self) -> Vec<SkillSummary> {
self.skills.iter().map(SkillSummary::from).collect()
}
pub fn find_by_name(&self, name: &str) -> Option<&SkillDocument> {
self.skills.iter().find(|s| s.name == name)
}
pub fn find_by_id(&self, id: &str) -> Option<&SkillDocument> {
self.skills.iter().find(|s| s.id == id)
}
}
#[derive(Debug, Clone)]
pub struct SelectionPolicy {
pub top_k: usize,
pub min_score: f32,
pub include_tags: Vec<String>,
pub exclude_tags: Vec<String>,
}
impl Default for SelectionPolicy {
fn default() -> Self {
Self { top_k: 1, min_score: 1.0, include_tags: Vec::new(), exclude_tags: Vec::new() }
}
}
#[derive(Debug, Clone, Serialize)]
pub struct SkillMatch {
pub score: f32,
pub skill: SkillSummary,
}