summaryrefslogtreecommitdiff
path: root/libpanto/src/provider_anthropic_messages.zig
diff options
context:
space:
mode:
authorT <t@tjp.lol>2026-05-27 07:30:51 -0600
committerT <t@tjp.lol>2026-05-27 07:34:46 -0600
commit75fd7d7833052383e1b61d4275d12c9a993c02d3 (patch)
treee63f25a697cac215281afe76e71892a69af23cb2 /libpanto/src/provider_anthropic_messages.zig
parenteddebe4e8131b7d2112b067fed07e7493d8b1984 (diff)
separate event for providing tool details
OpenAI and Anthropic have tool details (id, name) converge on different timelines. In all cases they are available in `onBlockComplete` which comes with the full block, and previously we had an optional `BlockMeta` argument to `onBlockStart` - the anthropic timeline. We removed the `BlockMeta` from `onBlockStart` since it was always going to be unreliable, and now have an explicit `onToolDetails` instead. This allows us to provide the tool id and name as early as possible from the openai provider (could be in the middle of content deltas), and provide a reliable signal across providers. Also, fix test output.
Diffstat (limited to 'libpanto/src/provider_anthropic_messages.zig')
-rw-r--r--libpanto/src/provider_anthropic_messages.zig21
1 files changed, 21 insertions, 0 deletions
diff --git a/libpanto/src/provider_anthropic_messages.zig b/libpanto/src/provider_anthropic_messages.zig
index 03c1d90..96649b6 100644
--- a/libpanto/src/provider_anthropic_messages.zig
+++ b/libpanto/src/provider_anthropic_messages.zig
@@ -283,6 +283,15 @@ const StreamState = struct {
};
if (block_type) |bt| {
try receiver.onBlockStart(bt, wire_index);
+ // Anthropic delivers tool id+name whole on content_block_start,
+ // so we can fire onToolDetails immediately — before any arg
+ // deltas. If the wire was malformed and either field is
+ // missing, skip: closeBlock will drop the block defensively.
+ if (kind == .tool_use) {
+ if (ab.tool_id != null and ab.tool_name != null) {
+ try receiver.onToolDetails(wire_index, ab.tool_id.?, ab.tool_name.?);
+ }
+ }
}
}
@@ -506,6 +515,7 @@ const RecordingReceiver = struct {
const vt: provider_mod.ReceiverVTable = .{
.onMessageStart = onMessageStart,
.onBlockStart = onBlockStart,
+ .onToolDetails = onToolDetails,
.onContentDelta = onContentDelta,
.onBlockComplete = onBlockComplete,
.onMessageComplete = onMessageComplete,
@@ -524,6 +534,17 @@ const RecordingReceiver = struct {
const self: *RecordingReceiver = @ptrCast(@alignCast(ptr));
try self.events.append(self.allocator, .{ .block_start = .{ .kind = bt, .index = idx } });
}
+ fn onToolDetails(
+ _: *anyopaque,
+ _: usize,
+ _: []const u8,
+ _: []const u8,
+ ) anyerror!void {
+ // Anthropic delivers identity at block_start time; the existing
+ // tests assert tool-use blocks via the ContentBlock in conv after
+ // finalize, not via the event stream. We accept and drop these
+ // here to keep the test recorder schema stable.
+ }
fn onContentDelta(ptr: *anyopaque, idx: usize, delta: []const u8) anyerror!void {
const self: *RecordingReceiver = @ptrCast(@alignCast(ptr));
const copy = try self.allocator.dupe(u8, delta);