diff options
| author | t <t@tjp.lol> | 2026-07-07 11:29:23 -0600 |
|---|---|---|
| committer | t <t@tjp.lol> | 2026-07-07 12:15:28 -0600 |
| commit | 621d7fee0ace729f8d44126032d2c6e13f72ee7f (patch) | |
| tree | a2938b0a9e5c0930e8644721abbe94875df9ff08 /libpanto/src/tool.zig | |
| parent | 0fa6d4209ff9b4a95e7d1955887aa4c73ee3423c (diff) | |
Remove the in-repo libpanto sources and binding projects from pantograph.
Consume libpantograph through the Zig package URL at
code.tjp.lol/libpantograph.git, including the Lua module artifact used by
CLI extensions.
Diffstat (limited to 'libpanto/src/tool.zig')
| -rw-r--r-- | libpanto/src/tool.zig | 149 |
1 files changed, 0 insertions, 149 deletions
diff --git a/libpanto/src/tool.zig b/libpanto/src/tool.zig deleted file mode 100644 index c96dfee..0000000 --- a/libpanto/src/tool.zig +++ /dev/null @@ -1,149 +0,0 @@ -//! Native tool extension API. -//! -//! A `Tool` is the boundary between the agent loop and any extension runtime -//! — native Zig code, a Lua bridge, a future Python or Go bridge. libpanto -//! itself does not parse tool inputs or outputs; it just dispatches. - -const std = @import("std"); -const Allocator = std.mem.Allocator; - -/// Tool metadata: everything the LLM-facing wire needs (name, -/// description, schema) without an invocation vtable. This is the more -/// atomic type, so it lives here; `tool_source.zig` imports it. -pub const ToolDecl = struct { - name: []const u8, - description: []const u8, - schema_json: []const u8, -}; - -/// A binary attachment a tool may return alongside (or instead of) text: -/// an image or a document (PDF). -/// -/// `data` is the **raw, un-encoded file bytes** — tools do no encoding. -/// libpanto owns the heavy lifting at tool-result assembly: it -/// magic-byte-detects the type when `media_type` is null, resizes large -/// rasters, and base64-encodes for storage/serialization. -pub const MediaPart = struct { - /// Optional MIME hint, e.g. "image/png". When null, libpanto detects - /// the type from `data`'s leading bytes (magic numbers). - media_type: ?[]const u8 = null, - /// Raw (un-encoded) file bytes. - data: []const u8, -}; - -/// One element of a tool's result. A tool returns a `ResultParts` (a thin -/// wrapper around `[]ResultPart`); the agent assembles these into a -/// `ToolResultBlock`. Bytes referenced by a part are owned by the allocator -/// passed to `invoke` / `invoke_batch`; ownership transfers to the agent, -/// which frees them. -pub const ResultPart = union(enum) { - text: []const u8, - media: MediaPart, - - /// Free the bytes this part owns, using `allocator`. - pub fn deinit(self: ResultPart, allocator: Allocator) void { - switch (self) { - .text => |t| allocator.free(t), - .media => |m| { - if (m.media_type) |mt| allocator.free(mt); - allocator.free(m.data); - }, - } - } -}; - -/// A tool's full result: an owned slice of `ResultPart`s. The value the -/// `Tool`/`ToolSource` vtable returns and the agent loop assembles — a thin -/// wrapper around `[]ResultPart` that carries the construction/teardown -/// ergonomics a bare slice alias can't. Build one with -/// `fromText`/`fromTextOwned` (or wrap a hand-built slice as -/// `.{ .items = slice }`); release it (slice + every part's bytes) with -/// `deinit`. -pub const ResultParts = struct { - items: []ResultPart, - - /// A single text part that owns `text` (duped from the input slice). - pub fn fromText(allocator: Allocator, text: []const u8) !ResultParts { - const owned = try allocator.dupe(u8, text); - errdefer allocator.free(owned); - const parts = try allocator.alloc(ResultPart, 1); - parts[0] = .{ .text = owned }; - return .{ .items = parts }; - } - - /// A single text part wrapping an already-owned `text` slice. Takes - /// ownership of `text` (frees it if the allocation below fails). - pub fn fromTextOwned(allocator: Allocator, text: []u8) !ResultParts { - const parts = allocator.alloc(ResultPart, 1) catch |e| { - allocator.free(text); - return e; - }; - parts[0] = .{ .text = text }; - return .{ .items = parts }; - } - - /// Free the slice and every part it owns. - pub fn deinit(self: ResultParts, allocator: Allocator) void { - for (self.items) |p| p.deinit(allocator); - allocator.free(self.items); - } -}; - -pub const Tool = struct { - /// Metadata: `name`, `description`, `schema_json`. Borrowed — the - /// lifetime of every string in `decl` is owned by whoever - /// constructs the `Tool`. Typically the same owner that backs - /// `ctx` (e.g. an adapter for an out-of-process runtime, or a - /// `comptime` static in a native tool). - decl: ToolDecl, - - /// Opaque context pointer passed back to every vtable call. - ctx: *anyopaque, - - vtable: *const VTable, - - pub const VTable = struct { - /// Invoke the tool. MUST be thread-safe — the agent may call - /// `invoke` concurrently from multiple threads when the LLM emits - /// multiple ToolUse blocks in a single response. - /// - /// `input` is the raw JSON bytes the provider sent. The tool is - /// responsible for parsing them if it cares about their structure. - /// - /// Returns a `ResultParts` allocated with `allocator`; each part's - /// bytes are likewise owned. These become the parts of the - /// ToolResult block sent back to the LLM. The agent takes ownership - /// and frees the slice and every part (see `ResultParts.deinit`). - /// Build the return value with `ResultParts.fromText` / - /// `.fromTextOwned` for the common single-text case, or wrap a - /// hand-built slice as `.{ .items = slice }`. - /// - /// Returning an error normally becomes a model-visible error - /// `ToolResult`: the agent synthesizes an error result for this - /// call (and keeps the matching `ToolResult` for every other call - /// in the batch), then lets the model continue so it can correct - /// arguments, try another tool, or explain the failure. Only hard - /// host failures (`error.Canceled`, `error.OutOfMemory`) abort the - /// whole turn and propagate to the embedder. - /// - /// Native tool implementations are responsible for catching their - /// own panics — a panic in `invoke` will crash the process. - /// Adapters that bridge to safer languages (Lua, Python, Go) should - /// convert panics/exceptions into errors. - invoke: *const fn ( - ctx: *anyopaque, - input: []const u8, - allocator: Allocator, - ) anyerror!ResultParts, - - /// Called when the tool is unregistered or the registry is torn - /// down. Frees any resources owned by `ctx`, including `ctx` - /// itself if it was heap-allocated. - /// - /// The strings inside `decl` are also typically owned by the - /// same allocation as `ctx` — the tool's deinit hook is - /// responsible for freeing them. - deinit: *const fn (ctx: *anyopaque, allocator: Allocator) void, - }; -}; - |
