//! Batch-dispatched tool extension API: `ToolSource`. //! //! Where `Tool` is a single, thread-safe handler (one tool, one vtable, //! reentrant), `ToolSource` is a single owner of many tools whose runtime //! prefers to receive calls in *batches* on a single thread. //! //! Motivation: Lua. A Lua extension runtime maintains one long-lived //! `lua_State` so that module-globals, lazy connection pools, rate //! limiters, etc. survive across calls. A single `lua_State` is not safe //! for concurrent host entry, so the runtime can't satisfy `Tool`'s //! thread-safety contract directly. The runtime *can* dispatch many calls //! cooperatively (coroutines + an event loop), but it needs to be told //! all of them at once. //! //! The contract libpanto provides: //! //! - For a given turn, every `ToolUse` block whose tool name belongs to //! a particular source is delivered in a single `invoke_batch` call, //! on one thread. //! - Distinct sources still execute concurrently (one OS thread per //! source per turn), so a Lua source and a native source can run in //! parallel. //! - Single `Tool` registrations are unchanged. They each get their own //! thread when they appear alongside other tool calls in a turn. //! //! The "thread-safe" promise that `Tool.invoke` carries relaxes to //! "coroutine-safe within the source's runtime" for source-backed tools — //! enforcement is the source's problem. const std = @import("std"); const Allocator = std.mem.Allocator; /// Tool metadata: everything the LLM-facing wire needs (name, /// description, schema) without an invocation vtable. `ToolSource`s /// declare their tools this way because they share a single dispatch /// path. pub const ToolDecl = struct { name: []const u8, description: []const u8, schema_json: []const u8, }; /// One pending invocation passed to `invoke_batch`. Slices borrowed from /// the caller for the duration of the call. pub const Call = struct { /// Which of the source's declared tools this call targets. tool_name: []const u8, /// Raw JSON bytes the provider sent. Borrowed. input: []const u8, }; /// Result for a single call. Mirrors the success/error split of /// `Tool.invoke`'s return shape. Owned by the caller-supplied allocator. pub const CallResult = union(enum) { /// Owned bytes, freed by libpanto after assembling the ToolResult /// block. ok: []u8, err: anyerror, }; /// A grouped tool runtime. pub const ToolSource = struct { /// Diagnostic name; surfaced in error messages and logs. Example /// values: `"panto-lua"`, `"panto-python"`. Borrowed; lifetime owned /// by the source. name: []const u8, /// Tool metadata for every tool this source owns. Borrowed. tools: []const ToolDecl, ctx: *anyopaque, vtable: *const VTable, pub const VTable = struct { /// libpanto guarantees: for a given turn, every ToolUse call /// whose tool name belongs to this source is delivered in one /// `invoke_batch`, on one thread. Different sources still /// execute in parallel. /// /// `calls` and `results` are parallel arrays of length N. /// `results` is pre-allocated by libpanto; the source fills each /// slot. The source decides internal scheduling — sequential, /// coroutine fan-out, worker pool, etc. /// /// On any uncaught error returned from this function (i.e. the /// fn itself returning an error rather than recording one in a /// `results[i]` slot), libpanto treats the *entire batch* as /// failed: it frees any `ok` slots already filled and aborts the /// turn with that error. invoke_batch: *const fn ( ctx: *anyopaque, calls: []const Call, results: []CallResult, allocator: Allocator, ) anyerror!void, /// Called when the source is removed from the registry or the /// registry is torn down. Frees any resources owned by `ctx`, /// including `ctx` itself if heap-allocated. deinit: *const fn (ctx: *anyopaque, allocator: Allocator) void, }; };