use std::path::PathBuf;
use algocline_core::{Budget, ExecutionMetrics, ExecutionSpec};
use mlua::LuaSerdeExt;
use mlua_isle::{AsyncIsle, AsyncIsleDriver, IsleError};
use mlua_pkg::{resolvers::FsResolver, Registry};
use crate::bridge;
use crate::llm_bridge::LlmRequest;
use crate::session::Session;
const PRELUDE: &str = include_str!("prelude.lua");
pub struct Executor {
isle: AsyncIsle,
_driver: AsyncIsleDriver,
lib_paths: Vec<PathBuf>,
}
impl Executor {
pub async fn new(lib_paths: Vec<PathBuf>) -> anyhow::Result<Self> {
let paths_for_shared = lib_paths.clone();
let (isle, driver) = AsyncIsle::spawn(move |lua| {
let mut reg = Registry::new();
for path in &paths_for_shared {
if let Ok(resolver) = FsResolver::new(path) {
reg.add(resolver);
}
}
reg.install(lua)?;
Ok(())
})
.await?;
Ok(Self {
isle,
_driver: driver,
lib_paths,
})
}
pub async fn eval_simple(&self, code: String) -> Result<serde_json::Value, String> {
let task = self.isle.spawn_exec(move |lua| {
let result: mlua::Value = lua
.load(&code)
.eval()
.map_err(|e| IsleError::Lua(e.to_string()))?;
let json: serde_json::Value = lua
.from_value(result)
.map_err(|e| IsleError::Lua(e.to_string()))?;
serde_json::to_string(&json).map_err(|e| IsleError::Lua(format!("JSON serialize: {e}")))
});
let json_str = task.await.map_err(|e| e.to_string())?;
serde_json::from_str(&json_str).map_err(|e| format!("JSON parse: {e}"))
}
pub async fn start_session(
&self,
code: String,
ctx: serde_json::Value,
) -> Result<Session, String> {
let spec = ExecutionSpec::new(code, ctx);
let metrics = ExecutionMetrics::new();
if let Some(budget) = Budget::from_ctx(&spec.ctx) {
metrics.set_budget(budget);
}
let (llm_tx, llm_rx) = tokio::sync::mpsc::channel::<LlmRequest>(16);
let bridge_config = bridge::BridgeConfig {
llm_tx: Some(llm_tx),
ns: spec.namespace.clone(),
custom_metrics: metrics.custom_metrics_handle(),
budget: metrics.budget_handle(),
progress: metrics.progress_handle(),
lib_paths: self.lib_paths.clone(),
};
let lua_ctx = spec.ctx.clone();
let lua_code = spec.code.clone();
let lib_paths = self.lib_paths.clone();
let (session_isle, session_driver) = AsyncIsle::spawn(move |lua| {
let mut reg = Registry::new();
for path in &lib_paths {
if let Ok(resolver) = FsResolver::new(path) {
reg.add(resolver);
}
}
reg.install(lua)?;
Ok(())
})
.await
.map_err(|e| format!("Session VM spawn failed: {e}"))?;
session_isle
.exec(move |lua| {
let alc_table = lua.create_table()?;
bridge::register(lua, &alc_table, bridge_config)?;
lua.globals().set("alc", alc_table)?;
let ctx_value = lua.to_value(&lua_ctx)?;
lua.globals().set("ctx", ctx_value)?;
lua.load(PRELUDE)
.exec()
.map_err(|e| IsleError::Lua(format!("Prelude load failed: {e}")))?;
Ok("ok".to_string())
})
.await
.map_err(|e| format!("Session setup failed: {e}"))?;
let wrapped_code = format!("return alc.json_encode((function()\n{lua_code}\nend)())");
let exec_task = session_isle.spawn_coroutine_eval(&wrapped_code);
drop(session_isle);
Ok(Session::new(llm_rx, exec_task, metrics, session_driver))
}
}