From 239e91a4d6199c3b6cd83cad63857bf071ea45a6 Mon Sep 17 00:00:00 2001 From: Xubin Ren Date: Wed, 22 Apr 2026 12:42:08 +0000 Subject: [PATCH] test(anthropic): pin tool_result image_url conversion regression Adds a focused regression test so the fix for tool_result image handling cannot silently revert. Two cases: - list content with an image_url + text block -> image_url is translated to a native Anthropic image block, sibling text passes through unchanged - plain string content passes through untouched (the new list branch must not alter the string path) These cover the exact symptom surface (silent image drop with a "Non-transient LLM error with image content" warning) and the only two content shapes tool results actually take today. Made-with: Cursor --- tests/providers/test_anthropic_tool_result.py | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/providers/test_anthropic_tool_result.py diff --git a/tests/providers/test_anthropic_tool_result.py b/tests/providers/test_anthropic_tool_result.py new file mode 100644 index 00000000..5860b8ba --- /dev/null +++ b/tests/providers/test_anthropic_tool_result.py @@ -0,0 +1,57 @@ +"""Tests for AnthropicProvider._tool_result_block image_url conversion. + +Regression for: tool results containing OpenAI-format image_url blocks +(e.g. from read_file on an image file, via build_image_content_blocks) +were passed to Anthropic unconverted, causing silent image drops with a +"Non-transient LLM error with image content, retrying without images" +warning. +""" + +from nanobot.providers.anthropic_provider import AnthropicProvider + + +def test_tool_result_block_converts_image_url_in_list_content(): + """image_url blocks inside tool_result list content must be translated + to Anthropic-native image blocks; sibling text blocks pass through.""" + msg = { + "role": "tool", + "tool_call_id": "call_1", + "content": [ + { + "type": "image_url", + "image_url": {"url": "data:image/png;base64,AAAA"}, + "_meta": {"path": "/tmp/x.png"}, + }, + {"type": "text", "text": "(Image file: /tmp/x.png)"}, + ], + } + block = AnthropicProvider._tool_result_block(msg) + + assert block["type"] == "tool_result" + assert block["tool_use_id"] == "call_1" + content = block["content"] + assert isinstance(content, list) + assert content[0] == { + "type": "image", + "source": { + "type": "base64", + "media_type": "image/png", + "data": "AAAA", + }, + } + assert content[1] == {"type": "text", "text": "(Image file: /tmp/x.png)"} + + +def test_tool_result_block_preserves_string_content(): + """String content must be passed through unchanged; the image-conversion + path for lists must not affect the string path.""" + msg = { + "role": "tool", + "tool_call_id": "call_2", + "content": "plain tool output", + } + block = AnthropicProvider._tool_result_block(msg) + + assert block["type"] == "tool_result" + assert block["tool_use_id"] == "call_2" + assert block["content"] == "plain tool output"