edgecrab_plugins/script/
engine.rs1use 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}