-- SPDX-FileCopyrightText: © 2026 Vladimir Zorin -- SPDX-License-Identifier: LicenseRef-OWL-1.0-or-later -- Licensed under OWL v1.0+. See LICENSE. -- Convention: tokens returned by this module are wrapper tables: -- { lev = , children = {[...wrapper_tokens]}, cancel = } -- with __index = lev_token so that wrapper.cancelled, wrapper:register(), etc. -- delegate to the underlying lev token. Wrapper can be passed directly to LEV -- blocking ops (lev.sleep, lev.connect, …). with_token() always places the -- underlying lev token in opts.cancel so LEV gets the real token. -- Access the raw lev token via token.lev or M.lev_token(token). local lev = require("lev") local agent_error = require("agent_smith.error") local M = {} -- Build a wrapper around a fresh lev cancel_token. local make_wrapper make_wrapper = function() local lev_tok = lev.cancel_token() local wrapper = { lev = lev_tok, children = {}, } -- Override cancel so token:cancel() fans out just like M.request(). wrapper.cancel = function(self) M.request(self) end setmetatable(wrapper, { __index = lev_tok }) return wrapper end function M.new_root() return make_wrapper() end function M.new_child(parent) if parent == nil or type(parent) ~= "table" or type(parent.children) ~= "table" then return nil, agent_error.new({ kind = agent_error.kind.internal, code = "invalid_cancel_token", message = "parent cancel token is nil or invalid", origin = "cancel", }) end local child = make_wrapper() parent.children[#parent.children + 1] = child return child end -- derive_child: single source of truth for deriving a child token from a -- parent. Returns nil when parent is nil (caller decides whether to fall back -- to a root token). If the parent is already cancelled, the child is cancelled -- eagerly so a child turn started after a parent cancel short-circuits. function M.derive_child(parent) if parent == nil then return nil end local child, child_err = M.new_child(parent) if not child then return nil, child_err end if M.is_cancelled(parent) then M.request(child) end return child end -- M.lev_token: helper to extract the underlying lev token from a wrapper. function M.lev_token(token) if token == nil then return nil end return token.lev end function M.is_cancelled(token) if token == nil or type(token) ~= "table" then return false end if token.cancelled == true then return true end return token.lev ~= nil and token.lev.cancelled == true end function M.check(token) if M.is_cancelled(token) then return nil, "cancelled" end return true end -- request: EAGER FAN-OUT — cancel this token and all descendants. function M.request(token) if token == nil then return false, "missing_token" end if not token.lev.cancelled then token.lev:cancel() end if type(token.children) == "table" then for _, child in ipairs(token.children) do M.request(child) end end return true end function M.with_token(opts, token) opts = opts or {} local lev_tok = token and token.lev or nil if opts.cancel ~= nil and opts.cancel ~= lev_tok and opts.cancel ~= token then return nil, agent_error.new({ kind = agent_error.kind.internal, code = "conflicting_cancel_token", message = "cancel token conflict", origin = "runtime", data = { has_cancel = true }, }) end local out = {} for key, value in pairs(opts) do out[key] = value end out.cancel = lev_tok return out end function M.normalize_return(ok, err_value, origin) if ok ~= nil then return ok end if err_value == "cancelled" then return nil, agent_error.cancelled(origin) end return nil, agent_error.normalize(err_value, { origin = origin or "runtime" }) end return M