use anyhow::{Context, Result};
use std::path::PathBuf;
use std::sync::Arc;
use wasmtime::{component::Component, Config, Engine};
pub struct SkillEngine {
engine: Arc<Engine>,
cache_dir: PathBuf,
}
impl SkillEngine {
pub fn new() -> Result<Self> {
let mut config = Config::new();
config.wasm_component_model(true);
config.async_support(true);
config.cranelift_opt_level(wasmtime::OptLevel::Speed);
let cache_dir = dirs::cache_dir()
.context("Failed to get cache directory")?
.join("skill-engine")
.join("wasmtime-cache");
std::fs::create_dir_all(&cache_dir)?;
config.cache_config_load_default()?;
config.max_wasm_stack(1024 * 1024);
config.debug_info(true);
let engine = Engine::new(&config)?;
tracing::info!(
"Initialized SkillEngine with cache at: {}",
cache_dir.display()
);
Ok(Self {
engine: Arc::new(engine),
cache_dir,
})
}
pub fn wasmtime_engine(&self) -> &Engine {
&self.engine
}
pub async fn load_component(&self, path: &std::path::Path) -> Result<Component> {
tracing::debug!("Loading component from: {}", path.display());
let component = Component::from_file(&self.engine, path)
.with_context(|| format!("Failed to load component from {}", path.display()))?;
tracing::info!("Successfully loaded component: {}", path.display());
Ok(component)
}
pub async fn precompile_component(&self, path: &std::path::Path) -> Result<Vec<u8>> {
tracing::debug!("Pre-compiling component: {}", path.display());
let bytes = std::fs::read(path)
.with_context(|| format!("Failed to read component file: {}", path.display()))?;
let compiled = self
.engine
.precompile_component(&bytes)
.context("Failed to precompile component")?;
tracing::info!("Pre-compiled component: {} bytes", compiled.len());
Ok(compiled)
}
pub async fn load_precompiled(
&self,
compiled_bytes: &[u8],
) -> Result<Component> {
unsafe {
Component::deserialize(&self.engine, compiled_bytes)
.context("Failed to deserialize pre-compiled component")
}
}
pub fn cache_dir(&self) -> &PathBuf {
&self.cache_dir
}
pub async fn validate_component(&self, _component: &Component) -> Result<()> {
tracing::debug!("Validating component");
Ok(())
}
}
impl Default for SkillEngine {
fn default() -> Self {
Self::new().expect("Failed to create default SkillEngine")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_engine_creation() {
let engine = SkillEngine::new().unwrap();
assert!(engine.cache_dir.exists());
}
#[test]
fn test_engine_config() {
let engine = SkillEngine::new().unwrap();
let _ = engine.wasmtime_engine();
}
}