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}