Skip to main content

mlua_swarm/lua/
bridge.rs

1//! Lua bridge — parses Lua-authored BluePrints (a Lua-table
2//! representation of `mlua_flow_ir::Node`) into a Rust `Node`, plus the
3//! initial `ctx` when the Lua source also builds one.
4//!
5//! This module is **parse-only**. Execution goes through
6//! `crate::service::TaskLaunchService` (compile, `service::linker::link`,
7//! `Engine::dispatch_attempt_with`), not through this bridge — the
8//! one-shot glue that used to live here (build a throwaway `mlua::Lua`,
9//! parse, then drive the result straight through an `EngineDispatcher`)
10//! was retired once `TaskLaunchService` became the sole production entry
11//! point.
12//!
13//! `mlua-flow-ir` also ships a sync path (`flow.eval`) that lets a
14//! dispatcher be written on the Lua side, but engine-async ↔ sync
15//! impedance forces a `block_on` there. That would violate our
16//! discipline, so we do not use it.
17
18use crate::core::errors::EngineError;
19use mlua::LuaSerdeExt;
20use mlua_flow_ir::Node;
21use serde_json::Value;
22
23/// Load a Lua source, evaluate it, and pull out a BluePrint `Node`
24/// (`mlua_flow_ir::Node`). The Lua source must ultimately
25/// **`return` a table** — one that follows the flow.ir schema.
26///
27/// Example:
28/// ```lua
29/// return {
30///   kind = "seq",
31///   children = {
32///     { kind = "step", ref = "agent-a",
33///       in_  = {op="path", at="$.input"},
34///       out  = {op="path", at="$.mid"} },
35///     ...
36///   }
37/// }
38/// ```
39///
40/// Note: the serde tag is `kind` for `Node` and `op` for `Expr`;
41/// field names are the same snake_case as the Rust struct. `ref` is a
42/// reserved word, so the Lua key stays `ref` and serde renames
43/// `ref` ↔ `ref_`; the same holds for `in`.
44pub fn parse_lua_blueprint(lua_src: &str) -> Result<Node, EngineError> {
45    let lua = mlua::Lua::new();
46    let bp_val: mlua::Value = lua
47        .load(lua_src)
48        .eval()
49        .map_err(|e| EngineError::Internal(format!("lua eval: {e}")))?;
50    let bp: Node = lua
51        .from_value(bp_val)
52        .map_err(|e| EngineError::Internal(format!("lua → Node parse: {e}")))?;
53    Ok(bp)
54}
55
56/// Load a Lua source, and also build the initial `ctx` (a Lua table)
57/// on the Lua side and convert it to a JSON `Value`. Returns
58/// `(BluePrint, initial ctx)`. The Lua source is expected to return a
59/// table of the form `return { bp = ..., ctx = ... }`.
60pub fn parse_lua_blueprint_with_ctx(lua_src: &str) -> Result<(Node, Value), EngineError> {
61    let lua = mlua::Lua::new();
62    let outer: mlua::Table = lua
63        .load(lua_src)
64        .eval()
65        .map_err(|e| EngineError::Internal(format!("lua eval: {e}")))?;
66
67    let bp_val: mlua::Value = outer
68        .get("bp")
69        .map_err(|e| EngineError::Internal(format!("table missing `bp`: {e}")))?;
70    let ctx_val: mlua::Value = outer
71        .get("ctx")
72        .map_err(|e| EngineError::Internal(format!("table missing `ctx`: {e}")))?;
73
74    let bp: Node = lua
75        .from_value(bp_val)
76        .map_err(|e| EngineError::Internal(format!("lua → Node parse: {e}")))?;
77    let ctx: Value = lua
78        .from_value(ctx_val)
79        .map_err(|e| EngineError::Internal(format!("lua → ctx parse: {e}")))?;
80    Ok((bp, ctx))
81}