agent: simplify tool allocation and schema info

Remove `tool_schema_arena` from `Agent` and allocate the tools slice
directly using the general allocator.

Remove the pre-calculated `is_multiline_capable` field from
`SchemaInfo` and compute it dynamically in `isMultiLineCapable()`.
This commit is contained in:
Adrià Arrufat
2026-05-21 22:24:27 +02:00
parent 3b2956c400
commit e89fb9b485
2 changed files with 8 additions and 14 deletions

View File

@@ -143,9 +143,8 @@ notification: *lp.Notification,
browser: lp.Browser,
session: *lp.Session,
node_registry: CDPNode.Registry,
tool_schema_arena: std.heap.ArenaAllocator,
/// Provider-facing tool list, built from `SlashCommand.globalSchemas()`. The
/// slice lives in `tool_schema_arena`; the JSON `Value` each entry points at
/// Provider-facing tool list, built from `SlashCommand.globalSchemas()`. Slice
/// is owned by `allocator`; the `parameters` JSON `Value` each entry points at
/// lives in the schema module's process-lifetime arena.
tools: []const zenai.provider.Tool,
terminal: Terminal,
@@ -244,7 +243,6 @@ pub fn init(allocator: std.mem.Allocator, app: *App, opts: Config.Agent) !*Agent
.browser = undefined,
.session = undefined,
.node_registry = CDPNode.Registry.init(allocator),
.tool_schema_arena = .init(allocator),
.tools = &.{},
.terminal = .init(allocator, history_path, Config.agentVerbosity(opts), will_repl),
.cmd_runner = undefined,
@@ -260,12 +258,12 @@ pub fn init(allocator: std.mem.Allocator, app: *App, opts: Config.Agent) !*Agent
.one_shot_task = opts.task,
.one_shot_attachments = if (opts.attach.items.len == 0) null else opts.attach.items,
};
errdefer self.tool_schema_arena.deinit();
errdefer self.node_registry.deinit();
errdefer self.terminal.deinit();
errdefer self.message_arena.deinit();
self.tools = try buildTools(self.tool_schema_arena.allocator());
self.tools = try buildTools(allocator);
errdefer allocator.free(self.tools);
try self.browser.init(app, .{}, null);
errdefer self.browser.deinit();
@@ -311,7 +309,7 @@ pub fn deinit(self: *Agent) void {
self.terminal.deinit();
self.message_arena.deinit();
self.messages.deinit(self.allocator);
self.tool_schema_arena.deinit();
self.allocator.free(self.tools);
self.node_registry.deinit();
self.browser.deinit();
self.notification.deinit();
@@ -327,9 +325,9 @@ pub fn deinit(self: *Agent) void {
self.allocator.destroy(self);
}
fn buildTools(arena: std.mem.Allocator) ![]const zenai.provider.Tool {
fn buildTools(allocator: std.mem.Allocator) ![]const zenai.provider.Tool {
const schemas = SlashCommand.globalSchemas();
const tools = try arena.alloc(zenai.provider.Tool, schemas.len);
const tools = try allocator.alloc(zenai.provider.Tool, schemas.len);
for (schemas, 0..) |s, i| {
tools[i] = .{ .name = s.tool_name, .description = s.description, .parameters = s.parameters };
}

View File

@@ -66,14 +66,13 @@ pub const SchemaInfo = struct {
recorded: bool,
can_heal: bool,
produces_data: bool,
is_multiline_capable: bool,
parameters: std.json.Value,
/// True when this tool's args fit a multi-line `/<name> '''…'''` opener:
/// exactly one required field, and that field is a string. Used by
/// `Command.ScriptIterator` to detect block openers.
pub fn isMultiLineCapable(self: *const SchemaInfo) bool {
return self.is_multiline_capable;
return self.required.len == 1 and self.fieldType(self.required[0]) == .string;
}
pub fn findField(self: *const SchemaInfo, key: []const u8) ?FieldEntry {
@@ -115,7 +114,6 @@ fn buildOne(arena: std.mem.Allocator, td: browser_tools.ToolDef, parsed: std.jso
.recorded = td.recorded,
.can_heal = td.can_heal,
.produces_data = td.produces_data,
.is_multiline_capable = false,
.parameters = parsed,
};
@@ -153,8 +151,6 @@ fn buildOne(arena: std.mem.Allocator, td: browser_tools.ToolDef, parsed: std.jso
info.hints = try buildHints(arena, info.required, info.fields);
std.debug.assert(info.hints.len <= max_hint_slots);
info.is_multiline_capable = (info.required.len == 1 and info.fieldType(info.required[0]) == .string);
return info;
}