lua-agent-lab / tmpfile.lua
jeorgexyz's picture
Publish reproducible Lua agent experiments and traces
98acb70 verified
Raw History Blame Contribute Delete
2.56 kB
-- tmpfile.lua - A temporary path that can actually be opened.
--
-- os.tmpname() is not usable on Windows. It returns a bare name rooted at
-- the current drive -- "\s5tg." -- and opening it fails with permission
-- denied unless the process happens to be able to write to C:\. The backends
-- used it to stage curl's request body and header files, so every real
-- request died at `req:write` on a nil handle.
--
-- What makes this worth a module rather than a one-liner: the fixture tests
-- for backend/http.lua and backend/ollama.lua inject a transport, which is
-- the right way to test request building -- and it means they never execute
-- the curl path at all. No amount of that kind of testing would have found
-- this. It surfaced the first time a backend was pointed at a real endpoint.
local tmpfile = {}
local counter = 0
local function tempdir()
-- TMPDIR is POSIX, TEMP/TMP are Windows. Fall back to the working
-- directory, which is always writable if anything is.
return os.getenv("TMPDIR") or os.getenv("TEMP") or os.getenv("TMP") or "."
end
-- Returns a path in a writable temp directory. Not opened, not created --
-- the caller writes it and is responsible for removing it.
function tmpfile.name(suffix)
counter = counter + 1
local dir = tempdir():gsub("[\\/]+$", "")
local sep = package.config:sub(1, 1)
-- os.time alone collides when two files are made in the same second,
-- which is exactly what a request/header pair does.
return string.format("%s%slua2_%d_%d_%d%s",
dir, sep, os.time(), math.random(1e6), counter, suffix or "")
end
-- Write a string to a fresh temp file. Returns the path, or nil + error --
-- the callers need to report "could not stage the request" distinctly from
-- "the request failed", and silently proceeding on a nil handle is what
-- produced an index-a-nil-value crash instead of a message.
function tmpfile.write(contents, suffix)
local path = tmpfile.name(suffix)
local fh, err = io.open(path, "w")
if not fh then
return nil, string.format("cannot write a temp file at %s (%s)",
path, tostring(err))
end
fh:write(contents)
fh:close()
return path
end
-- Read and delete in one step; used for curl's captured output.
function tmpfile.slurp(path)
local fh = io.open(path, "r")
if not fh then return nil end
local s = fh:read("a")
fh:close()
return s
end
function tmpfile.remove(...)
for _, p in ipairs({ ... }) do
if p then os.remove(p) end
end
end
return tmpfile