use super::{
Arc, AtomicU32, AtomicUsize, BTreeMap, ClosedScopes, DEFAULT_LUA_LOG_EVENTS,
DEFAULT_LUA_MEMORY_BYTES, Error, Json, Lua, LuaBlockResult, LuaFanoutResult, LuaModelHandle,
LuaOptions, LuaProgram, LuaSectionHandle, LuaSerdeExt, LuaToolHandle, ModelBindings,
ModelInferHook, ModelRuntime, MultiValue, Mutex, Observer, Ordering, Result, RuntimeResolution,
StdLib, StoreRef, ToolBinding, ToolBindings, ToolCallCounts, ToolPhase, ToolRuntime, ToolScope,
Value, close_model_scope, default_log_byte_budget, detail, finish_log_phase, harden,
install_h2_models, install_h2_tools, install_instruction_budget, install_log,
install_lua_tool_calls, install_store_table, install_tasks_table, resolve_section_target,
scalar_return, seal_sys,
};
#[derive(Debug)]
pub(crate) struct SectionVm {
execution: String,
lua: Lua,
bound_tools: ToolBindings,
bound_models: ModelBindings,
pub(crate) tool_runtime: Arc<Mutex<ToolRuntime>>,
pub(crate) model_runtime: Arc<Mutex<ModelRuntime>>,
counts_slot: Arc<Mutex<Option<ToolCallCounts>>>,
jump_slot: Arc<Mutex<Option<String>>>,
sys_live: Arc<Mutex<Option<Json>>>,
store: Option<StoreRef>,
host_injected: bool,
log_budget: Arc<AtomicU32>,
log_byte_budget: Arc<AtomicUsize>,
}
impl SectionVm {
pub(crate) fn new(
shared: Option<&LuaProgram>,
execution: &str,
observer: &dyn Observer,
section: &str,
) -> Result<Self> {
let lua = Lua::new_with(
StdLib::STRING | StdLib::TABLE | StdLib::MATH,
LuaOptions::default(),
)
.map_err(Error::lua)?;
lua.set_memory_limit(DEFAULT_LUA_MEMORY_BYTES)
.map_err(Error::lua)?;
let vm = Self {
execution: execution.to_owned(),
lua,
bound_tools: ToolBindings::default(),
bound_models: ModelBindings::default(),
tool_runtime: Arc::new(Mutex::new(ToolRuntime {
phase: ToolPhase::H2,
added: Vec::new(),
description_overrides: BTreeMap::new(),
generation: 0,
})),
model_runtime: Arc::new(Mutex::new(ModelRuntime::new())),
counts_slot: Arc::new(Mutex::new(None)),
jump_slot: Arc::new(Mutex::new(None)),
sys_live: Arc::new(Mutex::new(None)),
store: None,
host_injected: false,
log_budget: Arc::new(AtomicU32::new(DEFAULT_LUA_LOG_EVENTS)),
log_byte_budget: Arc::new(AtomicUsize::new(default_log_byte_budget(
DEFAULT_LUA_LOG_EVENTS,
))),
};
if let Err(error) = harden(&vm.lua) {
return vm.construction_failed(error, observer, section);
}
install_instruction_budget(&vm.lua);
if let Some(program) = shared {
observer.observe(execution, section, detail::LUA_SHARED_LOAD_STARTED);
match vm.run_loaded_with_log(program, observer, section) {
Ok(_) => observer.observe(execution, section, detail::LUA_SHARED_LOAD_SUCCEEDED),
Err(error) => {
observer.observe(execution, section, detail::LUA_SHARED_LOAD_FAILED);
return vm.construction_failed(error, observer, section);
}
}
}
Ok(vm)
}
pub(crate) fn new_for_section(
replay: Option<&LuaProgram>,
tools: &ToolBindings,
models: &ModelBindings,
execution: &str,
observer: &dyn Observer,
section: &str,
) -> Result<Self> {
let mut vm = Self::new(None, execution, observer, section)?;
if let Some(program) = replay {
observer.observe(execution, section, detail::LUA_SHARED_LOAD_STARTED);
match vm.run_loaded_without_host(program) {
Ok(_) => observer.observe(execution, section, detail::LUA_SHARED_LOAD_SUCCEEDED),
Err(error) => {
observer.observe(execution, section, detail::LUA_SHARED_LOAD_FAILED);
return vm.construction_failed(error, observer, section);
}
}
}
vm.bound_tools = tools.clone();
vm.bound_models = models.clone();
if let Err(error) = vm.install_captured_bindings() {
return vm.construction_failed(error, observer, section);
}
Ok(vm)
}
fn install_captured_bindings(&self) -> Result<()> {
let globals = self.lua.globals();
for binding in self.bound_tools.bindings() {
let handle =
LuaToolHandle::from_binding(binding.alias(), binding.description(), binding.id());
let userdata = self.lua.create_userdata(handle).map_err(Error::lua)?;
globals
.raw_set(binding.alias(), userdata)
.map_err(Error::lua)?;
}
for binding in self.bound_models.bindings() {
let userdata = self
.lua
.create_userdata(LuaModelHandle::from_binding(binding))
.map_err(Error::lua)?;
globals
.raw_set(binding.alias(), userdata)
.map_err(Error::lua)?;
}
Ok(())
}
pub(crate) fn inject_host(
&mut self,
args: &str,
sys: &Json,
store: &StoreRef,
last_reply: Option<&str>,
) -> Result<()> {
self.inject_host_with_var(args, sys, store, last_reply, None)
}
pub(crate) fn inject_host_with_var(
&mut self,
args: &str,
sys: &Json,
store: &StoreRef,
last_reply: Option<&str>,
initial_var: Option<&Json>,
) -> Result<()> {
if self.host_injected {
return Err(Error::Lua(
"section VM host values were already injected".to_owned(),
));
}
let globals = self.lua.globals();
globals.raw_set("args", args).map_err(Error::lua)?;
let sys_table = seal_sys(&self.lua, sys)?;
globals.raw_set("sys", sys_table).map_err(Error::lua)?;
{
let mut live = self
.sys_live
.lock()
.map_err(|_| Error::Lua("sys live slot was poisoned".to_owned()))?;
*live = Some(sys.clone());
}
let var = match initial_var {
Some(value) => self.lua.to_value(value).map_err(Error::lua)?,
None => Value::Table(self.lua.create_table().map_err(Error::lua)?),
};
globals.raw_set("var", var).map_err(Error::lua)?;
install_h2_tools(&self.lua, &globals, &self.bound_tools, &self.tool_runtime)?;
install_h2_models(&self.lua, &globals, &self.bound_models, &self.model_runtime)?;
let reply_value = match last_reply {
Some(text) => Value::String(self.lua.create_string(text).map_err(Error::lua)?),
None => Value::Nil,
};
globals.raw_set("reply", reply_value).map_err(Error::lua)?;
self.store = Some(store.clone());
self.host_injected = true;
Ok(())
}
pub(crate) fn run_live_h1_block(
&self,
program: &LuaProgram,
resolution: &RuntimeResolution<'_, '_>,
observer: &dyn Observer,
section: &str,
) -> Result<Option<String>> {
let result = self.lua.scope(|scope| {
resolution
.install(&self.lua, scope)
.map_err(mlua::Error::external)?;
self.run_prologue(program, observer, section)
.map_err(mlua::Error::external)
});
match result {
Ok(value) => Ok(value),
Err(error) => match resolution.take_callback_error()? {
Some(error) => Err(error),
None => Err(Error::lua(error)),
},
}
}
pub(crate) fn re_seal_sys(&self, sys: &Json) -> Result<()> {
if !self.host_injected {
return Err(Error::Lua(
"section VM host values were not injected".to_owned(),
));
}
let globals = self.lua.globals();
let sys_table = seal_sys(&self.lua, sys)?;
globals.raw_set("sys", sys_table).map_err(Error::lua)?;
let mut live = self
.sys_live
.lock()
.map_err(|_| Error::Lua("sys live slot was poisoned".to_owned()))?;
*live = Some(sys.clone());
Ok(())
}
pub(crate) fn sys_live_handle(&self) -> Arc<Mutex<Option<Json>>> {
Arc::clone(&self.sys_live)
}
pub(crate) fn current_sys(&self, fallback: &Json) -> Result<Json> {
let guard = self
.sys_live
.lock()
.map_err(|_| Error::Lua("sys live slot was poisoned".to_owned()))?;
Ok(guard.clone().unwrap_or_else(|| fallback.clone()))
}
pub(crate) fn run_prologue(
&self,
program: &LuaProgram,
observer: &dyn Observer,
section: &str,
) -> Result<Option<String>> {
observer.observe(&self.execution, section, detail::LUA_PROLOGUE_STARTED);
if !self.host_injected {
let error = Error::Lua("section VM host values have not been injected".to_owned());
observer.observe(&self.execution, section, detail::LUA_PROLOGUE_FAILED);
return Err(error);
}
let result = self.run_loaded_with_host(program, observer, section);
observer.observe(
&self.execution,
section,
if result.is_ok() {
detail::LUA_PROLOGUE_SUCCEEDED
} else {
detail::LUA_PROLOGUE_FAILED
},
);
result
}
pub(crate) fn run_prologue_with_control<E, F>(
&self,
program: &LuaProgram,
observer: &dyn Observer,
section: &str,
tasks: &[LuaSectionHandle],
execute_callback: Option<&E>,
fanout_callback: Option<&F>,
) -> Result<LuaBlockResult>
where
E: Fn(Value, Option<String>) -> std::result::Result<String, Error>,
F: Fn(String, String) -> std::result::Result<Vec<LuaFanoutResult>, Error>,
{
observer.observe(&self.execution, section, detail::LUA_PROLOGUE_STARTED);
if !self.host_injected {
let error = Error::Lua("section VM host values have not been injected".to_owned());
observer.observe(&self.execution, section, detail::LUA_PROLOGUE_FAILED);
return Err(error);
}
let result = self.run_loaded_with_control(
program,
observer,
section,
tasks,
execute_callback,
fanout_callback,
true,
);
let ok = result.is_ok();
observer.observe(
&self.execution,
section,
if ok {
detail::LUA_PROLOGUE_SUCCEEDED
} else {
detail::LUA_PROLOGUE_FAILED
},
);
result
}
pub(crate) fn bind_reply(
&self,
reply: &str,
observer: &dyn Observer,
section: &str,
) -> Result<()> {
observer.observe(&self.execution, section, detail::LUA_REPLY_BINDING_STARTED);
if !self.host_injected {
let error = Error::Lua("section VM host values have not been injected".to_owned());
observer.observe(&self.execution, section, detail::LUA_REPLY_BINDING_FAILED);
return Err(error);
}
if let Err(error) = self.require_closed_tool_scope("bind a model reply") {
observer.observe(&self.execution, section, detail::LUA_REPLY_BINDING_FAILED);
return Err(error);
}
let result = self
.lua
.globals()
.raw_set("reply", reply)
.map_err(Error::lua);
observer.observe(
&self.execution,
section,
if result.is_ok() {
detail::LUA_REPLY_BINDING_SUCCEEDED
} else {
detail::LUA_REPLY_BINDING_FAILED
},
);
result
}
pub(crate) fn run_epilog(
&self,
program: &LuaProgram,
observer: &dyn Observer,
section: &str,
) -> Result<Option<String>> {
observer.observe(&self.execution, section, detail::LUA_EPILOG_STARTED);
if !self.host_injected {
let error = Error::Lua("section VM host values have not been injected".to_owned());
observer.observe(&self.execution, section, detail::LUA_EPILOG_FAILED);
return Err(error);
}
if let Err(error) = self.require_closed_tool_scope("run an epilog") {
observer.observe(&self.execution, section, detail::LUA_EPILOG_FAILED);
return Err(error);
}
let result = self.run_loaded_with_host(program, observer, section);
observer.observe(
&self.execution,
section,
if result.is_ok() {
detail::LUA_EPILOG_SUCCEEDED
} else {
detail::LUA_EPILOG_FAILED
},
);
result
}
pub(crate) fn var(&self) -> Result<Json> {
if !self.host_injected {
return Err(Error::Lua(
"section VM host values have not been injected".to_owned(),
));
}
let value: Value = self.lua.globals().get("var").map_err(Error::lua)?;
self.lua.from_value(value).map_err(Error::lua)
}
pub(crate) fn set_global_string(&self, name: &str, value: &str) -> Result<()> {
self.lua.globals().raw_set(name, value).map_err(Error::lua)
}
pub(crate) fn run_epilog_with_control<E, F>(
&self,
program: &LuaProgram,
observer: &dyn Observer,
section: &str,
tasks: &[LuaSectionHandle],
execute_callback: Option<&E>,
fanout_callback: Option<&F>,
) -> Result<LuaBlockResult>
where
E: Fn(Value, Option<String>) -> std::result::Result<String, Error>,
F: Fn(String, String) -> std::result::Result<Vec<LuaFanoutResult>, Error>,
{
observer.observe(&self.execution, section, detail::LUA_EPILOG_STARTED);
if !self.host_injected {
let error = Error::Lua("section VM host values have not been injected".to_owned());
observer.observe(&self.execution, section, detail::LUA_EPILOG_FAILED);
return Err(error);
}
if let Err(error) = self.require_closed_tool_scope("run an epilog") {
observer.observe(&self.execution, section, detail::LUA_EPILOG_FAILED);
return Err(error);
}
let result = self.run_loaded_with_control(
program,
observer,
section,
tasks,
execute_callback,
fanout_callback,
true,
);
let ok = result.is_ok();
observer.observe(
&self.execution,
section,
if ok {
detail::LUA_EPILOG_SUCCEEDED
} else {
detail::LUA_EPILOG_FAILED
},
);
result
}
#[cfg(test)]
pub(crate) fn close_tool_scope(
&self,
observer: &dyn Observer,
section: &str,
) -> Result<ToolScope> {
Ok(self.close_scopes(observer, section)?.tools)
}
pub(crate) fn close_scopes(
&self,
observer: &dyn Observer,
section: &str,
) -> Result<ClosedScopes> {
observer.observe(&self.execution, section, detail::TOOL_SCOPE_CLOSING);
let tools = self.prepare_tool_scope();
observer.observe(
&self.execution,
section,
if tools.is_ok() {
detail::TOOL_SCOPE_CLOSED
} else {
detail::TOOL_SCOPE_FAILED
},
);
let tools = tools?;
let model = close_model_scope(
&self.bound_models,
&self.model_runtime,
&self.execution,
observer,
section,
)?;
self.commit_tool_scope_closed()?;
Ok(ClosedScopes { tools, model })
}
fn prepare_tool_scope(&self) -> Result<ToolScope> {
let bindings = &self.bound_tools;
let runtime = self
.tool_runtime
.lock()
.map_err(|_| Error::Lua("tool declaration runtime was poisoned".to_owned()))?;
if runtime.phase != ToolPhase::H2 {
return Err(Error::Lua(
"tool scope can only close once after H2 recording".to_owned(),
));
}
let aliases = bindings
.always
.iter()
.chain(runtime.added.iter())
.cloned()
.collect::<Vec<_>>();
let effective = aliases
.iter()
.map(|alias| binding_for_scope(bindings, &runtime, alias))
.collect::<Result<Vec<_>>>()?;
Ok(ToolScope {
bindings: effective,
})
}
fn commit_tool_scope_closed(&self) -> Result<()> {
let mut runtime = self
.tool_runtime
.lock()
.map_err(|_| Error::Lua("tool declaration runtime was poisoned".to_owned()))?;
if runtime.phase == ToolPhase::H2 {
runtime.phase = ToolPhase::Closed;
}
Ok(())
}
pub(crate) fn install_tool_call_counts(&mut self, scope: &ToolScope) -> Result<ToolCallCounts> {
let counts = {
let mut slot = self
.counts_slot
.lock()
.map_err(|_| Error::Lua("tool call counts mutex was poisoned".to_owned()))?;
if let Some(existing) = slot.as_ref() {
for binding in scope.bindings() {
existing.ensure(binding.alias())?;
}
existing.clone()
} else {
let created =
ToolCallCounts::new(scope.bindings().iter().map(|b| b.alias().to_owned()));
*slot = Some(created.clone());
created
}
};
let declared: Vec<String> = self
.bound_tools
.bindings()
.iter()
.map(|binding| binding.alias().to_owned())
.collect();
install_lua_tool_calls(&self.lua, &counts, &declared)?;
Ok(counts)
}
#[must_use]
pub(crate) fn tool_bag_handles(&self) -> (ToolBindings, Arc<Mutex<ToolRuntime>>) {
(self.bound_tools.clone(), Arc::clone(&self.tool_runtime))
}
#[must_use]
pub(crate) fn counts_slot(&self) -> Arc<Mutex<Option<ToolCallCounts>>> {
Arc::clone(&self.counts_slot)
}
pub(crate) fn apply_lua_limits(&self, memory_bytes: usize, log_events: u32) -> Result<()> {
self.lua
.set_memory_limit(memory_bytes)
.map_err(Error::lua)?;
self.log_budget.store(log_events, Ordering::Relaxed);
self.log_byte_budget
.store(default_log_byte_budget(log_events), Ordering::Relaxed);
Ok(())
}
pub(crate) fn set_infer_hook(&self, hook: ModelInferHook) {
self.lua.set_app_data(hook);
}
pub(crate) fn clear_infer_hook(&self) {
let _ = self.lua.remove_app_data::<ModelInferHook>();
}
fn require_closed_tool_scope(&self, operation: &str) -> Result<()> {
let runtime = self
.tool_runtime
.lock()
.map_err(|_| Error::Lua("tool declaration runtime was poisoned".to_owned()))?;
if runtime.phase == ToolPhase::Closed {
Ok(())
} else {
Err(Error::Lua(format!(
"tool scope must close before the section VM can {operation}"
)))
}
}
pub(crate) fn teardown(self, observer: &dyn Observer, section: &str) {
let execution = self.execution.clone();
observer.observe(&self.execution, section, detail::LUA_TEARDOWN_STARTED);
self.clear_infer_hook();
drop(self);
observer.observe(&execution, section, detail::LUA_TEARDOWN_SUCCEEDED);
}
fn construction_failed(
self,
error: Error,
observer: &dyn Observer,
section: &str,
) -> Result<Self> {
self.teardown(observer, section);
Err(error)
}
fn run_loaded_with_log(
&self,
program: &LuaProgram,
observer: &dyn Observer,
section: &str,
) -> Result<Option<String>> {
let returned: MultiValue = self
.lua
.scope(|scope| {
install_log(
&self.lua,
scope,
&self.execution,
observer,
section,
&self.log_budget,
&self.log_byte_budget,
)
.map_err(mlua::Error::external)?;
let result = program
.load(&self.lua)
.map_err(mlua::Error::external)?
.call(());
finish_log_phase(&self.lua, result)
})
.map_err(|error| program.map_runtime_error(&error))?;
scalar_return(returned)
}
fn run_loaded_without_host(&self, program: &LuaProgram) -> Result<Option<String>> {
let returned: MultiValue = program
.load(&self.lua)?
.call(())
.map_err(|error| program.map_runtime_error(&error))?;
scalar_return(returned)
}
fn run_loaded_with_host(
&self,
program: &LuaProgram,
observer: &dyn Observer,
section: &str,
) -> Result<Option<String>> {
let store = self.store.as_ref().ok_or_else(|| {
Error::Lua("section VM host values have not been injected".to_owned())
})?;
let returned: MultiValue = self
.lua
.scope(|scope| {
install_log(
&self.lua,
scope,
&self.execution,
observer,
section,
&self.log_budget,
&self.log_byte_budget,
)
.map_err(mlua::Error::external)?;
install_store_table(
&self.lua,
scope,
&self.lua.globals(),
store,
&self.execution,
observer,
section,
)
.map_err(mlua::Error::external)?;
let result = program
.load(&self.lua)
.map_err(mlua::Error::external)?
.call(());
finish_log_phase(&self.lua, result)
})
.map_err(|error| program.map_runtime_error(&error))?;
scalar_return(returned)
}
fn take_jump(&self) -> Result<Option<String>> {
let mut slot = self
.jump_slot
.lock()
.map_err(|_| Error::Lua("jump slot was poisoned".to_owned()))?;
Ok(slot.take())
}
#[expect(
clippy::too_many_arguments,
reason = "control-flow host fns are installed together for one Lua phase"
)]
fn run_loaded_with_control<E, F>(
&self,
program: &LuaProgram,
observer: &dyn Observer,
section: &str,
tasks: &[LuaSectionHandle],
execute_callback: Option<&E>,
fanout_callback: Option<&F>,
jump_enabled: bool,
) -> Result<LuaBlockResult>
where
E: Fn(Value, Option<String>) -> std::result::Result<String, Error>,
F: Fn(String, String) -> std::result::Result<Vec<LuaFanoutResult>, Error>,
{
let store = self.store.as_ref().ok_or_else(|| {
Error::Lua("section VM host values have not been injected".to_owned())
})?;
{
let mut slot = self
.jump_slot
.lock()
.map_err(|_| Error::Lua("jump slot was poisoned".to_owned()))?;
*slot = None;
}
let jump_slot = Arc::clone(&self.jump_slot);
let result = self.lua.scope(|scope| {
install_log(
&self.lua,
scope,
&self.execution,
observer,
section,
&self.log_budget,
&self.log_byte_budget,
)
.map_err(mlua::Error::external)?;
install_store_table(
&self.lua,
scope,
&self.lua.globals(),
store,
&self.execution,
observer,
section,
)
.map_err(mlua::Error::external)?;
install_tasks_table(&self.lua, tasks).map_err(mlua::Error::external)?;
if let Some(execute_callback) = execute_callback {
let execute_fn = scope
.create_function(|_, (target, input): (Value, Option<String>)| {
execute_callback(target, input).map_err(mlua::Error::external)
})
.map_err(mlua::Error::external)?;
self.lua
.globals()
.raw_set("execute", execute_fn)
.map_err(mlua::Error::external)?;
}
if jump_enabled {
let jump_fn = scope
.create_function(move |_, target: Value| -> mlua::Result<()> {
let heading = resolve_section_target(target)?;
let mut slot = jump_slot
.lock()
.map_err(|_| mlua::Error::external("jump slot poisoned"))?;
*slot = Some(heading);
Err(mlua::Error::external("jump transfer"))
})
.map_err(mlua::Error::external)?;
self.lua
.globals()
.raw_set("jump", jump_fn)
.map_err(mlua::Error::external)?;
}
if let Some(fanout_callback) = fanout_callback {
let fanout_fn = scope
.create_function(|lua, (worker, list): (String, String)| {
let replies =
fanout_callback(worker, list).map_err(mlua::Error::external)?;
let table = lua.create_table_with_capacity(replies.len(), 0)?;
for (i, reply) in replies.into_iter().enumerate() {
table.raw_set(i + 1, reply)?;
}
Ok(table)
})
.map_err(mlua::Error::external)?;
self.lua
.globals()
.raw_set("fanout", fanout_fn)
.map_err(mlua::Error::external)?;
}
let result = program
.load(&self.lua)
.map_err(mlua::Error::external)?
.call(());
finish_log_phase(&self.lua, result)
});
let jump = self.take_jump();
let cleanup = self.clear_control_globals();
let jump = jump?;
if let Some(heading) = jump {
cleanup?;
return Ok(LuaBlockResult::Jump(heading));
}
let returned = result.map_err(|error| program.map_runtime_error(&error));
match (returned, cleanup) {
(Err(execution), _) => Err(execution),
(Ok(_), Err(cleanup)) => Err(cleanup),
(Ok(values), Ok(())) => Ok(LuaBlockResult::Returned(scalar_return(values)?)),
}
}
fn clear_control_globals(&self) -> Result<()> {
let globals = self.lua.globals();
let mut first_error: Option<Error> = None;
for name in ["jump", "execute", "fanout", "tasks"] {
if let Err(error) = globals.raw_set(name, Value::Nil)
&& first_error.is_none()
{
first_error = Some(Error::lua(error));
}
}
match first_error {
Some(error) => Err(error),
None => Ok(()),
}
}
#[cfg(test)]
fn run_source(
&self,
source: &str,
observer: &dyn Observer,
section: &str,
) -> Result<Option<String>> {
let store = self.store.as_ref().ok_or_else(|| {
Error::Lua("section VM host values have not been injected".to_owned())
})?;
let returned: MultiValue = self
.lua
.scope(|scope| {
install_log(
&self.lua,
scope,
&self.execution,
observer,
section,
&self.log_budget,
&self.log_byte_budget,
)
.map_err(mlua::Error::external)?;
install_store_table(
&self.lua,
scope,
&self.lua.globals(),
store,
&self.execution,
observer,
section,
)
.map_err(mlua::Error::external)?;
let result = self.lua.load(source).eval();
finish_log_phase(&self.lua, result)
})
.map_err(Error::lua)?;
scalar_return(returned)
}
}
#[cfg(test)]
#[derive(Debug, Clone)]
pub(crate) struct LuaOutcome {
pub(crate) returned: Option<String>,
pub(crate) var: Json,
}
#[cfg(test)]
pub(crate) fn run_chunk(
source: &str,
args: &str,
sys: &Json,
store: &StoreRef,
execution: &str,
observer: &dyn Observer,
section: &str,
) -> Result<LuaOutcome> {
let mut vm = SectionVm::new(None, execution, observer, section)?;
vm.inject_host(args, sys, store, None)?;
let returned = vm.run_source(source, observer, section)?;
let var = vm.var()?;
Ok(LuaOutcome { returned, var })
}
pub(crate) fn snapshot_tool_scope(
bindings: &ToolBindings,
runtime: &Mutex<ToolRuntime>,
) -> Result<ToolScope> {
let runtime = runtime
.lock()
.map_err(|_| Error::Lua("tool declaration runtime was poisoned".to_owned()))?;
let aliases = bindings
.always
.iter()
.chain(runtime.added.iter())
.cloned()
.collect::<Vec<_>>();
let effective = aliases
.iter()
.map(|alias| binding_for_scope(bindings, &runtime, alias))
.collect::<Result<Vec<_>>>()?;
Ok(ToolScope {
bindings: effective,
})
}
pub(crate) fn binding_for_scope(
bindings: &ToolBindings,
runtime: &ToolRuntime,
alias: &str,
) -> Result<ToolBinding> {
let mut binding = bindings
.binding(alias)
.cloned()
.ok_or_else(|| Error::Lua(format!("tool alias {alias:?} has no frozen binding")))?;
if let Some(description) = runtime.description_overrides.get(alias) {
binding.model_description = Some(description.clone());
}
Ok(binding)
}