diff options
| author | T <t@tjp.lol> | 2026-05-27 06:25:52 -0600 |
|---|---|---|
| committer | T <t@tjp.lol> | 2026-05-27 07:02:47 -0600 |
| commit | eddebe4e8131b7d2112b067fed07e7493d8b1984 (patch) | |
| tree | 21cc58822aa4e99d55d082444c2347c15101bfe1 /libpanto/src/provider.zig | |
| parent | 1f0915edbe0213e8bc134922f10933468d35a172 (diff) | |
Stream OpenAI tool_use as discrete blocks; drop BlockMeta
Close each tool_use on next-index delta (and at finalize) so receivers
see one start/delta/complete trio per tool, matching Anthropic. Log+drop
fragments for already-closed indices.
Remove BlockMeta from the Receiver interface: tool id/name can't be
reliably known at onBlockStart under OpenAI's streaming model. Receivers
get identity from the assembled ContentBlock at onBlockComplete. CLI
renders 'tool: {args} : (name)' accordingly.
Diffstat (limited to 'libpanto/src/provider.zig')
| -rw-r--r-- | libpanto/src/provider.zig | 19 |
1 files changed, 10 insertions, 9 deletions
diff --git a/libpanto/src/provider.zig b/libpanto/src/provider.zig index 177fc2f..fd170f0 100644 --- a/libpanto/src/provider.zig +++ b/libpanto/src/provider.zig @@ -12,12 +12,6 @@ pub const ContentBlockType = enum { ToolResult, }; -pub const BlockMeta = struct { - /// Only populated for ToolUse blocks. Null for Text/Thinking. - tool_id: ?[]const u8 = null, - tool_name: ?[]const u8 = null, -}; - /// Vtable for receiving streaming events from a Provider. /// /// The lifecycle callbacks (`onMessageStart` ... `onMessageComplete`) return @@ -25,6 +19,13 @@ pub const BlockMeta = struct { /// stops streaming, calls `onError(err)`, and propagates `err` out of /// `streamStep`. No partial assistant message is appended to the conversation. /// +/// Identity fields (tool id/name for ToolUse blocks) are intentionally *not* +/// surfaced at `onBlockStart`. They cannot be reliably known at start time +/// across all wire protocols — OpenAI streaming may deliver `id` and `name` +/// in fragments across multiple deltas — so any "meta" we passed at start +/// would be a phantom guarantee. Receivers that need identity get it from +/// the fully-assembled `ContentBlock` delivered to `onBlockComplete`. +/// /// `onError` is the receiver's cleanup hook. It fires exactly once per failed /// turn, whether the error originated in the receiver itself (a write failure) /// or in the Provider (HTTP/parse/stream failure). It is the last callback the @@ -32,7 +33,7 @@ pub const BlockMeta = struct { /// must swallow secondary failures during cleanup. pub const ReceiverVTable = struct { onMessageStart: *const fn (*anyopaque, conversation.MessageRole) anyerror!void, - onBlockStart: *const fn (*anyopaque, ContentBlockType, usize, ?BlockMeta) anyerror!void, + onBlockStart: *const fn (*anyopaque, ContentBlockType, usize) anyerror!void, onContentDelta: *const fn (*anyopaque, usize, []const u8) anyerror!void, onBlockComplete: *const fn (*anyopaque, usize, conversation.ContentBlock) anyerror!void, onMessageComplete: *const fn (*anyopaque, conversation.Message) anyerror!void, @@ -47,8 +48,8 @@ pub const Receiver = struct { try self.vtable.onMessageStart(self.ptr, role); } - pub fn onBlockStart(self: Receiver, block_type: ContentBlockType, index: usize, meta: ?BlockMeta) !void { - try self.vtable.onBlockStart(self.ptr, block_type, index, meta); + pub fn onBlockStart(self: Receiver, block_type: ContentBlockType, index: usize) !void { + try self.vtable.onBlockStart(self.ptr, block_type, index); } pub fn onContentDelta(self: Receiver, block_index: usize, delta: []const u8) !void { |
