mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 09:35:59 -04:00
agent: improve eval parsing and command recording
- Extract `getJsonString` helper in Agent.zig. - Match specific triple quote types in EVAL blocks to allow nested quotes. - Use `cmd.format` and larger buffers in Recorder.zig.
This commit is contained in:
@@ -415,31 +415,22 @@ fn toolCallToCommand(arena: std.mem.Allocator, tool_name: []const u8, arguments:
|
||||
else => return null,
|
||||
};
|
||||
|
||||
const getString = struct {
|
||||
fn f(o: std.json.ObjectMap, key: []const u8) ?[]const u8 {
|
||||
return switch (o.get(key) orelse return null) {
|
||||
.string => |s| s,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
}.f;
|
||||
|
||||
return switch (action) {
|
||||
.goto => .{ .goto = getString(obj, "url") orelse return null },
|
||||
.click => .{ .click = getString(obj, "selector") orelse return null },
|
||||
.hover => .{ .hover = getString(obj, "selector") orelse return null },
|
||||
.eval => .{ .eval_js = getString(obj, "script") orelse return null },
|
||||
.waitForSelector => .{ .wait = getString(obj, "selector") orelse return null },
|
||||
.goto => .{ .goto = getJsonString(obj, "url") orelse return null },
|
||||
.click => .{ .click = getJsonString(obj, "selector") orelse return null },
|
||||
.hover => .{ .hover = getJsonString(obj, "selector") orelse return null },
|
||||
.eval => .{ .eval_js = getJsonString(obj, "script") orelse return null },
|
||||
.waitForSelector => .{ .wait = getJsonString(obj, "selector") orelse return null },
|
||||
.fill => .{ .type_cmd = .{
|
||||
.selector = getString(obj, "selector") orelse return null,
|
||||
.value = getString(obj, "value") orelse return null,
|
||||
.selector = getJsonString(obj, "selector") orelse return null,
|
||||
.value = getJsonString(obj, "value") orelse return null,
|
||||
} },
|
||||
.selectOption => .{ .select = .{
|
||||
.selector = getString(obj, "selector") orelse return null,
|
||||
.value = getString(obj, "value") orelse return null,
|
||||
.selector = getJsonString(obj, "selector") orelse return null,
|
||||
.value = getJsonString(obj, "value") orelse return null,
|
||||
} },
|
||||
.setChecked => .{ .check = .{
|
||||
.selector = getString(obj, "selector") orelse return null,
|
||||
.selector = getJsonString(obj, "selector") orelse return null,
|
||||
.checked = switch (obj.get("checked") orelse return null) {
|
||||
.bool => |b| b,
|
||||
else => return null,
|
||||
@@ -461,6 +452,13 @@ fn toolCallToCommand(arena: std.mem.Allocator, tool_name: []const u8, arguments:
|
||||
};
|
||||
}
|
||||
|
||||
fn getJsonString(o: std.json.ObjectMap, key: []const u8) ?[]const u8 {
|
||||
return switch (o.get(key) orelse return null) {
|
||||
.string => |s| s,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
fn getEnvApiKey(provider_type: Config.AiProvider) ?[:0]const u8 {
|
||||
return switch (provider_type) {
|
||||
.anthropic => std.posix.getenv("ANTHROPIC_API_KEY"),
|
||||
|
||||
@@ -248,9 +248,9 @@ pub const ScriptIterator = struct {
|
||||
const trimmed = std.mem.trim(u8, line, &std.ascii.whitespace);
|
||||
if (trimmed.len == 0) continue;
|
||||
|
||||
if (isEvalTripleQuote(trimmed)) {
|
||||
if (isEvalTripleQuote(trimmed)) |quote_type| {
|
||||
const start_line = self.line_num;
|
||||
if (self.collectEvalBlock()) |js| {
|
||||
if (self.collectEvalBlock(quote_type)) |js| {
|
||||
return .{
|
||||
.line_num = start_line,
|
||||
.raw_line = trimmed,
|
||||
@@ -274,21 +274,23 @@ pub const ScriptIterator = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
fn isEvalTripleQuote(line: []const u8) bool {
|
||||
fn isEvalTripleQuote(line: []const u8) ?[]const u8 {
|
||||
const cmd_end = std.mem.indexOfAny(u8, line, &std.ascii.whitespace) orelse line.len;
|
||||
const cmd_word = line[0..cmd_end];
|
||||
if (!std.ascii.eqlIgnoreCase(cmd_word, "EVAL")) return false;
|
||||
if (!std.ascii.eqlIgnoreCase(cmd_word, "EVAL")) return null;
|
||||
const rest = std.mem.trim(u8, line[cmd_end..], &std.ascii.whitespace);
|
||||
return std.mem.startsWith(u8, rest, "\"\"\"") or std.mem.startsWith(u8, rest, "'''");
|
||||
if (std.mem.startsWith(u8, rest, "\"\"\"")) return "\"\"\"";
|
||||
if (std.mem.startsWith(u8, rest, "'''")) return "'''";
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Collect lines until closing triple quote (""" or '''), return the JS content.
|
||||
fn collectEvalBlock(self: *ScriptIterator) ?[]const u8 {
|
||||
/// Collect lines until matching closing triple quote, return the JS content.
|
||||
fn collectEvalBlock(self: *ScriptIterator, quote_type: []const u8) ?[]const u8 {
|
||||
var parts: std.ArrayListUnmanaged(u8) = .empty;
|
||||
while (self.lines.next()) |line| {
|
||||
self.line_num += 1;
|
||||
const trimmed = std.mem.trim(u8, line, &std.ascii.whitespace);
|
||||
if (std.mem.eql(u8, trimmed, "\"\"\"") or std.mem.eql(u8, trimmed, "'''")) {
|
||||
if (std.mem.eql(u8, trimmed, quote_type)) {
|
||||
return parts.toOwnedSlice(self.allocator) catch null;
|
||||
}
|
||||
if (parts.items.len > 0) {
|
||||
@@ -671,6 +673,21 @@ test "ScriptIterator unterminated EVAL" {
|
||||
try std.testing.expectEqualStrings("unterminated EVAL block", e1.command.natural_language);
|
||||
}
|
||||
|
||||
test "ScriptIterator multi-line EVAL mismatched triple quote" {
|
||||
const script =
|
||||
\\EVAL """
|
||||
\\ const s = " ''' ";
|
||||
\\ console.log(s);
|
||||
\\"""
|
||||
;
|
||||
var iter: ScriptIterator = .init(script, std.testing.allocator);
|
||||
|
||||
const e1 = iter.next().?;
|
||||
try std.testing.expect(e1.command == .eval_js);
|
||||
try std.testing.expectEqualStrings(" const s = \" ''' \";\n console.log(s);", e1.command.eval_js);
|
||||
std.testing.allocator.free(e1.command.eval_js);
|
||||
}
|
||||
|
||||
test "trimMatchingQuotes" {
|
||||
const cases = [_]struct { in: []const u8, out: ?[]const u8 }{
|
||||
.{ .in = "'hello'", .out = "hello" },
|
||||
|
||||
@@ -37,16 +37,22 @@ pub fn record(self: *Self, cmd: Command.Command) void {
|
||||
const f = self.file orelse return;
|
||||
if (!cmd.isRecorded()) return;
|
||||
|
||||
var buf: [1024]u8 = undefined;
|
||||
const line = std.fmt.bufPrint(&buf, "{f}\n", .{cmd}) catch return;
|
||||
_ = f.write(line) catch return;
|
||||
var buf: [8192]u8 = undefined;
|
||||
var fba = std.heap.FixedBufferAllocator.init(&buf);
|
||||
var aw: std.Io.Writer.Allocating = .init(fba.allocator());
|
||||
|
||||
cmd.format(&aw.writer) catch return;
|
||||
aw.writer.writeByte('\n') catch return;
|
||||
|
||||
_ = f.write(aw.written()) catch return;
|
||||
self.needs_separator = true;
|
||||
}
|
||||
|
||||
pub fn recordComment(self: *Self, comment: []const u8) void {
|
||||
const f = self.file orelse return;
|
||||
var buf: [1024]u8 = undefined;
|
||||
const prefix: []const u8 = if (self.needs_separator) "\n# " else "# ";
|
||||
|
||||
var buf: [8192]u8 = undefined;
|
||||
const line = std.fmt.bufPrint(&buf, "{s}{s}\n", .{ prefix, comment }) catch return;
|
||||
_ = f.write(line) catch return;
|
||||
self.needs_separator = true;
|
||||
|
||||
Reference in New Issue
Block a user