summaryrefslogtreecommitdiff
path: root/libpanto/src/provider.zig
diff options
context:
space:
mode:
Diffstat (limited to 'libpanto/src/provider.zig')
-rw-r--r--libpanto/src/provider.zig21
1 files changed, 15 insertions, 6 deletions
diff --git a/libpanto/src/provider.zig b/libpanto/src/provider.zig
index fd170f0..bc39026 100644
--- a/libpanto/src/provider.zig
+++ b/libpanto/src/provider.zig
@@ -19,12 +19,16 @@ pub const ContentBlockType = enum {
/// 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`.
+/// Tool-use identity (`id`, `name`) is delivered via `onToolDetails`, fired
+/// once per ToolUse block at the earliest moment both fields are known. For
+/// Anthropic this is immediately after `onBlockStart`, before any deltas. For
+/// OpenAI Chat Completions this may be partway through the arg-deltas, since
+/// the wire protocol can split `id` and `name` across multiple streaming
+/// chunks. The only guarantees are: it fires strictly after the block's
+/// `onBlockStart`, strictly before its `onBlockComplete`, and at most once
+/// per ToolUse block. It never fires for non-ToolUse blocks. If a tool_use
+/// block is dropped because identity never fully arrived, `onToolDetails`
+/// (and `onBlockComplete`) never fire for it.
///
/// `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)
@@ -34,6 +38,7 @@ pub const ContentBlockType = enum {
pub const ReceiverVTable = struct {
onMessageStart: *const fn (*anyopaque, conversation.MessageRole) anyerror!void,
onBlockStart: *const fn (*anyopaque, ContentBlockType, usize) anyerror!void,
+ onToolDetails: *const fn (*anyopaque, usize, []const u8, []const u8) 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,
@@ -52,6 +57,10 @@ pub const Receiver = struct {
try self.vtable.onBlockStart(self.ptr, block_type, index);
}
+ pub fn onToolDetails(self: Receiver, block_index: usize, id: []const u8, name: []const u8) !void {
+ try self.vtable.onToolDetails(self.ptr, block_index, id, name);
+ }
+
pub fn onContentDelta(self: Receiver, block_index: usize, delta: []const u8) !void {
try self.vtable.onContentDelta(self.ptr, block_index, delta);
}