diff options
| author | T <t@tjp.lol> | 2026-05-27 07:30:51 -0600 |
|---|---|---|
| committer | T <t@tjp.lol> | 2026-05-27 07:34:46 -0600 |
| commit | 75fd7d7833052383e1b61d4275d12c9a993c02d3 (patch) | |
| tree | e63f25a697cac215281afe76e71892a69af23cb2 /libpanto/src/provider_anthropic_messages.zig | |
| parent | eddebe4e8131b7d2112b067fed07e7493d8b1984 (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.zig | 21 |
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); |
