diff options
Diffstat (limited to 'agent/tools/read.lua')
| -rw-r--r-- | agent/tools/read.lua | 108 |
1 files changed, 29 insertions, 79 deletions
diff --git a/agent/tools/read.lua b/agent/tools/read.lua index f691f44..cd7f26b 100644 --- a/agent/tools/read.lua +++ b/agent/tools/read.lua @@ -1,5 +1,4 @@ -local panto = require("panto") -local uv = require("luv") +local std = require("_std") local tool = { name = "std.read", @@ -15,11 +14,6 @@ local tool = { }, } -panto.ext.on("tool", function(e) - if e.tool_name ~= tool.name then return end - e:set_component(e:get_component()) -end) - -- Read a file from disk and return its text verbatim. -- -- Output cap: 50 KB and 2000 lines, whichever is hit first. Large @@ -63,71 +57,10 @@ local MAX_BYTES = 50 * 1024 -- 50 KB local MAX_LINES = 2000 local READ_CHUNK_SIZE = MAX_BYTES --- --------------------------------------------------------------------------- --- Coroutine-synchronous libuv wrappers. --- --- panto runs every tool handler inside its own Lua coroutine and drives --- a single `uv.run()` to completion. The contract for an async handler --- is: the ONLY way it may block is to yield on a pending libuv request --- whose callback resumes it exactly once. These helpers encapsulate --- that pattern — each fires one async libuv op, yields, and returns the --- callback's results once resumed. They look synchronous to the caller --- but cooperate with sibling tool calls sharing the event loop. --- --- INVARIANTS each wrapper upholds (and every async tool must too): --- * exactly one libuv op armed per yield; --- * the callback resumes the coroutine exactly once; --- * `coroutine.resume` errors are re-raised so a crash in the resumed --- handler isn't silently swallowed inside the uv callback. --- --------------------------------------------------------------------------- - --- Run a single callback-style libuv fs operation and block the --- coroutine until its callback fires. `arm` receives a `resolve` --- function and must start exactly one libuv op that calls it. -local function await(arm) - local co = assert(coroutine.running(), - "read: await must run inside a tool handler coroutine") - local res, fired = nil, false - arm(function(...) - assert(not fired, "read: libuv callback fired twice") - fired = true - res = table.pack(...) - local ok, err = coroutine.resume(co) - if not ok then error(err, 0) end - end) - coroutine.yield() - return table.unpack(res, 1, res.n) -end - -local function fs_stat(path) - return await(function(resolve) uv.fs_stat(path, resolve) end) -end - -local function fs_open(path, flags, mode) - return await(function(resolve) uv.fs_open(path, flags, mode, resolve) end) -end - -local function fs_read(fd, size, offset) - return await(function(resolve) uv.fs_read(fd, size, offset, resolve) end) -end - -local function fs_close(fd) - return await(function(resolve) uv.fs_close(fd, resolve) end) -end - -- `uv.fs_opendir` takes the entries-per-readdir batch hint as its -- SECOND positional arg, before the callback. We pass it explicitly so -- each `fs_readdir` returns up to `n` entries per round-trip. local READDIR_BATCH = 256 -local function fs_opendir(path) - return await(function(resolve) uv.fs_opendir(path, resolve, READDIR_BATCH) end) -end -local function fs_readdir(dir) - return await(function(resolve) uv.fs_readdir(dir, resolve) end) -end -local function fs_closedir(dir) - return await(function(resolve) uv.fs_closedir(dir, resolve) end) -end local function scandir_sorted(path) -- Use the streaming opendir/readdir/closedir API rather than @@ -135,16 +68,16 @@ local function scandir_sorted(path) -- with synchronous `readdir(3)` syscalls, which would block the -- shared event loop on large directories. opendir/readdir route -- every batch through libuv's thread pool, keeping us cooperative. - local err, dir = fs_opendir(path) + local err, dir = std.fs_opendir(path, READDIR_BATCH) if not dir then return nil, err end local entries = {} while true do - local rerr, batch = fs_readdir(dir) + local rerr, batch = std.fs_readdir(dir) if rerr then - fs_closedir(dir) + std.fs_closedir(dir) return nil, rerr end -- A nil/empty batch signals end of the directory stream. @@ -154,7 +87,7 @@ local function scandir_sorted(path) entries[#entries + 1] = { name = e.name, typ = e.type } end end - fs_closedir(dir) + std.fs_closedir(dir) table.sort(entries, function(a, b) return a.name < b.name @@ -248,7 +181,7 @@ tool.handler = function(input) return string.format("Error: limit (%d) must be >= 1.", limit) end - local stat_err, stat = fs_stat(path) + local stat_err, stat = std.fs_stat(path) if not stat then return "Error: " .. (stat_err or ("could not stat " .. path)) end @@ -256,7 +189,7 @@ tool.handler = function(input) return render_directory_listing(path, offset, limit) end - local open_err, fd = fs_open(path, "r", 0) + local open_err, fd = std.fs_open(path, "r", 0) if not fd then return "Error: " .. (open_err or ("could not open " .. path)) end @@ -266,13 +199,13 @@ tool.handler = function(input) -- it's an attachment, we return the raw file bytes; libpanto does -- the real work (precise type detection, resizing, encoding). do - local herr, header = fs_read(fd, 16, 0) + local herr, header = std.fs_read(fd, 16, 0) if not herr and header and #header > 0 and is_attachment(header) then local whole, rerr = (function() local chunks = {} local off = 0 while true do - local e, d = fs_read(fd, READ_CHUNK_SIZE, off) + local e, d = std.fs_read(fd, READ_CHUNK_SIZE, off) if e then return nil, e end if d == nil or #d == 0 then break end chunks[#chunks + 1] = d @@ -280,7 +213,7 @@ tool.handler = function(input) end return table.concat(chunks) end)() - fs_close(fd) + std.fs_close(fd) if not whole then return "Error: read failed: " .. tostring(rerr) end @@ -332,7 +265,7 @@ tool.handler = function(input) end while not done do - local err, data = fs_read(fd, READ_CHUNK_SIZE, file_offset) + local err, data = std.fs_read(fd, READ_CHUNK_SIZE, file_offset) if err then read_err = err break @@ -372,7 +305,7 @@ tool.handler = function(input) end end - fs_close(fd) + std.fs_close(fd) if read_err then return "Error: read failed: " .. tostring(read_err) @@ -418,4 +351,21 @@ tool.handler = function(input) return body end +local function quote(v) + if v == nil or v == "" then return nil end + return tostring(v) +end + +std.install_renderer(tool.name, function(st) + local obj = std.decode(st.input) + local path = obj and quote(obj.path) + if not path then return "read" end + if type(obj.offset) == "number" and type(obj.limit) == "number" then + return string.format("read %s (lines %d-%d)", path, obj.offset, obj.offset + obj.limit - 1) + elseif type(obj.offset) == "number" then + return string.format("read %s (from line %d)", path, obj.offset) + end + return "read " .. path +end) + return tool |
