use async_trait::async_trait;
use crate::tools::{Tool, ToolResult};
use super::executor::{SkillExecutor, DEFAULT_TIMEOUT};
use super::types::Skill;
pub struct SkillTool {
skill: Skill,
}
impl SkillTool {
pub fn new(skill: Skill) -> Self {
Self { skill }
}
fn build_description(&self) -> String {
let meta_desc = self.skill.metadata.description.clone();
let instructions = self.skill.instructions.trim();
if instructions.is_empty() {
return meta_desc;
}
const MAX_INSTRUCTIONS_LEN: usize = 2_000;
let truncated = if instructions.len() > MAX_INSTRUCTIONS_LEN {
format!(
"{}\n\n[... instructions truncated at {} chars ...]",
&instructions[..MAX_INSTRUCTIONS_LEN],
MAX_INSTRUCTIONS_LEN
)
} else {
instructions.to_string()
};
format!("{}\n\nInstructions:\n{}", meta_desc, truncated)
}
fn build_parameters_schema(&self) -> serde_json::Value {
if let Some(ref input_format) = self.skill.metadata.input_format {
input_format.schema.clone()
} else if self.skill.executable_path.is_some() {
serde_json::json!({
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "Input to pass to the skill executable"
}
}
})
} else {
serde_json::json!({
"type": "object",
"properties": {}
})
}
}
}
#[async_trait]
impl Tool for SkillTool {
fn name(&self) -> &str {
&self.skill.metadata.name
}
fn description(&self) -> &str {
&self.skill.metadata.description
}
fn parameters_schema(&self) -> serde_json::Value {
self.build_parameters_schema()
}
async fn execute(&self, args: serde_json::Value) -> ToolResult {
if let Some(ref exe_path) = self.skill.executable_path {
SkillExecutor::execute(exe_path, args, &self.skill.directory, DEFAULT_TIMEOUT).await
} else {
ToolResult::ok(serde_json::json!({
"instructions": self.skill.instructions.trim()
}))
}
}
fn to_definition(&self) -> llm_cascade::ToolDefinition {
llm_cascade::ToolDefinition {
name: self.name().to_owned(),
description: self.build_description(),
parameters: self.parameters_schema(),
}
}
}