From 3f1ace16afc7877b0bfad374cb286d4d84140960 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 4 Jun 2026 09:46:49 -0600 Subject: failure retries scheme --- libpanto/src/anthropic_messages_json.zig | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'libpanto/src/anthropic_messages_json.zig') diff --git a/libpanto/src/anthropic_messages_json.zig b/libpanto/src/anthropic_messages_json.zig index 8e33539..940d55e 100644 --- a/libpanto/src/anthropic_messages_json.zig +++ b/libpanto/src/anthropic_messages_json.zig @@ -232,6 +232,13 @@ fn writeBlock(s: *std.json.Stringify, block: conversation.ContentBlock) !void { try s.write("tool_result"); try s.objectField("tool_use_id"); try s.write(tr.tool_use_id); + // Anthropic supports an `is_error` marker on tool results; + // emit it only when set (omitting it defaults to a success + // result on the wire). + if (tr.is_error) { + try s.objectField("is_error"); + try s.write(true); + } // Anthropic accepts `content` as an array of typed blocks: // text, image (base64 source), and document (PDF). try s.objectField("content"); @@ -1053,12 +1060,43 @@ test "serializeRequest - user ToolResult becomes tool_result content block" { try testing.expectEqual(@as(usize, 1), arr.len); try testing.expectEqualStrings("tool_result", arr[0].object.get("type").?.string); try testing.expectEqualStrings("tu_1", arr[0].object.get("tool_use_id").?.string); + // A successful result omits the is_error marker entirely. + try testing.expect(arr[0].object.get("is_error") == null); const tr_content = arr[0].object.get("content").?.array.items; try testing.expectEqual(@as(usize, 1), tr_content.len); try testing.expectEqualStrings("text", tr_content[0].object.get("type").?.string); try testing.expectEqualStrings("the answer is 42", tr_content[0].object.get("text").?.string); } +test "serializeRequest - error ToolResult emits is_error: true" { + const allocator = testing.allocator; + + var conv = conversation.Conversation.init(allocator); + defer conv.deinit(); + + const id = try allocator.dupe(u8, "tu_err"); + var parts: std.ArrayList(conversation.ResultPartStored) = .empty; + try parts.append(allocator, .{ .text = try conversation.textualBlockFromSlice(allocator, "file not found") }); + + var blocks: std.ArrayList(conversation.ContentBlock) = .empty; + try blocks.append(allocator, .{ .ToolResult = .{ .tool_use_id = id, .parts = parts, .is_error = true } }); + try conv.messages.append(allocator, .{ .role = .user, .content = blocks }); + + var empty_tools = tool_registry_mod.ToolRegistry.init(allocator); + defer empty_tools.deinit(); + const cfg = testConfig("claude-x"); + const body = try serializeRequest(allocator, &cfg, &conv, &empty_tools); + defer allocator.free(body); + + var parsed = try std.json.parseFromSlice(std.json.Value, allocator, body, .{}); + defer parsed.deinit(); + + const msg = parsed.value.object.get("messages").?.array.items[0].object; + const arr = msg.get("content").?.array.items; + try testing.expectEqualStrings("tool_result", arr[0].object.get("type").?.string); + try testing.expect(arr[0].object.get("is_error").?.bool); +} + test "serializeRequest - tool result with image part emits image block" { const allocator = testing.allocator; -- cgit v1.3