1use anyhow::{Context, Result};
2use std::path::PathBuf;
3use std::sync::Arc;
4use wasmtime::{component::Component, Config, Engine};
5
6
7pub struct SkillEngine {
9 engine: Arc<Engine>,
10 cache_dir: PathBuf,
11}
12
13impl SkillEngine {
14 pub fn new() -> Result<Self> {
16 let mut config = Config::new();
17
18 config.wasm_component_model(true);
20
21 config.async_support(true);
23
24 config.cranelift_opt_level(wasmtime::OptLevel::Speed);
26
27 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 config.max_wasm_stack(1024 * 1024); 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 pub fn wasmtime_engine(&self) -> &Engine {
57 &self.engine
58 }
59
60 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 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 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 pub fn cache_dir(&self) -> &PathBuf {
102 &self.cache_dir
103 }
104
105 pub async fn validate_component(&self, _component: &Component) -> Result<()> {
107 tracing::debug!("Validating component");
110
111 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 let _ = engine.wasmtime_engine();
139 }
140}