use crate::skill::{Skill, SkillDescriptor};
use argentor_core::{ArgentorError, ArgentorResult, ToolCall, ToolResult};
use argentor_security::Capability;
use async_trait::async_trait;
use std::path::Path;
use tracing::info;
use wasmtime::*;
use wasmtime_wasi::preview1::{self, WasiP1Ctx};
use wasmtime_wasi::WasiCtxBuilder;
pub struct WasmSkill {
descriptor: SkillDescriptor,
engine: Engine,
module: Module,
}
pub struct WasmSkillRuntime {
engine: Engine,
}
impl WasmSkillRuntime {
pub fn new() -> ArgentorResult<Self> {
let engine = Engine::default();
Ok(Self { engine })
}
pub fn load_skill(
&self,
path: &Path,
name: String,
description: String,
parameters_schema: serde_json::Value,
required_capabilities: Vec<Capability>,
requires_approval: bool,
) -> ArgentorResult<WasmSkill> {
info!(path = %path.display(), name = %name, "Loading WASM skill");
let module = Module::from_file(&self.engine, path)
.map_err(|e| ArgentorError::Skill(format!("Failed to load WASM module: {e}")))?;
Ok(WasmSkill {
descriptor: SkillDescriptor {
name,
description,
parameters_schema,
required_capabilities,
requires_approval,
},
engine: self.engine.clone(),
module,
})
}
}
impl Default for WasmSkillRuntime {
#[allow(clippy::expect_used)]
fn default() -> Self {
Self::new().expect("Failed to create WASM runtime")
}
}
#[async_trait]
impl Skill for WasmSkill {
fn descriptor(&self) -> &SkillDescriptor {
&self.descriptor
}
async fn execute(&self, call: ToolCall) -> ArgentorResult<ToolResult> {
let engine = self.engine.clone();
let module = self.module.clone();
let call_id = call.id.clone();
let input = serde_json::to_string(&call.arguments)
.map_err(|e| ArgentorError::Skill(format!("Failed to serialize args: {e}")))?;
let result = tokio::task::spawn_blocking(move || run_wasm_skill(&engine, &module, &input))
.await
.map_err(|e| ArgentorError::Skill(format!("WASM task panicked: {e}")))?;
match result {
Ok(output) => Ok(ToolResult::success(call_id, output)),
Err(e) => Ok(ToolResult::error(call_id, e.to_string())),
}
}
}
fn run_wasm_skill(engine: &Engine, module: &Module, input: &str) -> ArgentorResult<String> {
let mut linker = Linker::<WasiP1Ctx>::new(engine);
preview1::add_to_linker_sync(&mut linker, |t| t)
.map_err(|e| ArgentorError::Skill(format!("WASI linker error: {e}")))?;
let wasi = WasiCtxBuilder::new().inherit_stdio().arg(input).build_p1();
let mut store = Store::new(engine, wasi);
store.set_fuel(1_000_000).ok();
let instance = linker
.instantiate(&mut store, module)
.map_err(|e| ArgentorError::Skill(format!("WASM instantiation error: {e}")))?;
let start = instance
.get_typed_func::<(), ()>(&mut store, "_start")
.map_err(|e| ArgentorError::Skill(format!("No _start export: {e}")))?;
start
.call(&mut store, ())
.map_err(|e| ArgentorError::Skill(format!("WASM execution error: {e}")))?;
Ok("WASM skill executed successfully".to_string())
}