summaryrefslogtreecommitdiff
path: root/agent/tools/write.lua
diff options
context:
space:
mode:
Diffstat (limited to 'agent/tools/write.lua')
-rw-r--r--agent/tools/write.lua81
1 files changed, 81 insertions, 0 deletions
diff --git a/agent/tools/write.lua b/agent/tools/write.lua
new file mode 100644
index 0000000..70aa5c8
--- /dev/null
+++ b/agent/tools/write.lua
@@ -0,0 +1,81 @@
+-- Write text to a file, creating parent directories as needed. Pi-style
+-- terse description because every byte of every tool description rides
+-- in the system prompt on every turn.
+
+local uv = require("luv")
+
+-- mkdir -p equivalent: walk the path components and create each.
+-- Ignores EEXIST 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 ok, err, name = uv.fs_mkdir(cur, tonumber("755", 8))
+ if not ok and name ~= "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
+
+return {
+ name = "write",
+ description = "Save content to a path. Overwrites existing files; missing parent directories are created automatically.",
+ schema = {
+ type = "object",
+ properties = {
+ path = { type = "string", description = "Path to the file (relative or absolute)." },
+ content = { type = "string", description = "Exact bytes to write. No newline is added." },
+ },
+ required = { "path", "content" },
+ },
+ handler = function(input)
+ local path = input.path
+ local content = input.content
+
+ if type(path) ~= "string" or path == "" then
+ return "Error: `path` must be a non-empty string."
+ end
+ if type(content) ~= "string" then
+ return "Error: `content` must be a string."
+ end
+
+ local parent = dirname(path)
+ if parent then
+ local ok, err = mkdir_p(parent)
+ if not ok then
+ return "Error: could not create parent directory " .. parent .. ": " .. tostring(err)
+ end
+ end
+
+ local f, open_err = io.open(path, "wb")
+ if not f then
+ return "Error: " .. (open_err or ("could not open " .. path .. " for writing"))
+ end
+
+ local ok, write_err = f:write(content)
+ if not ok then
+ f:close()
+ return "Error: write failed: " .. tostring(write_err)
+ end
+
+ local close_ok, close_err = f:close()
+ if not close_ok then
+ return "Error: close failed: " .. tostring(close_err)
+ end
+
+ return string.format("Wrote %d bytes to %s.", #content, path)
+ end,
+}