summaryrefslogtreecommitdiff
path: root/src/lua_runtime.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/lua_runtime.zig')
-rw-r--r--src/lua_runtime.zig134
1 files changed, 67 insertions, 67 deletions
diff --git a/src/lua_runtime.zig b/src/lua_runtime.zig
index 7d18fe9..73078c0 100644
--- a/src/lua_runtime.zig
+++ b/src/lua_runtime.zig
@@ -548,9 +548,9 @@ const Slot = struct {
/// `true` if the handler returned cleanly, `false` if it raised
/// via the `pcall` wrapping.
ok: bool = false,
- /// Result payload as owned bytes. Allocated from `allocator`.
- /// Caller frees.
- value: ?[]u8 = null,
+ /// Result payload as owned parts. Allocated from `allocator`.
+ /// Caller frees via `panto.freeResultParts`.
+ value: ?[]panto.ResultPart = null,
/// On `ok = false`, an owned copy of the error message.
err_msg: ?[]u8 = null,
};
@@ -703,11 +703,11 @@ fn runBatch(
continue;
}
if (slot.ok) {
- results[i] = .{ .ok = slot.value orelse try allocator.dupe(u8, "") };
+ results[i] = .{ .ok = slot.value orelse try panto.textResult(allocator, "") };
// Free the err_msg if both ended up set somehow.
if (slot.err_msg) |m| allocator.free(m);
} else {
- if (slot.value) |v| allocator.free(v);
+ if (slot.value) |v| panto.freeResultParts(allocator, v);
std.log.warn(
"panto-lua: tool '{s}' failed: {s}",
.{
@@ -734,12 +734,13 @@ fn formatToolError(
allocator: Allocator,
tool_name: []const u8,
message: []const u8,
-) ![]u8 {
- return std.fmt.allocPrint(
+) ![]panto.ResultPart {
+ const text = try std.fmt.allocPrint(
allocator,
"panto-lua: tool '{s}' failed: {s}",
.{ tool_name, message },
);
+ return panto.ownedTextResult(allocator, text);
}
/// Start one coroutine: create a thread under the runtime's lua_State,
@@ -815,7 +816,7 @@ fn runLegacySync(
};
var err_msg: ?[]u8 = null;
defer if (err_msg) |m| allocator.free(m);
- const out_bytes = invokeCoroutineSync(
+ const out_parts = invokeCoroutineSync(
self.L,
handler_ref,
call.input,
@@ -834,7 +835,7 @@ fn runLegacySync(
) catch return .{ .err = error.OutOfMemory };
return .{ .ok = bytes };
};
- return .{ .ok = out_bytes };
+ return .{ .ok = out_parts };
}
/// Run a handler synchronously, with no event loop. On Lua-level
@@ -847,7 +848,7 @@ fn invokeCoroutineSync(
input: []const u8,
allocator: Allocator,
err_msg_out: *?[]u8,
-) ![]u8 {
+) ![]panto.ResultPart {
const co = c.lua_newthread(L) orelse return RuntimeError.LuaInitFailed;
defer c.lua_settop(L, c.lua_gettop(L) - 1);
@@ -1058,6 +1059,29 @@ fn logTopAsError(L: *c.lua_State, prefix: []const u8) void {
const testing = std.testing;
+/// Test helper: concatenated text across a result's text parts.
+/// Returns the first text part's items (sufficient for current tests,
+/// which return a single text part).
+fn okText(result: panto.ToolCallResult) []const u8 {
+ switch (result) {
+ .ok => |parts| {
+ for (parts) |p| {
+ if (p == .text) return p.text;
+ }
+ return "";
+ },
+ .err => return "",
+ }
+}
+
+/// Test helper: free a results slice (parts on `.ok`).
+fn freeResults(results: []panto.ToolCallResult) void {
+ for (results) |r| switch (r) {
+ .ok => |b| panto.freeResultParts(testing.allocator, b),
+ .err => {},
+ };
+}
+
fn writeTempScript(dir: Io.Dir, name: []const u8, source: []const u8) ![]const u8 {
try dir.writeFile(testing.io, .{ .sub_path = name, .data = source });
var buf: [std.fs.max_path_bytes]u8 = undefined;
@@ -1204,14 +1228,11 @@ test "invokeBatch runs each call through a coroutine and returns the result" {
};
try src.vtable.invoke_batch(src.ctx, &calls, &results, testing.allocator);
- defer for (results) |r| switch (r) {
- .ok => |b| testing.allocator.free(b),
- .err => {},
- };
+ defer freeResults(&results);
- try testing.expectEqualStrings("got: hello", results[0].ok);
- try testing.expectEqualStrings("HI!", results[1].ok);
- try testing.expectEqualStrings("got: again", results[2].ok);
+ try testing.expectEqualStrings("got: hello", okText(results[0]));
+ try testing.expectEqualStrings("HI!", okText(results[1]));
+ try testing.expectEqualStrings("got: again", okText(results[2]));
}
test "module-global state survives across calls in the same runtime" {
@@ -1250,14 +1271,11 @@ test "module-global state survives across calls in the same runtime" {
};
try src.vtable.invoke_batch(src.ctx, &calls, &results, testing.allocator);
- defer for (results) |r| switch (r) {
- .ok => |b| testing.allocator.free(b),
- .err => {},
- };
+ defer freeResults(&results);
- try testing.expectEqualStrings("1", results[0].ok);
- try testing.expectEqualStrings("2", results[1].ok);
- try testing.expectEqualStrings("3", results[2].ok);
+ try testing.expectEqualStrings("1", okText(results[0]));
+ try testing.expectEqualStrings("2", okText(results[1]));
+ try testing.expectEqualStrings("3", okText(results[2]));
// And a second batch keeps the counter going.
var more: [1]panto.ToolCallResult = .{.{ .err = error.SourceDroppedCall }};
@@ -1267,8 +1285,8 @@ test "module-global state survives across calls in the same runtime" {
&more,
testing.allocator,
);
- defer testing.allocator.free(more[0].ok);
- try testing.expectEqualStrings("4", more[0].ok);
+ defer freeResults(&more);
+ try testing.expectEqualStrings("4", okText(more[0]));
}
test "handler crash: per-call error surfaces, sibling calls succeed" {
@@ -1306,12 +1324,9 @@ test "handler crash: per-call error surfaces, sibling calls succeed" {
.{ .err = error.SourceDroppedCall },
};
try src.vtable.invoke_batch(src.ctx, &calls, &results, testing.allocator);
- defer for (results) |r| switch (r) {
- .ok => |b| testing.allocator.free(b),
- .err => {},
- };
+ defer freeResults(&results);
- try testing.expectEqualStrings("fine", results[0].ok);
+ try testing.expectEqualStrings("fine", okText(results[0]));
// A handler crash is surfaced as an *ok* result whose payload is
// the formatted error message — not as `.err` — because libpanto
// would otherwise abort the entire turn on per-call `.err`. The
@@ -1319,11 +1334,11 @@ test "handler crash: per-call error surfaces, sibling calls succeed" {
// prefix and includes the Lua-side error message.
try testing.expect(std.mem.startsWith(
u8,
- results[1].ok,
+ okText(results[1]),
"panto-lua: tool 'boom' failed:",
));
- try testing.expect(std.mem.indexOf(u8, results[1].ok, "kaboom") != null);
- try testing.expectEqualStrings("fine", results[2].ok);
+ try testing.expect(std.mem.indexOf(u8, okText(results[1]), "kaboom") != null);
+ try testing.expectEqualStrings("fine", okText(results[2]));
}
test "directory-style extension can require sibling modules" {
@@ -1369,8 +1384,8 @@ test "directory-style extension can require sibling modules" {
const calls = [_]panto.ToolCall{.{ .tool_name = "shout", .input = "{\"text\":\"hi\"}" }};
var results: [1]panto.ToolCallResult = .{.{ .err = error.SourceDroppedCall }};
try src.vtable.invoke_batch(src.ctx, &calls, &results, testing.allocator);
- defer testing.allocator.free(results[0].ok);
- try testing.expectEqualStrings("HI!", results[0].ok);
+ defer freeResults(&results);
+ try testing.expectEqualStrings("HI!", okText(results[0]));
}
test "yielding handler with no event loop surfaces LuaHandlerYielded" {
@@ -1395,10 +1410,7 @@ test "yielding handler with no event loop surfaces LuaHandlerYielded" {
const calls = [_]panto.ToolCall{.{ .tool_name = "sleeper", .input = "{}" }};
var results: [1]panto.ToolCallResult = .{.{ .err = error.SourceDroppedCall }};
try src.vtable.invoke_batch(src.ctx, &calls, &results, testing.allocator);
- defer for (results) |r| switch (r) {
- .ok => |b| testing.allocator.free(b),
- .err => {},
- };
+ defer freeResults(&results);
// Same policy as the crash test: the failure is surfaced as `.ok`
// text so libpanto doesn't abort the turn. The error type name
@@ -1406,10 +1418,10 @@ test "yielding handler with no event loop surfaces LuaHandlerYielded" {
// sync path's formatToolError call.
try testing.expect(std.mem.startsWith(
u8,
- results[0].ok,
+ okText(results[0]),
"panto-lua: tool 'sleeper' failed:",
));
- try testing.expect(std.mem.indexOf(u8, results[0].ok, "LuaHandlerYielded") != null);
+ try testing.expect(std.mem.indexOf(u8, okText(results[0]), "LuaHandlerYielded") != null);
}
// Integration test: requires a `$PANTO_HOME` with luv already
@@ -1488,13 +1500,10 @@ test "scheduler: yielding handler is resumed by libuv" {
.{ .err = error.SourceDroppedCall },
};
try src.vtable.invoke_batch(src.ctx, &calls, &results, testing.allocator);
- defer for (results) |r| switch (r) {
- .ok => |b| testing.allocator.free(b),
- .err => {},
- };
+ defer freeResults(&results);
- try testing.expectEqualStrings("awake", results[0].ok);
- try testing.expectEqualStrings("awake", results[1].ok);
+ try testing.expectEqualStrings("awake", okText(results[0]));
+ try testing.expectEqualStrings("awake", okText(results[1]));
}
// Contract-violation guard: a handler that yields WITHOUT arming any
@@ -1537,14 +1546,11 @@ test "scheduler: handler that yields without arming libuv work is surfaced as an
.{ .err = error.SourceDroppedCall },
};
try src.vtable.invoke_batch(src.ctx, &calls, &results, testing.allocator);
- defer for (results) |r| switch (r) {
- .ok => |b| testing.allocator.free(b),
- .err => {},
- };
+ defer freeResults(&results);
- try testing.expect(std.mem.startsWith(u8, results[0].ok, "panto-lua: tool 'abandon' failed:"));
- try testing.expect(std.mem.indexOf(u8, results[0].ok, "still suspended") != null);
- try testing.expectEqualStrings("ok", results[1].ok);
+ try testing.expect(std.mem.startsWith(u8, okText(results[0]), "panto-lua: tool 'abandon' failed:"));
+ try testing.expect(std.mem.indexOf(u8, okText(results[0]), "still suspended") != null);
+ try testing.expectEqualStrings("ok", okText(results[1]));
}
/// Absolute path to the repo's `agent/tools` directory, derived from
@@ -1618,13 +1624,10 @@ test "scheduler: two real std.shell calls in one batch do not deadlock" {
.{ .err = error.SourceDroppedCall },
};
try src.vtable.invoke_batch(src.ctx, &calls, &results, testing.allocator);
- defer for (results) |r| switch (r) {
- .ok => |b| testing.allocator.free(b),
- .err => {},
- };
+ defer freeResults(&results);
- try testing.expect(std.mem.indexOf(u8, results[0].ok, "first-done") != null);
- try testing.expect(std.mem.indexOf(u8, results[1].ok, "second-done") != null);
+ try testing.expect(std.mem.indexOf(u8, okText(results[0]), "first-done") != null);
+ try testing.expect(std.mem.indexOf(u8, okText(results[1]), "second-done") != null);
}
// Reproduction: two REAL `std.read` calls in one batch. read.lua uses
@@ -1658,13 +1661,10 @@ test "scheduler: two real std.read calls in one batch do not deadlock" {
};
try src.vtable.invoke_batch(src.ctx, &calls, &results, testing.allocator);
- defer for (results) |r| switch (r) {
- .ok => |b| testing.allocator.free(b),
- .err => {},
- };
+ defer freeResults(&results);
- try testing.expect(results[0].ok.len > 0);
- try testing.expect(results[1].ok.len > 0);
+ try testing.expect(okText(results[0]).len > 0);
+ try testing.expect(okText(results[1]).len > 0);
}
test "loadExtension: duplicate tool name from a second extension errors" {