Files
browser/src/agent/CommandRunner.zig
Adrià Arrufat 55a50746fa script: move helper functions into Command union
Renames `Command.zig` to `command.zig` and moves associated functions
like `parse`, `toToolCall`, and `canHeal` directly into the `Command`
union. This simplifies usage from `Command.Command` to just `Command`.
2026-05-21 17:13:19 +02:00

72 lines
3.0 KiB
Zig

// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const lp = @import("lightpanda");
const browser_tools = lp.tools;
const Command = lp.script.Command;
const ToolExecutor = @import("ToolExecutor.zig");
const Terminal = @import("Terminal.zig");
const CommandRunner = @This();
tool_executor: *ToolExecutor,
terminal: *Terminal,
pub fn init(tool_executor: *ToolExecutor, terminal: *Terminal) CommandRunner {
return .{
.tool_executor = tool_executor,
.terminal = terminal,
};
}
/// Caller contract: `cmd` must not be `.natural_language`, `.comment`,
/// `.login`, or `.accept_cookies` — those are filtered upstream (see
/// `Agent.runRepl`) because they have no tool mapping.
pub fn executeWithResult(self: *CommandRunner, arena: std.mem.Allocator, cmd: Command) browser_tools.ToolResult {
switch (cmd) {
.extract => |schema| return self.execExtract(arena, schema),
.eval_js => |script| return browser_tools.ToolResult.unwrap(self.tool_executor.callEval(arena, script)),
else => {},
}
const tc = (cmd.toToolCall(arena, browser_tools.substituteEnvVars) catch
return .{ .text = "out of memory", .is_error = true }) orelse
return .{ .text = "internal: command has no tool mapping", .is_error = true };
return self.tool_executor.callValue(arena, tc.name, tc.args) catch |err| .{
.text = std.fmt.allocPrint(arena, "{s} failed: {s}", .{ tc.name, @errorName(err) }) catch "tool failed",
.is_error = true,
};
}
/// Data output (EXTRACT/EVAL/MARKDOWN/TREE) → stdout on success; everything
/// else, including failures from those same commands, → stderr.
pub fn printResult(self: *CommandRunner, cmd: Command, result: browser_tools.ToolResult) void {
if (cmd.producesData() and !result.is_error) {
self.terminal.printAssistant(result.text);
} else {
self.terminal.printActionResult(result.text);
}
}
fn execExtract(self: *CommandRunner, arena: std.mem.Allocator, raw_schema: []const u8) browser_tools.ToolResult {
const schema = browser_tools.substituteEnvVars(arena, raw_schema) catch
return .{ .text = "out of memory", .is_error = true };
return browser_tools.ToolResult.unwrap(self.tool_executor.extract(arena, schema));
}