diff --git a/nanobot/providers/anthropic_oauth.py b/nanobot/providers/anthropic_oauth.py index a840faa..c14eb03 100644 --- a/nanobot/providers/anthropic_oauth.py +++ b/nanobot/providers/anthropic_oauth.py @@ -148,6 +148,9 @@ class AnthropicOAuthProvider(LLMProvider): if role == "user": content = msg.get("content", "") + # Convert OpenAI image_url blocks to Anthropic image blocks + if isinstance(content, list): + content = self._convert_image_blocks(content) # Merge text into previous user message if it has tool_result blocks # (handles the "Reflect on the results" interleaved message) if converted and converted[-1].get("role") == "user": @@ -167,6 +170,33 @@ class AnthropicOAuthProvider(LLMProvider): system_prompt = "\n\n".join(system_parts) return system_prompt, converted + @staticmethod + def _convert_image_blocks(content: list[dict[str, Any]]) -> list[dict[str, Any]]: + """Convert OpenAI image_url blocks to Anthropic image blocks. + + OpenAI format: {"type": "image_url", "image_url": {"url": "data:mime;base64,DATA"}} + Anthropic format: {"type": "image", "source": {"type": "base64", "media_type": "mime", "data": "DATA"}} + """ + converted = [] + for block in content: + if block.get("type") == "image_url": + url = block.get("image_url", {}).get("url", "") + if url.startswith("data:") and ";base64," in url: + header, data = url.split(";base64,", 1) + media_type = header.removeprefix("data:") + converted.append({ + "type": "image", + "source": {"type": "base64", "media_type": media_type, "data": data}, + }) + else: + converted.append({ + "type": "image", + "source": {"type": "url", "url": url}, + }) + else: + converted.append(block) + return converted + def _convert_tools_to_anthropic( self, tools: list[dict[str, Any]] | None