summaryrefslogtreecommitdiff
path: root/agent/tools/edit.lua
diff options
context:
space:
mode:
Diffstat (limited to 'agent/tools/edit.lua')
-rw-r--r--agent/tools/edit.lua93
1 files changed, 38 insertions, 55 deletions
diff --git a/agent/tools/edit.lua b/agent/tools/edit.lua
index b32516a..2fa3468 100644
--- a/agent/tools/edit.lua
+++ b/agent/tools/edit.lua
@@ -10,8 +10,7 @@
-- All matches are evaluated against the original file, not
-- progressively against the result of earlier entries.
-local panto = require("panto")
-local uv = require("luv")
+local std = require("_std")
local tool = {
name = "std.edit",
@@ -38,82 +37,39 @@ local tool = {
},
}
-panto.ext.on("tool", function(e)
- if e.tool_name ~= tool.name then return end
- e:set_component(e:get_component())
-end)
-
--- ---------------------------------------------------------------------------
--- Coroutine-synchronous libuv wrappers. panto runs each tool handler in
--- its own coroutine and drives a single `uv.run()` to completion; a
--- handler may only block by yielding on a pending libuv op whose
--- callback resumes it exactly once. `await` encapsulates that: arm one
--- op, yield, return the callback's `(err, value)` results.
--- ---------------------------------------------------------------------------
-local function await(arm)
- local co = assert(coroutine.running(),
- "edit: await must run inside a tool handler coroutine")
- local res, fired = nil, false
- arm(function(...)
- assert(not fired, "edit: 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_open(path, flags, mode)
- return await(function(resolve) uv.fs_open(path, flags, mode, resolve) end)
-end
-local function fs_fstat(fd)
- return await(function(resolve) uv.fs_fstat(fd, 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_write(fd, data, offset)
- return await(function(resolve) uv.fs_write(fd, data, offset, resolve) end)
-end
-local function fs_close(fd)
- return await(function(resolve) uv.fs_close(fd, resolve) end)
-end
-
-- Read an entire file into a string. Returns (content, nil) or
-- (nil, err). EOF on `fs_read` is the empty string, not nil.
local function read_all(path)
- local err, fd = fs_open(path, "r", 0)
+ local err, fd = std.fs_open(path, "r", 0)
if not fd then return nil, err end
- local serr, st = fs_fstat(fd)
- if not st then fs_close(fd); return nil, serr end
+ local serr, st = std.fs_fstat(fd)
+ if not st then std.fs_close(fd); return nil, serr end
local parts, offset = {}, 0
while true do
- local rerr, data = fs_read(fd, 64 * 1024, offset)
- if rerr then fs_close(fd); return nil, rerr end
+ local rerr, data = std.fs_read(fd, 64 * 1024, offset)
+ if rerr then std.fs_close(fd); return nil, rerr end
if data == nil or #data == 0 then break end
parts[#parts + 1] = data
offset = offset + #data
end
- fs_close(fd)
+ std.fs_close(fd)
return table.concat(parts), nil
end
-- Overwrite a file with `content` (O_WRONLY|O_CREAT|O_TRUNC, 0644).
-- Returns (true, nil) or (nil, err).
local function write_all(path, content)
- local err, fd = fs_open(path, "w", tonumber("644", 8))
+ local err, fd = std.fs_open(path, "w", tonumber("644", 8))
if not fd then return nil, err end
-- A single `fs_write` may short-write; loop until all bytes land.
local offset = 0
while offset < #content do
- local werr, n = fs_write(fd, content:sub(offset + 1), offset)
- if not n then fs_close(fd); return nil, werr end
+ local werr, n = std.fs_write(fd, content:sub(offset + 1), offset)
+ if not n then std.fs_close(fd); return nil, werr end
offset = offset + n
end
-- Empty content still needs the truncate that O_TRUNC gave us.
- fs_close(fd)
+ std.fs_close(fd)
return true, nil
end
@@ -261,4 +217,31 @@ tool.handler = function(input)
)
end
+local function edit_diff(input)
+ local obj = std.decode(input)
+ if not obj or type(obj.edits) ~= "table" then return nil end
+ local out = {}
+ for _, e in ipairs(obj.edits) do
+ if type(e) == "table" then
+ if type(e.old) == "string" then
+ for l in (e.old .. "\n"):gmatch("(.-)\n") do if l ~= "" then out[#out + 1] = "-" .. l end end
+ end
+ if type(e.new) == "string" then
+ for l in (e.new .. "\n"):gmatch("(.-)\n") do if l ~= "" then out[#out + 1] = "+" .. l end end
+ end
+ end
+ end
+ return table.concat(out, "\n")
+end
+
+std.install_renderer(tool.name, function(st)
+ local obj = std.decode(st.input)
+ local n = obj and type(obj.edits) == "table" and #obj.edits or 0
+ if n == 1 then return "edit " .. tostring(obj.path or "") .. " (1 edit)" end
+ if n > 1 then return string.format("edit %s (%d edits)", tostring(obj.path or ""), n) end
+ return "edit " .. tostring(obj and obj.path or "")
+end, function(st)
+ return edit_diff(st.input) or st.output
+end)
+
return tool