summaryrefslogtreecommitdiff
path: root/agent/tools/write.lua
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-06-23 09:38:23 -0600
committert <t@tjp.lol>2026-06-23 10:53:26 -0600
commit69a1ee138bb78ad4663fe2d9e58678f7b0d07b74 (patch)
tree453d3ad42a07536220e6ca5d91b439ff57793e32 /agent/tools/write.lua
parent538a5d926fa626a00cc3dc12c555f11f82867efd (diff)
ponytail simplifications to panto cli and lua extensiosn
Diffstat (limited to 'agent/tools/write.lua')
-rw-r--r--agent/tools/write.lua89
1 files changed, 12 insertions, 77 deletions
diff --git a/agent/tools/write.lua b/agent/tools/write.lua
index 144a305..ee1b2ae 100644
--- a/agent/tools/write.lua
+++ b/agent/tools/write.lua
@@ -2,8 +2,7 @@
-- description terse because every byte of every tool description rides
-- in the system prompt on every turn.
-local panto = require("panto")
-local uv = require("luv")
+local std = require("_std")
local tool = {
name = "std.write",
@@ -18,75 +17,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)
-
--- ---------------------------------------------------------------------------
--- 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.
--- ---------------------------------------------------------------------------
-local function await(arm)
- local co = assert(coroutine.running(),
- "write: await must run inside a tool handler coroutine")
- local res, fired = nil, false
- arm(function(...)
- assert(not fired, "write: 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
-
--- Async `fs_mkdir`. luv's async callback is `(err, success)`, and on
--- EEXIST it reports the error via a string code in `err`; we detect
--- that textually since the async form doesn't surface the errno name
--- separately the way the sync form's third return does.
-local function fs_mkdir(path, mode)
- return await(function(resolve) uv.fs_mkdir(path, mode, 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_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
-
--- mkdir -p equivalent: walk the path components and create each.
--- Ignores "already exists" so the call is idempotent.
-local function mkdir_p(path)
- if path == "" or path == "." or path == "/" then return true end
- local parts = {}
- for piece in string.gmatch(path, "[^/]+") do
- parts[#parts + 1] = piece
- end
- local cur = (path:sub(1, 1) == "/") and "" or "."
- for _, piece in ipairs(parts) do
- cur = cur .. "/" .. piece
- local err = fs_mkdir(cur, tonumber("755", 8))
- if err and not tostring(err):find("EEXIST") then
- return nil, err
- end
- end
- return true
-end
-
--- Return the dirname of `path`, or nil if there isn't one (basename only).
-local function dirname(path)
- local slash = path:find("/[^/]*$")
- if not slash or slash == 1 then return nil end
- return path:sub(1, slash - 1)
-end
-
tool.handler = function(input)
local path = input.path
local content = input.content
@@ -98,15 +28,15 @@ tool.handler = function(input)
return "Error: `content` must be a string."
end
- local parent = dirname(path)
+ local parent = std.dirname(path)
if parent then
- local ok, err = mkdir_p(parent)
+ local ok, err = std.mkdir_p_async(parent)
if not ok then
return "Error: could not create parent directory " .. parent .. ": " .. tostring(err)
end
end
- local open_err, fd = fs_open(path, "w", tonumber("644", 8))
+ local open_err, fd = std.fs_open(path, "w", tonumber("644", 8))
if not fd then
return "Error: " .. (open_err or ("could not open " .. path .. " for writing"))
end
@@ -114,15 +44,15 @@ tool.handler = function(input)
-- 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)
+ local werr, n = std.fs_write(fd, content:sub(offset + 1), offset)
if not n then
- fs_close(fd)
+ std.fs_close(fd)
return "Error: write failed: " .. tostring(werr)
end
offset = offset + n
end
- local close_err = fs_close(fd)
+ local close_err = std.fs_close(fd)
if close_err then
return "Error: close failed: " .. tostring(close_err)
end
@@ -130,4 +60,9 @@ tool.handler = function(input)
return string.format("Wrote %d bytes to %s.", #content, path)
end
+std.install_renderer(tool.name, function(st)
+ local obj = std.decode(st.input)
+ return "write " .. tostring(obj and obj.path or "")
+end)
+
return tool