exomonad_core/
plugin_manager.rs1use crate::effects::{host_fn::yield_effect_host_fn, host_fn::YieldEffectContext, EffectRegistry};
8use anyhow::{Context, Result};
9use extism::{Manifest, Plugin, PluginBuilder};
10use serde::{Deserialize, Serialize};
11use std::sync::{Arc, RwLock};
12
13#[derive(Clone)]
30pub struct PluginManager {
31 plugin: Arc<RwLock<Plugin>>,
33 content_hash: String,
34}
35
36impl PluginManager {
37 pub async fn new(wasm_bytes: &[u8], registry: Arc<EffectRegistry>) -> Result<Self> {
44 let hash = sha256_short(wasm_bytes);
45 tracing::info!(size = wasm_bytes.len(), hash = %hash, "Loading embedded WASM plugin");
46
47 let manifest = Manifest::new([extism::Wasm::data(wasm_bytes.to_vec())]);
48
49 tracing::info!(
51 namespaces = ?registry.namespaces(),
52 "Registering yield_effect with {} handler namespaces",
53 registry.namespaces().len()
54 );
55
56 let ctx = YieldEffectContext { registry };
57 let functions = vec![yield_effect_host_fn(ctx)];
58
59 let plugin = PluginBuilder::new(manifest)
60 .with_functions(functions)
61 .with_wasi(true)
62 .build()
63 .context("Failed to create plugin")?;
64
65 Ok(Self {
66 plugin: Arc::new(RwLock::new(plugin)),
67 content_hash: hash,
68 })
69 }
70
71 pub fn content_hash(&self) -> &str {
73 &self.content_hash
74 }
75
76 pub async fn call<I, O>(&self, function: &str, input: &I) -> Result<O>
83 where
84 I: Serialize + Send + Sync + 'static,
85 O: for<'de> Deserialize<'de> + Send + 'static,
86 {
87 let plugin_lock = self.plugin.clone();
88 let function_name = function.to_string();
89 let input_data = serde_json::to_vec(input)?;
90
91 let result_bytes = tokio::task::spawn_blocking(move || -> Result<Vec<u8>> {
92 let mut plugin = plugin_lock
93 .write()
94 .map_err(|e| anyhow::anyhow!("Plugin lock poisoned: {}", e))?;
95 plugin.call::<&[u8], Vec<u8>>(&function_name, &input_data)
96 })
97 .await??;
98
99 if result_bytes.is_empty() {
100 let null: O = serde_json::from_str("null")?;
101 return Ok(null);
102 }
103
104 let output: O = serde_json::from_slice(&result_bytes)?;
105 Ok(output)
106 }
107}
108
109fn sha256_short(data: &[u8]) -> String {
110 use sha2::{Digest, Sha256};
111 format!("{:x}", Sha256::digest(data))[..12].to_string()
112}