algocline_engine/
executor.rs1use std::path::PathBuf;
21
22use algocline_core::{Budget, ExecutionMetrics, ExecutionSpec};
23use mlua::LuaSerdeExt;
24use mlua_isle::{AsyncIsle, AsyncIsleDriver, IsleError};
25use mlua_pkg::{resolvers::FsResolver, Registry};
26
27use crate::bridge;
28use crate::llm_bridge::LlmRequest;
29use crate::session::Session;
30
31const PRELUDE: &str = include_str!("prelude.lua");
34
35pub struct Executor {
44 isle: AsyncIsle,
46 _driver: AsyncIsleDriver,
47 lib_paths: Vec<PathBuf>,
49}
50
51impl Executor {
52 pub async fn new(lib_paths: Vec<PathBuf>) -> anyhow::Result<Self> {
53 let paths_for_shared = lib_paths.clone();
54 let (isle, driver) = AsyncIsle::spawn(move |lua| {
55 let mut reg = Registry::new();
56 for path in &paths_for_shared {
57 if let Ok(resolver) = FsResolver::new(path) {
58 reg.add(resolver);
59 }
60 }
61 reg.install(lua)?;
62 Ok(())
63 })
64 .await?;
65
66 Ok(Self {
67 isle,
68 _driver: driver,
69 lib_paths,
70 })
71 }
72
73 pub async fn eval_simple(&self, code: String) -> Result<serde_json::Value, String> {
76 let task = self.isle.spawn_exec(move |lua| {
77 let result: mlua::Value = lua
78 .load(&code)
79 .eval()
80 .map_err(|e| IsleError::Lua(e.to_string()))?;
81 let json: serde_json::Value = lua
82 .from_value(result)
83 .map_err(|e| IsleError::Lua(e.to_string()))?;
84 serde_json::to_string(&json).map_err(|e| IsleError::Lua(format!("JSON serialize: {e}")))
85 });
86
87 let json_str = task.await.map_err(|e| e.to_string())?;
88 serde_json::from_str(&json_str).map_err(|e| format!("JSON parse: {e}"))
89 }
90
91 pub async fn start_session(
98 &self,
99 code: String,
100 ctx: serde_json::Value,
101 ) -> Result<Session, String> {
102 let spec = ExecutionSpec::new(code, ctx);
103 let metrics = ExecutionMetrics::new();
104
105 if let Some(budget) = Budget::from_ctx(&spec.ctx) {
107 metrics.set_budget(budget);
108 }
109
110 let (llm_tx, llm_rx) = tokio::sync::mpsc::channel::<LlmRequest>(16);
111
112 let bridge_config = bridge::BridgeConfig {
113 llm_tx: Some(llm_tx),
114 ns: spec.namespace.clone(),
115 custom_metrics: metrics.custom_metrics_handle(),
116 budget: metrics.budget_handle(),
117 progress: metrics.progress_handle(),
118 lib_paths: self.lib_paths.clone(),
119 };
120 let lua_ctx = spec.ctx.clone();
121 let lua_code = spec.code.clone();
122
123 let lib_paths = self.lib_paths.clone();
125 let (session_isle, session_driver) = AsyncIsle::spawn(move |lua| {
126 let mut reg = Registry::new();
127 for path in &lib_paths {
128 if let Ok(resolver) = FsResolver::new(path) {
129 reg.add(resolver);
130 }
131 }
132 reg.install(lua)?;
133 Ok(())
134 })
135 .await
136 .map_err(|e| format!("Session VM spawn failed: {e}"))?;
137
138 session_isle
141 .exec(move |lua| {
142 let alc_table = lua.create_table()?;
143 bridge::register(lua, &alc_table, bridge_config)?;
144 lua.globals().set("alc", alc_table)?;
145
146 let ctx_value = lua.to_value(&lua_ctx)?;
147 lua.globals().set("ctx", ctx_value)?;
148
149 lua.load(PRELUDE)
150 .exec()
151 .map_err(|e| IsleError::Lua(format!("Prelude load failed: {e}")))?;
152
153 Ok("ok".to_string())
156 })
157 .await
158 .map_err(|e| format!("Session setup failed: {e}"))?;
159
160 let wrapped_code = format!("return alc.json_encode((function()\n{lua_code}\nend)())");
162 let exec_task = session_isle.spawn_coroutine_eval(&wrapped_code);
163
164 drop(session_isle);
167
168 Ok(Session::new(llm_rx, exec_task, metrics, session_driver))
169 }
170}