use std::sync::Arc;
use agent_contracts::runtime::runtime_view::RuntimeView;
use agent_contracts::skill::registry::SkillContext;
use agent_contracts::tool::{ToolExecutor, ToolSpecView};
use agent_types::tool::call_types::FinalToolCall;
use agent_types::tool::execution_types::{RawToolOutcome, ToolExecutionError, ToolExecutorOutput};
use async_trait::async_trait;
use super::input::SkillToolInput;
use super::spec::SkillToolSpec;
use super::substitute::substitute_arguments;
pub struct SkillToolExecutor {
spec: Arc<SkillToolSpec>,
}
impl SkillToolExecutor {
pub fn new(spec: Arc<SkillToolSpec>) -> Self {
Self { spec }
}
}
#[async_trait]
impl ToolExecutor for SkillToolExecutor {
fn spec(&self) -> &dyn ToolSpecView {
self.spec.as_ref()
}
async fn invoke(
&self,
call: &FinalToolCall,
runtime: &dyn RuntimeView,
) -> Result<ToolExecutorOutput, ToolExecutionError> {
let input: SkillToolInput = serde_json::from_value(call.input.clone()).map_err(|e| {
ToolExecutionError::ExecutionFailed {
message: format!("failed to parse skill tool input: {}", e),
}
})?;
let registry = runtime
.skill_registry()
.ok_or(ToolExecutionError::ExecutionFailed {
message: "skill registry not available".into(),
})?;
let spec = registry
.get_skill(&input.skill)
.ok_or(ToolExecutionError::ExecutionFailed {
message: format!("skill '{}' not found", input.skill),
})?;
if spec.disable_model_invocation() {
return Ok(ToolExecutorOutput::Completed {
raw_outcome: RawToolOutcome::Error {
message: format!(
"skill '{}' can only be invoked by user, not by model",
input.skill
),
},
});
}
let prompt = substitute_arguments(spec.full_prompt(), &input.args, spec.arguments());
match spec.context() {
SkillContext::Inline => Ok(ToolExecutorOutput::Completed {
raw_outcome: RawToolOutcome::Success { output: prompt },
}),
SkillContext::Fork => {
Ok(ToolExecutorOutput::Completed {
raw_outcome: RawToolOutcome::Error {
message: "fork context is not yet supported".into(),
},
})
}
}
}
}