summaryrefslogtreecommitdiff
path: root/src/lua_runtime.zig
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-06-10 10:06:07 -0600
committert <t@tjp.lol>2026-06-10 12:17:27 -0600
commitbb85e867bc938138f29fb45230169af1ba60761c (patch)
tree5ad1cf50274c61ccfffab7048b4a4e28003d82b5 /src/lua_runtime.zig
parentd26890bbc52e9f0050db40f208763a7f2b714ddd (diff)
Add addUserText helper; update all call sites
Conversation.addUserMessage now takes a []ContentBlock (symmetric with addAssistantMessage). Introduce a thin addUserText wrapper in agent.zig for the plain-text case and update every call site in agent.zig and anthropic_messages_json.zig accordingly.
Diffstat (limited to 'src/lua_runtime.zig')
-rw-r--r--src/lua_runtime.zig83
1 files changed, 75 insertions, 8 deletions
diff --git a/src/lua_runtime.zig b/src/lua_runtime.zig
index e26d017..2574fae 100644
--- a/src/lua_runtime.zig
+++ b/src/lua_runtime.zig
@@ -130,6 +130,15 @@ pub const LuaRuntime = struct {
// Install the real `panto.ext.emit` now that the bridge exists, so
// a Lua `emit(name, data)` reaches the native bus once attached.
lua_bridge.installEmit(L, @ptrCast(eb), lua_event_bridge.emitThunk);
+
+ // In production, `require('panto')` is wired to the native module
+ // by `installPantoModule` (after bootstrap stages `panto.so`).
+ // Unit tests have no staged `.so`, and load extension scripts that
+ // `require('panto')` for `panto.ext.*`; give them a fabricated
+ // `{ ext = ... }` module table so those `require`s resolve.
+ if (@import("builtin").is_test) {
+ lua_bridge.installTestPantoTable(L);
+ }
return self;
}
@@ -139,6 +148,62 @@ pub const LuaRuntime = struct {
return self.event_bridge;
}
+ /// Wire `require('panto')` to the native `libpanto-lua` module plus the
+ /// CLI's `ext` subtable. Run **after** luarocks bootstrap has staged
+ /// `panto.so` and configured `package.cpath`, and **before**
+ /// `installScheduler` (whose `_record_result` / wrapper closure live on
+ /// the module table).
+ ///
+ /// The sequence (all in Zig, via the C API, so the CLI never calls
+ /// `luaopen_panto` itself — it only speaks `require`):
+ ///
+ /// 1. `require('panto')` resolves the staged `panto.so` on `cpath`,
+ /// running its `luaopen_panto`, which returns a **fresh** table
+ /// (the agent/stream surface).
+ /// 2. Attach the `ext` subtable (built by `lua_bridge.install`) onto
+ /// that fresh native table — mutating the host's own copy, which
+ /// is safe precisely because the table is fresh, not shared.
+ /// 3. Install a `package.preload['panto']` loader returning that same
+ /// augmented table, so any *later* `require('panto')` (e.g. from
+ /// an extension) gets `ext` too. `require` already cached the
+ /// table in `package.loaded` from step 1; the preload entry wins
+ /// over `cpath` for any cache miss.
+ /// 4. Stash the augmented table under `panto_table_key` so internal
+ /// Zig (`pushPantoTable`, the scheduler) reaches it.
+ pub fn installPantoModule(self: *LuaRuntime) !void {
+ const L = self.L;
+
+ // 1. require('panto') -> native agent/stream table on the stack.
+ _ = c.lua_getglobal(L, "require");
+ _ = c.lua_pushlstring(L, "panto", 5);
+ if (c.lua_pcallk(L, 1, 1, 0, 0, null) != 0) {
+ logTopAsError(L, "panto-lua: require('panto') failed (was panto.so staged on cpath?)");
+ c.lua_settop(L, c.lua_gettop(L) - 1);
+ return RuntimeError.LuaInitFailed;
+ }
+ if (c.lua_type(L, -1) != lua_bridge.T_TABLE) {
+ c.lua_settop(L, c.lua_gettop(L) - 1);
+ return RuntimeError.LuaInitFailed;
+ }
+ // Stack: ..., panto (native, fresh)
+
+ // 2. panto.ext = <ext table>.
+ lua_bridge.pushExtTable(L);
+ if (c.lua_type(L, -1) != lua_bridge.T_TABLE) {
+ c.lua_settop(L, c.lua_gettop(L) - 2);
+ return RuntimeError.LuaInitFailed;
+ }
+ c.lua_setfield(L, -2, "ext"); // panto.ext = ext; pops ext
+ // Stack: ..., panto (augmented)
+
+ // 3. Set package.preload['panto'] to return this augmented table.
+ lua_bridge.installPantoPreload(L); // leaves `panto` on the stack
+
+ // 4. Stash in the registry slot for internal Zig access.
+ lua_bridge.setPantoTable(L); // dups + stores; leaves `panto`
+ c.lua_settop(L, c.lua_gettop(L) - 1); // pop the leftover `panto`
+ }
+
/// Install the libuv-driven coroutine scheduler:
/// - Register `panto._record_result` (C function with `self` as
/// light-userdata upvalue) so the wrapper closure can hand
@@ -148,7 +213,9 @@ pub const LuaRuntime = struct {
/// - Cache `require("luv").run` for fast per-tick access.
///
/// Must be called after luarocks bootstrap has installed luv,
- /// otherwise the `require("luv")` step will fail.
+ /// otherwise the `require("luv")` step will fail. Also after
+ /// `installPantoModule`, since the scheduler writes onto the module
+ /// table.
pub fn installScheduler(self: *LuaRuntime) !void {
try installRecordResult(self);
try installWrapperClosure(self);
@@ -261,15 +328,15 @@ pub const LuaRuntime = struct {
//
// Push `panto.ext.register_tool` *first*, then load+run the chunk
// so its return value naturally lands above it; calling pcall then
- // consumes both in the right order.
+ // consumes both in the right order. We reach `register_tool`
+ // through the `ext` table directly (it exists from `install`,
+ // independent of whether the native module table has been wired).
const L = self.L;
- lua_bridge.pushPantoTable(L);
- _ = c.lua_getfield(L, -1, "ext");
+ lua_bridge.pushExtTable(L);
_ = c.lua_getfield(L, -1, "register_tool");
- // Stack: ..., panto, ext, register_tool. Collapse to just
- // register_tool by overwriting the lowest of the three.
- c.lua_copy(L, -1, -3); // overwrite `panto` with `register_tool`
- c.lua_settop(L, c.lua_gettop(L) - 2); // pop ext + duplicate
+ // Stack: ..., ext, register_tool. Collapse to just register_tool.
+ c.lua_copy(L, -1, -2); // overwrite `ext` with `register_tool`
+ c.lua_settop(L, c.lua_gettop(L) - 1); // pop duplicate
// Stack: ..., register_tool
if (c.luaL_loadfilex(L, path_z.ptr, null) != 0) {