1#[cfg(feature = "wasm-runtime")]
2use std::collections::HashMap;
3#[cfg(feature = "wasm-runtime")]
4use std::sync::RwLock;
5
6#[cfg(feature = "wasm-runtime")]
7use crate::error::SkillError;
8
9#[cfg(feature = "wasm-runtime")]
10#[derive(Debug)]
11pub struct WasmConfig {
12 pub memory_limit_mb: u64,
13 pub default_timeout_ms: u64,
14 pub max_instances: usize,
15}
16
17#[cfg(feature = "wasm-runtime")]
18impl Default for WasmConfig {
19 fn default() -> Self {
20 Self {
21 memory_limit_mb: 64,
22 default_timeout_ms: 5000,
23 max_instances: 10,
24 }
25 }
26}
27
28#[cfg(feature = "wasm-runtime")]
29pub struct WasmRuntime {
30 engine: wasmtime::Engine,
31 module_cache: RwLock<HashMap<String, wasmtime::Module>>,
32 config: WasmConfig,
33}
34
35#[cfg(feature = "wasm-runtime")]
36impl std::fmt::Debug for WasmRuntime {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 f.debug_struct("WasmRuntime")
39 .field("engine", &"<wasmtime::Engine>")
40 .field("module_cache", &self.module_cache)
41 .field("config", &self.config)
42 .finish()
43 }
44}
45
46#[cfg(feature = "wasm-runtime")]
47impl WasmRuntime {
48 pub fn new(config: WasmConfig) -> Result<Self, SkillError> {
49 let mut engine_config = wasmtime::Config::default();
50 engine_config
51 .consume_fuel(true)
52 .epoch_interruption(true);
53
54 let engine = wasmtime::Engine::new(&engine_config)
55 .map_err(|e| SkillError::Wasm(e.to_string()))?;
56
57 Ok(Self {
58 engine,
59 module_cache: RwLock::new(HashMap::new()),
60 config,
61 })
62 }
63
64 pub async fn execute(
66 &self,
67 module_bytes: &[u8],
68 tool: &str,
69 _args: serde_json::Value,
70 ) -> Result<serde_json::Value, SkillError> {
71 let cache_key = format!("{}:{}", tool, hex::encode(&module_bytes[..module_bytes.len().min(32)]));
72
73 let module = {
74 let cache = self.module_cache.read().unwrap();
75 cache.get(&cache_key).cloned()
76 };
77
78 let module = match module {
79 Some(m) => m,
80 None => {
81 let m = wasmtime::Module::new(&self.engine, module_bytes)
82 .map_err(|e| SkillError::Wasm(e.to_string()))?;
83 self.module_cache
84 .write()
85 .unwrap()
86 .insert(cache_key.clone(), m.clone());
87 m
88 }
89 };
90
91 let mut store = wasmtime::Store::new(&self.engine, ());
92 store.set_fuel(1_000_000)
93 .map_err(|e| SkillError::Wasm(e.to_string()))?;
94
95 let linker = wasmtime::Linker::new(&self.engine);
96 let instance = linker
97 .instantiate_async(&mut store, &module)
98 .await
99 .map_err(|e| SkillError::Wasm(e.to_string()))?;
100
101 let result = if let Ok(func) = instance.get_typed_func::<(), i32>(&mut store, tool) {
102 let ret = func.call_async(&mut store, ())
103 .await
104 .map_err(|e| SkillError::Wasm(e.to_string()))?;
105 serde_json::json!({"exit_code": ret})
106 } else if let Ok(func) = instance.get_typed_func::<(), ()>(&mut store, tool) {
107 func.call_async(&mut store, ())
108 .await
109 .map_err(|e| SkillError::Wasm(e.to_string()))?;
110 serde_json::json!({"status": "ok"})
111 } else if let Ok(func) = instance.get_typed_func::<(), ()>(&mut store, "_start") {
112 func.call_async(&mut store, ())
113 .await
114 .map_err(|e| SkillError::Wasm(e.to_string()))?;
115 serde_json::json!({"status": "started"})
116 } else {
117 return Err(SkillError::Wasm(format!(
118 "Function '{}' not found in WASM module", tool
119 )));
120 };
121
122 Ok(result)
123 }
124}
125
126#[cfg(feature = "wasm-runtime")]
128mod hex {
129 pub fn encode(bytes: &[u8]) -> String {
130 bytes.iter().map(|b| format!("{:02x}", b)).collect()
131 }
132}