From ac5c4898dfa0a9e57424336774893dfc72b132e9 Mon Sep 17 00:00:00 2001 From: t Date: Wed, 3 Jun 2026 09:11:36 -0600 Subject: image uploads --- libpanto/src/conversation.zig | 49 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) (limited to 'libpanto/src/conversation.zig') diff --git a/libpanto/src/conversation.zig b/libpanto/src/conversation.zig index 780f650..2c29f8e 100644 --- a/libpanto/src/conversation.zig +++ b/libpanto/src/conversation.zig @@ -41,13 +41,58 @@ pub const ToolUseBlock = struct { } }; +/// A binary attachment stored in a tool result. `media_type` is owned +/// (e.g. "image/png"); `data` is the streaming text buffer holding +/// base64-encoded bytes. +pub const StoredMediaPart = struct { + media_type: []const u8, + data: TextualBlock = .empty, + + pub fn deinit(self: *StoredMediaPart, alloc: Allocator) void { + alloc.free(self.media_type); + self.data.deinit(alloc); + } +}; + +/// One part of a tool result as stored in the conversation. Text uses the +/// streaming `TextualBlock` form to preserve incremental-append semantics. +pub const ResultPartStored = union(enum) { + text: TextualBlock, + media: StoredMediaPart, + + pub fn deinit(self: *ResultPartStored, alloc: Allocator) void { + switch (self.*) { + .text => |*t| t.deinit(alloc), + .media => |*m| m.deinit(alloc), + } + } +}; + pub const ToolResultBlock = struct { tool_use_id: []const u8, - content: TextualBlock = .empty, + parts: std.ArrayList(ResultPartStored) = .empty, pub fn deinit(self: *ToolResultBlock, alloc: Allocator) void { alloc.free(self.tool_use_id); - self.content.deinit(alloc); + for (self.parts.items) |*p| p.deinit(alloc); + self.parts.deinit(alloc); + } + + /// Concatenate all text parts into `out`. Media parts are skipped. + /// Used by callers (compaction, OpenAI fan-out) that want only the + /// textual portion. + pub fn appendTextInto(self: ToolResultBlock, alloc: Allocator, out: *TextualBlock) !void { + for (self.parts.items) |p| { + if (p == .text) try out.appendSlice(alloc, p.text.items); + } + } + + /// True if any part is a media attachment. + pub fn hasMedia(self: ToolResultBlock) bool { + for (self.parts.items) |p| { + if (p == .media) return true; + } + return false; } }; -- cgit v1.3