mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 09:35:59 -04:00
105 lines
3.8 KiB
Zig
105 lines
3.8 KiB
Zig
const std = @import("std");
|
|
const lp = @import("lightpanda");
|
|
const zenai = @import("zenai");
|
|
|
|
const App = @import("../App.zig");
|
|
const CDPNode = @import("../cdp/Node.zig");
|
|
const browser_tools = lp.tools;
|
|
|
|
const Self = @This();
|
|
|
|
allocator: std.mem.Allocator,
|
|
app: *App,
|
|
notification: *lp.Notification,
|
|
browser: lp.Browser,
|
|
session: *lp.Session,
|
|
node_registry: CDPNode.Registry,
|
|
tool_schema_arena: std.heap.ArenaAllocator,
|
|
/// Schemas parsed once at init from `browser_tools.tool_defs`. The slice and
|
|
/// every JSON `Value` inside live in `tool_schema_arena`.
|
|
tools: []const zenai.provider.Tool,
|
|
|
|
pub fn init(allocator: std.mem.Allocator, app: *App) !*Self {
|
|
const notification: *lp.Notification = try .init(allocator);
|
|
errdefer notification.deinit();
|
|
|
|
const self = try allocator.create(Self);
|
|
errdefer allocator.destroy(self);
|
|
|
|
self.* = .{
|
|
.allocator = allocator,
|
|
.app = app,
|
|
.notification = notification,
|
|
.browser = undefined,
|
|
.session = undefined,
|
|
.node_registry = CDPNode.Registry.init(allocator),
|
|
.tool_schema_arena = std.heap.ArenaAllocator.init(allocator),
|
|
.tools = &.{},
|
|
};
|
|
|
|
self.tools = try buildTools(self.tool_schema_arena.allocator());
|
|
|
|
try self.browser.init(app, .{}, null);
|
|
errdefer self.browser.deinit();
|
|
|
|
self.session = try self.browser.newSession(self.notification);
|
|
return self;
|
|
}
|
|
|
|
fn buildTools(arena: std.mem.Allocator) ![]const zenai.provider.Tool {
|
|
const tools = try arena.alloc(zenai.provider.Tool, browser_tools.tool_defs.len);
|
|
for (browser_tools.tool_defs, 0..) |t, i| {
|
|
const parsed = try std.json.parseFromSliceLeaky(std.json.Value, arena, t.input_schema, .{});
|
|
tools[i] = .{ .name = t.name, .description = t.description, .parameters = parsed };
|
|
}
|
|
return tools;
|
|
}
|
|
|
|
pub fn deinit(self: *Self) void {
|
|
self.tool_schema_arena.deinit();
|
|
self.node_registry.deinit();
|
|
self.browser.deinit();
|
|
self.notification.deinit();
|
|
self.allocator.destroy(self);
|
|
}
|
|
|
|
pub const CallError = browser_tools.ToolError || error{InvalidJsonArguments};
|
|
|
|
/// Allocator backing the parsed tool schemas. Lives for the executor's
|
|
/// lifetime, so callers can hand back slices that need the same lifetime
|
|
/// (e.g. derived caches over `getTools` output).
|
|
pub fn schemaAllocator(self: *Self) std.mem.Allocator {
|
|
return self.tool_schema_arena.allocator();
|
|
}
|
|
|
|
pub fn getCurrentUrl(self: *Self) []const u8 {
|
|
const page = self.session.currentFrame() orelse return "(no page loaded)";
|
|
return page.url;
|
|
}
|
|
|
|
/// Run a JavaScript expression and return the full result (text + error flag).
|
|
pub fn callEval(self: *Self, arena: std.mem.Allocator, script: []const u8) browser_tools.EvalResult {
|
|
return browser_tools.evalScript(arena, self.session, &self.node_registry, script);
|
|
}
|
|
|
|
pub fn call(self: *Self, arena: std.mem.Allocator, tool_name: []const u8, arguments_json: []const u8) CallError![]const u8 {
|
|
const arguments: ?std.json.Value = if (arguments_json.len > 0)
|
|
std.json.parseFromSliceLeaky(std.json.Value, arena, arguments_json, .{}) catch
|
|
return error.InvalidJsonArguments
|
|
else
|
|
null;
|
|
|
|
return self.callValue(arena, tool_name, arguments);
|
|
}
|
|
|
|
/// Like `call` but takes an already-parsed JSON value. Skips the
|
|
/// stringify+reparse for callers (e.g. PandaScript replay) that already
|
|
/// have a `std.json.Value`.
|
|
pub fn callValue(self: *Self, arena: std.mem.Allocator, tool_name: []const u8, arguments: ?std.json.Value) browser_tools.ToolError![]const u8 {
|
|
return browser_tools.call(arena, self.session, &self.node_registry, tool_name, arguments);
|
|
}
|
|
|
|
pub fn extractText(self: *Self, arena: std.mem.Allocator, selector: []const u8) browser_tools.EvalResult {
|
|
return browser_tools.extractText(arena, self.session, &self.node_registry, selector);
|
|
}
|