local M = {}
local cl = require("compile_loop")
local LUA_RUNNER_TIMEOUT = 60
local CARGO_RUNNER_TIMEOUT = 300
local function exec_result(res, pass_fn)
if not res.ok then
return { ok = false, stdout = "", stderr = tostring(res.error), exit_code = -1 }
end
local stdout = res.stdout or ""
local stderr = res.stderr or ""
return {
ok = res.code == 0 and pass_fn(stdout, stderr),
stdout = stdout,
stderr = stderr,
exit_code = res.code,
}
end
local BUILTIN_RUNNERS = {
lua = function(file_path)
local res = sh.exec("lua " .. file_path, { timeout = LUA_RUNNER_TIMEOUT })
return exec_result(res, function(stdout)
return stdout:find("ALL_PASS", 1, true) ~= nil
end)
end,
cargo = function(file_path)
local dir = file_path:match("^(.*)/[^/]+$") or "."
local res = sh.exec("cargo test --offline", { cwd = dir, timeout = CARGO_RUNNER_TIMEOUT })
return exec_result(res, function(stdout, stderr)
return (stdout .. stderr):find("test result: ok", 1, true) ~= nil
end)
end,
}
local function resolve_runner(kind)
if type(kind) == "function" then
return kind, nil
end
if type(kind) == "string" then
local fn = BUILTIN_RUNNERS[kind]
if fn then
return fn, nil
end
return nil, "unknown runner_kind: " .. kind
end
return nil, "runner_kind must be a string or function, got: " .. type(kind)
end
function M.run(opts)
assert(type(opts) == "table", "opts table required")
assert(opts.target_file, "opts.target_file required")
assert(opts.spec, "opts.spec required")
assert(type(opts.runner) == "function", "opts.runner (function) required")
local conf = {
runner = opts.runner,
lang = opts.lang,
max_iters = opts.max_iters,
system = opts.system,
on_iter = opts.on_iter,
name = "compile_loop",
llm = {
provider = opts.provider,
base_url = opts.base_url,
api_key = opts.api_key,
api_key_env = opts.api_key_env,
model = opts.model,
max_tokens = opts.max_tokens,
temperature = opts.temperature,
disable_thinking = opts.disable_thinking,
timeout = opts.timeout,
},
}
local td = cl.make(conf)
local raw_json = td.handler({
spec = opts.spec,
target_file = opts.target_file,
lang = opts.lang,
})
local ok, result = pcall(std.json.decode, raw_json)
if not ok or type(result) ~= "table" then
return {
ok = false,
failure_reason = "decode_failed",
last_error = tostring(result),
iters = 0,
summary = "coding_agent.run: failed to decode compile_loop result",
}
end
return result
end
function M.register_tool(opts)
assert(type(opts) == "table", "opts table required")
assert(opts.runner_kind ~= nil, "opts.runner_kind required")
local runner, rerr = resolve_runner(opts.runner_kind)
if not runner then
error("coding_agent.register_tool: " .. tostring(rerr))
end
local conf = {
runner = runner,
lang = opts.lang,
max_iters = opts.max_iters,
system = opts.system,
name = opts.name,
llm = {
provider = opts.provider,
base_url = opts.base_url,
api_key = opts.api_key,
api_key_env = opts.api_key_env,
model = opts.model,
max_tokens = opts.max_tokens,
temperature = opts.temperature,
disable_thinking = opts.disable_thinking,
timeout = opts.timeout,
},
}
local td = cl.make(conf)
return td.name
end
function M._test_helpers()
return {
resolve_runner = resolve_runner,
builtin_runners = BUILTIN_RUNNERS,
exec_result = exec_result,
}
end
return M