skill_runtime/
engine.rs

1use anyhow::{Context, Result};
2use std::path::PathBuf;
3use std::sync::Arc;
4use wasmtime::{component::Component, Config, Engine};
5
6
7/// Main WASM runtime engine for executing skills
8pub struct SkillEngine {
9    engine: Arc<Engine>,
10    cache_dir: PathBuf,
11}
12
13impl SkillEngine {
14    /// Create a new SkillEngine with optimized configuration
15    pub fn new() -> Result<Self> {
16        let mut config = Config::new();
17
18        // Enable Component Model support (required for WIT interfaces)
19        config.wasm_component_model(true);
20
21        // Enable async support for non-blocking I/O
22        config.async_support(true);
23
24        // Performance optimizations
25        config.cranelift_opt_level(wasmtime::OptLevel::Speed);
26
27        // Enable caching for faster subsequent loads
28        let cache_dir = dirs::cache_dir()
29            .context("Failed to get cache directory")?
30            .join("skill-engine")
31            .join("wasmtime-cache");
32
33        std::fs::create_dir_all(&cache_dir)?;
34        config.cache_config_load_default()?;
35
36        // Memory limits (1GB per skill)
37        config.max_wasm_stack(1024 * 1024); // 1MB stack
38
39        // Enable debug info for better error messages
40        config.debug_info(true);
41
42        let engine = Engine::new(&config)?;
43
44        tracing::info!(
45            "Initialized SkillEngine with cache at: {}",
46            cache_dir.display()
47        );
48
49        Ok(Self {
50            engine: Arc::new(engine),
51            cache_dir,
52        })
53    }
54
55    /// Get the underlying Wasmtime engine
56    pub fn wasmtime_engine(&self) -> &Engine {
57        &self.engine
58    }
59
60    /// Load a WASM component from file
61    pub async fn load_component(&self, path: &std::path::Path) -> Result<Component> {
62        tracing::debug!("Loading component from: {}", path.display());
63
64        let component = Component::from_file(&self.engine, path)
65            .with_context(|| format!("Failed to load component from {}", path.display()))?;
66
67        tracing::info!("Successfully loaded component: {}", path.display());
68
69        Ok(component)
70    }
71
72    /// Pre-compile a component and store in cache (AOT compilation)
73    pub async fn precompile_component(&self, path: &std::path::Path) -> Result<Vec<u8>> {
74        tracing::debug!("Pre-compiling component: {}", path.display());
75
76        let bytes = std::fs::read(path)
77            .with_context(|| format!("Failed to read component file: {}", path.display()))?;
78
79        let compiled = self
80            .engine
81            .precompile_component(&bytes)
82            .context("Failed to precompile component")?;
83
84        tracing::info!("Pre-compiled component: {} bytes", compiled.len());
85
86        Ok(compiled)
87    }
88
89    /// Load a pre-compiled component from cache
90    pub async fn load_precompiled(
91        &self,
92        compiled_bytes: &[u8],
93    ) -> Result<Component> {
94        unsafe {
95            Component::deserialize(&self.engine, compiled_bytes)
96                .context("Failed to deserialize pre-compiled component")
97        }
98    }
99
100    /// Get cache directory path
101    pub fn cache_dir(&self) -> &PathBuf {
102        &self.cache_dir
103    }
104
105    /// Validate a component against the skill interface
106    pub async fn validate_component(&self, _component: &Component) -> Result<()> {
107        // TODO: Use wit-bindgen to validate exports
108        // For now, just check that the component loads successfully
109        tracing::debug!("Validating component");
110
111        // The fact that it loaded successfully is a good start
112        // Full validation will be implemented with wit-bindgen integration
113
114        Ok(())
115    }
116}
117
118impl Default for SkillEngine {
119    fn default() -> Self {
120        Self::new().expect("Failed to create default SkillEngine")
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[tokio::test]
129    async fn test_engine_creation() {
130        let engine = SkillEngine::new().unwrap();
131        assert!(engine.cache_dir.exists());
132    }
133
134    #[test]
135    fn test_engine_config() {
136        let engine = SkillEngine::new().unwrap();
137        // Verify the engine was created successfully
138        let _ = engine.wasmtime_engine();
139    }
140}