Skip to content

Commit 3f6d031

Browse files
Extract image MIME type detection to shared utility
Move BASE64_IMAGE_PREFIXES mapping and detect_image_mime_type() function to openhands/tools/utils/image.py to eliminate code duplication. Update browser_use to import from shared utility module. Add comprehensive test coverage for the utility function. Co-authored-by: openhands <[email protected]>
1 parent 374a3e6 commit 3f6d031

File tree

4 files changed

+104
-23
lines changed

4 files changed

+104
-23
lines changed

openhands-tools/openhands/tools/browser_use/definition.py

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
register_tool,
1515
)
1616
from openhands.sdk.utils import maybe_truncate
17+
from openhands.tools.utils.image import detect_image_mime_type
1718

1819

1920
# Lazy import to avoid hanging during module import
@@ -24,29 +25,6 @@
2425
# Maximum output size for browser observations
2526
MAX_BROWSER_OUTPUT_SIZE = 50000
2627

27-
# Mapping of base64 prefixes to MIME types for image detection
28-
BASE64_IMAGE_PREFIXES = {
29-
"/9j/": "image/jpeg",
30-
"iVBORw0KGgo": "image/png",
31-
"R0lGODlh": "image/gif",
32-
"UklGR": "image/webp",
33-
}
34-
35-
36-
def detect_image_mime_type(base64_data: str) -> str:
37-
"""Detect MIME type from base64-encoded image data.
38-
39-
Args:
40-
base64_data: Base64-encoded image data
41-
42-
Returns:
43-
Detected MIME type, defaults to "image/png" if not detected
44-
"""
45-
for prefix, mime_type in BASE64_IMAGE_PREFIXES.items():
46-
if base64_data.startswith(prefix):
47-
return mime_type
48-
return "image/png"
49-
5028

5129
class BrowserObservation(Observation):
5230
"""Base observation for browser operations."""
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Image-related utility functions."""
2+
3+
# Mapping of base64 prefixes to MIME types for image detection
4+
BASE64_IMAGE_PREFIXES = {
5+
"/9j/": "image/jpeg",
6+
"iVBORw0KGgo": "image/png",
7+
"R0lGODlh": "image/gif",
8+
"UklGR": "image/webp",
9+
}
10+
11+
12+
def detect_image_mime_type(base64_data: str) -> str:
13+
"""Detect MIME type from base64-encoded image data.
14+
15+
Args:
16+
base64_data: Base64-encoded image data
17+
18+
Returns:
19+
Detected MIME type, defaults to "image/png" if not detected
20+
"""
21+
for prefix, mime_type in BASE64_IMAGE_PREFIXES.items():
22+
if base64_data.startswith(prefix):
23+
return mime_type
24+
return "image/png"

tests/tools/utils/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Tests for tool utilities."""

tests/tools/utils/test_image.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""Tests for image utility functions."""
2+
3+
import pytest
4+
5+
from openhands.tools.utils.image import (
6+
BASE64_IMAGE_PREFIXES,
7+
detect_image_mime_type,
8+
)
9+
10+
11+
def test_base64_image_prefixes_mapping():
12+
"""Test that BASE64_IMAGE_PREFIXES contains expected mappings."""
13+
assert BASE64_IMAGE_PREFIXES["/9j/"] == "image/jpeg"
14+
assert BASE64_IMAGE_PREFIXES["iVBORw0KGgo"] == "image/png"
15+
assert BASE64_IMAGE_PREFIXES["R0lGODlh"] == "image/gif"
16+
assert BASE64_IMAGE_PREFIXES["UklGR"] == "image/webp"
17+
18+
19+
def test_detect_image_mime_type_jpeg():
20+
"""Test JPEG detection."""
21+
jpeg_base64 = "/9j/4AAQSkZJRg..."
22+
assert detect_image_mime_type(jpeg_base64) == "image/jpeg"
23+
24+
25+
def test_detect_image_mime_type_png():
26+
"""Test PNG detection."""
27+
png_base64 = "iVBORw0KGgoAAAANSUhEUg..."
28+
assert detect_image_mime_type(png_base64) == "image/png"
29+
30+
31+
def test_detect_image_mime_type_gif():
32+
"""Test GIF detection."""
33+
gif_base64 = "R0lGODlhAQABAIAAAP..."
34+
assert detect_image_mime_type(gif_base64) == "image/gif"
35+
36+
37+
def test_detect_image_mime_type_webp():
38+
"""Test WebP detection."""
39+
webp_base64 = "UklGRiQAAABXRUJQ..."
40+
assert detect_image_mime_type(webp_base64) == "image/webp"
41+
42+
43+
def test_detect_image_mime_type_unknown():
44+
"""Test unknown format defaults to PNG."""
45+
unknown_base64 = "UNKNOWN_FORMAT_DATA"
46+
assert detect_image_mime_type(unknown_base64) == "image/png"
47+
48+
49+
def test_detect_image_mime_type_empty_string():
50+
"""Test empty string defaults to PNG."""
51+
assert detect_image_mime_type("") == "image/png"
52+
53+
54+
@pytest.mark.parametrize(
55+
"base64_data,expected_mime",
56+
[
57+
(
58+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", # noqa: E501
59+
"image/png",
60+
),
61+
(
62+
"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/", # noqa: E501
63+
"image/jpeg",
64+
),
65+
(
66+
"R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
67+
"image/gif",
68+
),
69+
(
70+
"UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAQAcJaQAA3AA/v3AgAA=",
71+
"image/webp",
72+
),
73+
("AAAABBBBCCCC", "image/png"), # Unknown format
74+
],
75+
)
76+
def test_detect_image_mime_type_parametrized(base64_data, expected_mime):
77+
"""Test MIME type detection with various real base64 samples."""
78+
assert detect_image_mime_type(base64_data) == expected_mime

0 commit comments

Comments
 (0)