summaryrefslogtreecommitdiff
path: root/libpanto/src/tool.zig
diff options
context:
space:
mode:
Diffstat (limited to 'libpanto/src/tool.zig')
-rw-r--r--libpanto/src/tool.zig84
1 files changed, 49 insertions, 35 deletions
diff --git a/libpanto/src/tool.zig b/libpanto/src/tool.zig
index 60a7736..c96dfee 100644
--- a/libpanto/src/tool.zig
+++ b/libpanto/src/tool.zig
@@ -31,10 +31,11 @@ pub const MediaPart = struct {
data: []const u8,
};
-/// One element of a tool's result. A tool returns a `[]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.
+/// 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,
@@ -51,11 +52,42 @@ pub const ResultPart = union(enum) {
}
};
-/// Free a `[]ResultPart` and every part it owns.
-pub fn freeResultParts(allocator: Allocator, parts: []ResultPart) void {
- for (parts) |p| p.deinit(allocator);
- allocator.free(parts);
-}
+/// 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
@@ -78,11 +110,13 @@ pub const Tool = struct {
/// `input` is the raw JSON bytes the provider sent. The tool is
/// responsible for parsing them if it cares about their structure.
///
- /// Returns an owned slice of `ResultPart`s 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
- /// `freeResultParts`).
+ /// 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
@@ -100,7 +134,7 @@ pub const Tool = struct {
ctx: *anyopaque,
input: []const u8,
allocator: Allocator,
- ) anyerror![]ResultPart,
+ ) anyerror!ResultParts,
/// Called when the tool is unregistered or the registry is torn
/// down. Frees any resources owned by `ctx`, including `ctx`
@@ -113,23 +147,3 @@ pub const Tool = struct {
};
};
-/// Convenience: allocate a single-element `[]ResultPart` holding one text
-/// part that owns `text` (duped from the input slice).
-pub fn textResult(allocator: Allocator, text: []const u8) ![]ResultPart {
- const owned = try allocator.dupe(u8, text);
- errdefer allocator.free(owned);
- const parts = try allocator.alloc(ResultPart, 1);
- parts[0] = .{ .text = owned };
- return parts;
-}
-
-/// Convenience: wrap an already-owned `text` slice as a single-element
-/// `[]ResultPart`. Takes ownership of `text`.
-pub fn ownedTextResult(allocator: Allocator, text: []u8) ![]ResultPart {
- const parts = allocator.alloc(ResultPart, 1) catch |e| {
- allocator.free(text);
- return e;
- };
- parts[0] = .{ .text = text };
- return parts;
-}