mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-08-02 10:47:15 -04:00
Merge pull request #3073 from lightpanda-io/agent-tool-cap
agent: reduce tool output cap to 64KB except for extract
This commit is contained in:
@@ -1535,7 +1535,7 @@ fn recordSlashToolCall(
|
||||
|
||||
// capToolOutput returns its input unchanged under the cap; dupe so content
|
||||
// doesn't alias the caller's per-iteration arena.
|
||||
const capped = capToolOutput(ma, result.text);
|
||||
const capped = capToolOutput(ma, tool_name, result.text);
|
||||
const content = if (capped.ptr == result.text.ptr) try ma.dupe(u8, capped) else capped;
|
||||
|
||||
const tool_results = try ma.alloc(zenai.provider.ToolResult, 1);
|
||||
@@ -1807,15 +1807,23 @@ fn buildUserMessageParts(
|
||||
return parts.toOwnedSlice(ma);
|
||||
}
|
||||
|
||||
// Cap per-call tool output so heavy pages don't balloon the message arena (and
|
||||
// the next request body) without bound.
|
||||
const tool_output_max_bytes: usize = 1 * 1024 * 1024;
|
||||
// Tool results are re-sent with every subsequent turn, so an unscoped read of
|
||||
// a huge page is paid once per remaining turn — the cap bounds that growth.
|
||||
// `extract` is exempt up to the loose backstop: its output is schema-shaped
|
||||
// JSON the model explicitly asked for, and truncation would corrupt it.
|
||||
const tool_output_max_bytes: usize = 64 * 1024;
|
||||
const extract_output_max_bytes: usize = 1024 * 1024;
|
||||
|
||||
fn capToolOutput(allocator: std.mem.Allocator, output: []const u8) []const u8 {
|
||||
if (output.len <= tool_output_max_bytes) return output;
|
||||
const prefix = string.truncateUtf8(output, tool_output_max_bytes);
|
||||
var suffix_buf: [64]u8 = undefined;
|
||||
const suffix = std.fmt.bufPrint(&suffix_buf, "\n...[truncated, original {d} bytes]", .{output.len}) catch return prefix;
|
||||
fn toolOutputCap(tool_name: []const u8) usize {
|
||||
return if (std.mem.eql(u8, tool_name, "extract")) extract_output_max_bytes else tool_output_max_bytes;
|
||||
}
|
||||
|
||||
fn capToolOutput(allocator: std.mem.Allocator, tool_name: []const u8, output: []const u8) []const u8 {
|
||||
const cap = toolOutputCap(tool_name);
|
||||
if (output.len <= cap) return output;
|
||||
const prefix = string.truncateUtf8(output, cap);
|
||||
var suffix_buf: [128]u8 = undefined;
|
||||
const suffix = std.fmt.bufPrint(&suffix_buf, "\n...[truncated, original {d} bytes — re-read scoped (selector/backendNodeId)]", .{output.len}) catch return prefix;
|
||||
return std.mem.concat(allocator, u8, &.{ prefix, suffix }) catch prefix;
|
||||
}
|
||||
|
||||
@@ -1836,7 +1844,7 @@ fn handleToolCall(ctx: *anyopaque, allocator: std.mem.Allocator, tool_name: []co
|
||||
defer self.terminal.spinner.setThinking();
|
||||
|
||||
const outcome: zenai.provider.Client.ToolHandler.Result = if (browser_tools.call(allocator, self.session, &self.node_registry, tool_name, arguments)) |result|
|
||||
.{ .content = capToolOutput(allocator, result.text), .is_error = result.is_error }
|
||||
.{ .content = capToolOutput(allocator, tool_name, result.text), .is_error = result.is_error }
|
||||
else |err|
|
||||
.{ .content = std.fmt.allocPrint(allocator, "Error: {s}", .{@errorName(err)}) catch "Error: tool execution failed", .is_error = true };
|
||||
|
||||
@@ -1952,7 +1960,7 @@ test "savePrompt: save instructions followed by the rendered script skill" {
|
||||
|
||||
test "capToolOutput: passes through when under cap" {
|
||||
const ta = std.testing.allocator;
|
||||
const out = capToolOutput(ta, "short");
|
||||
const out = capToolOutput(ta, "markdown", "short");
|
||||
try std.testing.expectEqualStrings("short", out);
|
||||
}
|
||||
|
||||
@@ -1972,9 +1980,23 @@ test "capToolOutput: appends a marker when truncating" {
|
||||
buf[cap + 1] = 0x9C;
|
||||
@memset(buf[cap + 2 ..], 'b');
|
||||
|
||||
const out = capToolOutput(ta, buf);
|
||||
const out = capToolOutput(ta, "markdown", buf);
|
||||
defer if (out.ptr != buf.ptr) ta.free(out);
|
||||
|
||||
try std.testing.expect(std.unicode.utf8ValidateSlice(out));
|
||||
try std.testing.expect(std.mem.indexOf(u8, out, "truncated") != null);
|
||||
}
|
||||
|
||||
test "capToolOutput: extract is exempt from the default cap" {
|
||||
const ta = std.testing.allocator;
|
||||
|
||||
const buf = try ta.alloc(u8, tool_output_max_bytes + 8);
|
||||
defer ta.free(buf);
|
||||
@memset(buf, 'a');
|
||||
|
||||
const out = capToolOutput(ta, "extract", buf);
|
||||
try std.testing.expect(out.ptr == buf.ptr);
|
||||
|
||||
try std.testing.expectEqual(extract_output_max_bytes, toolOutputCap("extract"));
|
||||
try std.testing.expectEqual(tool_output_max_bytes, toolOutputCap("html"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user