diff options
| author | t <t@tjp.lol> | 2026-06-10 10:06:07 -0600 |
|---|---|---|
| committer | t <t@tjp.lol> | 2026-06-10 12:17:27 -0600 |
| commit | bb85e867bc938138f29fb45230169af1ba60761c (patch) | |
| tree | 5ad1cf50274c61ccfffab7048b4a4e28003d82b5 /src/lua_runtime.zig | |
| parent | d26890bbc52e9f0050db40f208763a7f2b714ddd (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.zig | 83 |
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) { |
