Skip to main content

edgecrab_plugins/script/
engine.rs

1use std::path::Path;
2use std::sync::{Arc, Mutex};
3
4use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, Scope};
5
6use crate::error::PluginError;
7
8#[derive(Clone, Default)]
9pub struct ScriptOutput {
10    pub emitted_messages: Arc<Mutex<Vec<String>>>,
11}
12
13pub struct ScriptRuntime {
14    engine: Engine,
15    ast: rhai::AST,
16    output: ScriptOutput,
17}
18
19impl ScriptRuntime {
20    pub fn load(
21        path: &Path,
22        max_operations: u64,
23        max_call_depth: usize,
24    ) -> Result<Self, PluginError> {
25        let mut engine = Engine::new();
26        engine.set_max_operations(max_operations);
27        engine.set_max_call_levels(max_call_depth);
28        engine.on_print(|message| tracing::info!(target: "plugin_script", "{message}"));
29        let output = ScriptOutput::default();
30        let emitted = output.emitted_messages.clone();
31        engine.register_fn("log", |level: &str, msg: &str| {
32            tracing::info!(target: "plugin_script", level, "{msg}");
33        });
34        engine.register_fn("get_env", |key: &str| {
35            std::env::var(key).unwrap_or_default()
36        });
37        engine.register_fn("emit_message", move |msg: &str| {
38            if let Ok(mut messages) = emitted.lock() {
39                messages.push(msg.to_string());
40            }
41        });
42
43        let source = std::fs::read_to_string(path)?;
44        let ast = engine
45            .compile(&source)
46            .map_err(|error| PluginError::Script(error.to_string()))?;
47        Ok(Self {
48            engine,
49            ast,
50            output,
51        })
52    }
53
54    pub fn call_tool(
55        &self,
56        tool_name: &str,
57        args: &serde_json::Value,
58    ) -> Result<String, PluginError> {
59        let mut scope = Scope::new();
60        let args_json = serde_json::to_string(args)?;
61        let result: Result<ImmutableString, Box<EvalAltResult>> = self.engine.call_fn(
62            &mut scope,
63            &self.ast,
64            "tool_call",
65            (tool_name.to_string(), args_json),
66        );
67        result
68            .map(|value| value.to_string())
69            .map_err(|error| PluginError::Script(error.to_string()))
70    }
71
72    pub fn run_hook(
73        &self,
74        hook_name: &str,
75        payload: &serde_json::Value,
76    ) -> Result<(), PluginError> {
77        let mut scope = Scope::new();
78        let payload_json = serde_json::to_string(payload)?;
79        let _: Dynamic = self
80            .engine
81            .call_fn(
82                &mut scope,
83                &self.ast,
84                "run_hook",
85                (hook_name.to_string(), payload_json),
86            )
87            .map_err(|error| PluginError::Script(error.to_string()))?;
88        Ok(())
89    }
90
91    pub fn take_emitted_messages(&self) -> Vec<String> {
92        self.output
93            .emitted_messages
94            .lock()
95            .map(|mut messages| std::mem::take(&mut *messages))
96            .unwrap_or_default()
97    }
98}