-- One transient warming request: the session's current prompt, a minimal -- extra user message, one model response -- and none of it kept. -- -- The request runs on a throwaway agent so the live session is never -- touched. The pieces: -- -- transcript `panto.file_system_jsonl_store` over the session's own -- store dir, then `load` -- the same reconstruction `/resume` -- uses, so system blocks, tool inputs, and thinking -- signatures come back verbatim and the request shares the -- session's prompt prefix byte for byte. -- config `panto.ext.resolve_model` for the session's current -- model/reasoning label, which inherits the live credentialed -- transport when it resolves the session's own model. -- tools the session agent's declarations, copied with `set_tools` -- so the tools array matches the session's own requests (see -- the README's fidelity notes on schema re-encoding). -- store `panto.null_store()` -- the warm turn's user message and -- response are appended to the throwaway conversation and -- persisted nowhere. -- turn `agent:run_async { prompt, dispatch_tools = false }` on its -- own pump thread, so the UI thread never blocks. Tools are -- never dispatched: a misbehaving model cannot run real -- tools from a keep-alive prompt. The response is drained -- and dropped. -- -- Nothing here raises: the binding reports failures by raising, and every -- call goes through `try` so callers get one `nil, message` shape. local M = {} local function try(fn, ...) local ok, value = pcall(fn, ...) if not ok then return false, tostring(value) end return true, value end -- start(prompt) -> handle | nil, err -- -- Everything that can fail before the turn starts fails here: a caller -- either gets a handle to poll or a message. function M.start(prompt) local panto = require("panto") local ext = panto.ext local ok, info = try(ext.session_info) if not ok then return nil, "session_info: " .. tostring(info) end if type(info) ~= "table" or type(info.session_dir) ~= "string" or type(info.session_id) ~= "string" then return nil, "no session information available" end local opened, store = try(panto.file_system_jsonl_store, { dir = info.session_dir }) if not opened then return nil, "session store: " .. tostring(store) end if store == nil then return nil, "the session store could not be opened" end local loaded, conv = try(store.load, store, info.session_id) if not loaded then return nil, "session store: " .. tostring(conv) end if conv == nil then return nil, "the session transcript could not be loaded" end local asked, cfg, resolve_err = pcall(ext.resolve_model, { model = info.model, reasoning = info.reasoning, }) if not asked then return nil, tostring(cfg) end if cfg == nil then return nil, resolve_err and tostring(resolve_err) or "the session model could not be resolved" end local made, no_store = try(panto.null_store) if not made then return nil, tostring(no_store) end local built, agent = try(panto.agent, { config = cfg, store = no_store, conversation = conv, }) if not built then return nil, "warm agent: " .. tostring(agent) end if agent == nil then return nil, "the warm agent could not be created" end local primary = ext.agent if primary ~= nil then local got, decls = try(primary.tools, primary) if not got then return nil, "tools: " .. tostring(decls) end if type(decls) == "table" and #decls > 0 then local armed, tools_err = try(agent.set_tools, agent, decls) if not armed then return nil, "warm tools: " .. tostring(tools_err) end end end local started, job = try(agent.run_async, agent, { prompt = prompt, dispatch_tools = false, }) if not started then return nil, "warm turn: " .. tostring(job) end if job == nil then return nil, "the warm turn could not be started" end return { agent = agent, job = job, store = store, conv = conv } end -- poll(handle) -> nil while the pump runs, then a settled -- { status = "completed"|"failed"|"cancelled", err = ... }. Drains buffered -- events as it goes so a chatty response cannot grow the queue. The result -- content is deliberately discarded; `close` joins the (settled) pump. function M.poll(handle) local job = handle.job while true do local ok, event = pcall(job.next_event, job) if not ok or event == nil then break end end local ok, result = pcall(job.result, job) if not ok then pcall(job.close, job) return { status = "failed", err = tostring(result) } end if type(result) ~= "table" then return nil end pcall(job.close, job) return { status = result.status or "failed", err = result.err } end -- abandon(handle): cancel and join the pump, on session teardown. The -- cancel half shuts the response socket down, so the join returns promptly -- even mid-request. function M.abandon(handle) pcall(handle.job.request_cancel, handle.job) pcall(handle.job.close, handle.job) end return M