hud-python 0.3.5__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hud-python might be problematic. Click here for more details.
- hud/__init__.py +22 -89
- hud/agents/__init__.py +17 -0
- hud/agents/art.py +101 -0
- hud/agents/base.py +599 -0
- hud/{mcp → agents}/claude.py +373 -321
- hud/{mcp → agents}/langchain.py +250 -250
- hud/agents/misc/__init__.py +7 -0
- hud/{agent → agents}/misc/response_agent.py +80 -80
- hud/{mcp → agents}/openai.py +352 -334
- hud/agents/openai_chat_generic.py +154 -0
- hud/{mcp → agents}/tests/__init__.py +1 -1
- hud/agents/tests/test_base.py +742 -0
- hud/agents/tests/test_claude.py +324 -0
- hud/{mcp → agents}/tests/test_client.py +363 -324
- hud/{mcp → agents}/tests/test_openai.py +237 -238
- hud/cli/__init__.py +617 -0
- hud/cli/__main__.py +8 -0
- hud/cli/analyze.py +371 -0
- hud/cli/analyze_metadata.py +230 -0
- hud/cli/build.py +427 -0
- hud/cli/clone.py +185 -0
- hud/cli/cursor.py +92 -0
- hud/cli/debug.py +392 -0
- hud/cli/docker_utils.py +83 -0
- hud/cli/init.py +281 -0
- hud/cli/interactive.py +353 -0
- hud/cli/mcp_server.py +756 -0
- hud/cli/pull.py +336 -0
- hud/cli/push.py +379 -0
- hud/cli/remote_runner.py +311 -0
- hud/cli/runner.py +160 -0
- hud/cli/tests/__init__.py +3 -0
- hud/cli/tests/test_analyze.py +284 -0
- hud/cli/tests/test_cli_init.py +265 -0
- hud/cli/tests/test_cli_main.py +27 -0
- hud/cli/tests/test_clone.py +142 -0
- hud/cli/tests/test_cursor.py +253 -0
- hud/cli/tests/test_debug.py +453 -0
- hud/cli/tests/test_mcp_server.py +139 -0
- hud/cli/tests/test_utils.py +388 -0
- hud/cli/utils.py +263 -0
- hud/clients/README.md +143 -0
- hud/clients/__init__.py +16 -0
- hud/clients/base.py +354 -0
- hud/clients/fastmcp.py +202 -0
- hud/clients/mcp_use.py +278 -0
- hud/clients/tests/__init__.py +1 -0
- hud/clients/tests/test_client_integration.py +111 -0
- hud/clients/tests/test_fastmcp.py +342 -0
- hud/clients/tests/test_protocol.py +188 -0
- hud/clients/utils/__init__.py +1 -0
- hud/clients/utils/retry_transport.py +160 -0
- hud/datasets.py +322 -192
- hud/misc/__init__.py +1 -0
- hud/{agent → misc}/claude_plays_pokemon.py +292 -283
- hud/otel/__init__.py +35 -0
- hud/otel/collector.py +142 -0
- hud/otel/config.py +164 -0
- hud/otel/context.py +536 -0
- hud/otel/exporters.py +366 -0
- hud/otel/instrumentation.py +97 -0
- hud/otel/processors.py +118 -0
- hud/otel/tests/__init__.py +1 -0
- hud/otel/tests/test_processors.py +197 -0
- hud/server/__init__.py +5 -5
- hud/server/context.py +114 -0
- hud/server/helper/__init__.py +5 -0
- hud/server/low_level.py +132 -0
- hud/server/server.py +166 -0
- hud/server/tests/__init__.py +3 -0
- hud/settings.py +73 -79
- hud/shared/__init__.py +5 -0
- hud/{exceptions.py → shared/exceptions.py} +180 -180
- hud/{server → shared}/requests.py +264 -264
- hud/shared/tests/test_exceptions.py +157 -0
- hud/{server → shared}/tests/test_requests.py +275 -275
- hud/telemetry/__init__.py +25 -30
- hud/telemetry/instrument.py +379 -0
- hud/telemetry/job.py +309 -141
- hud/telemetry/replay.py +74 -0
- hud/telemetry/trace.py +83 -0
- hud/tools/__init__.py +33 -34
- hud/tools/base.py +365 -65
- hud/tools/bash.py +161 -137
- hud/tools/computer/__init__.py +15 -13
- hud/tools/computer/anthropic.py +437 -420
- hud/tools/computer/hud.py +376 -334
- hud/tools/computer/openai.py +295 -292
- hud/tools/computer/settings.py +82 -0
- hud/tools/edit.py +314 -290
- hud/tools/executors/__init__.py +30 -30
- hud/tools/executors/base.py +539 -532
- hud/tools/executors/pyautogui.py +621 -619
- hud/tools/executors/tests/__init__.py +1 -1
- hud/tools/executors/tests/test_base_executor.py +338 -338
- hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
- hud/tools/executors/xdo.py +511 -503
- hud/tools/{playwright_tool.py → playwright.py} +412 -379
- hud/tools/tests/__init__.py +3 -3
- hud/tools/tests/test_base.py +282 -0
- hud/tools/tests/test_bash.py +158 -152
- hud/tools/tests/test_bash_extended.py +197 -0
- hud/tools/tests/test_computer.py +425 -52
- hud/tools/tests/test_computer_actions.py +34 -34
- hud/tools/tests/test_edit.py +259 -240
- hud/tools/tests/test_init.py +27 -27
- hud/tools/tests/test_playwright_tool.py +183 -183
- hud/tools/tests/test_tools.py +145 -157
- hud/tools/tests/test_utils.py +156 -156
- hud/tools/types.py +72 -0
- hud/tools/utils.py +50 -50
- hud/types.py +136 -89
- hud/utils/__init__.py +10 -16
- hud/utils/async_utils.py +65 -0
- hud/utils/design.py +168 -0
- hud/utils/mcp.py +55 -0
- hud/utils/progress.py +149 -149
- hud/utils/telemetry.py +66 -66
- hud/utils/tests/test_async_utils.py +173 -0
- hud/utils/tests/test_init.py +17 -21
- hud/utils/tests/test_progress.py +261 -225
- hud/utils/tests/test_telemetry.py +82 -37
- hud/utils/tests/test_version.py +8 -8
- hud/version.py +7 -7
- hud_python-0.4.0.dist-info/METADATA +474 -0
- hud_python-0.4.0.dist-info/RECORD +132 -0
- hud_python-0.4.0.dist-info/entry_points.txt +3 -0
- {hud_python-0.3.5.dist-info → hud_python-0.4.0.dist-info}/licenses/LICENSE +21 -21
- hud/adapters/__init__.py +0 -8
- hud/adapters/claude/__init__.py +0 -5
- hud/adapters/claude/adapter.py +0 -180
- hud/adapters/claude/tests/__init__.py +0 -1
- hud/adapters/claude/tests/test_adapter.py +0 -519
- hud/adapters/common/__init__.py +0 -6
- hud/adapters/common/adapter.py +0 -178
- hud/adapters/common/tests/test_adapter.py +0 -289
- hud/adapters/common/types.py +0 -446
- hud/adapters/operator/__init__.py +0 -5
- hud/adapters/operator/adapter.py +0 -108
- hud/adapters/operator/tests/__init__.py +0 -1
- hud/adapters/operator/tests/test_adapter.py +0 -370
- hud/agent/__init__.py +0 -19
- hud/agent/base.py +0 -126
- hud/agent/claude.py +0 -271
- hud/agent/langchain.py +0 -215
- hud/agent/misc/__init__.py +0 -3
- hud/agent/operator.py +0 -268
- hud/agent/tests/__init__.py +0 -1
- hud/agent/tests/test_base.py +0 -202
- hud/env/__init__.py +0 -11
- hud/env/client.py +0 -35
- hud/env/docker_client.py +0 -349
- hud/env/environment.py +0 -446
- hud/env/local_docker_client.py +0 -358
- hud/env/remote_client.py +0 -212
- hud/env/remote_docker_client.py +0 -292
- hud/gym.py +0 -130
- hud/job.py +0 -773
- hud/mcp/__init__.py +0 -17
- hud/mcp/base.py +0 -631
- hud/mcp/client.py +0 -312
- hud/mcp/tests/test_base.py +0 -512
- hud/mcp/tests/test_claude.py +0 -294
- hud/task.py +0 -149
- hud/taskset.py +0 -237
- hud/telemetry/_trace.py +0 -347
- hud/telemetry/context.py +0 -230
- hud/telemetry/exporter.py +0 -575
- hud/telemetry/instrumentation/__init__.py +0 -3
- hud/telemetry/instrumentation/mcp.py +0 -259
- hud/telemetry/instrumentation/registry.py +0 -59
- hud/telemetry/mcp_models.py +0 -270
- hud/telemetry/tests/__init__.py +0 -1
- hud/telemetry/tests/test_context.py +0 -210
- hud/telemetry/tests/test_trace.py +0 -312
- hud/tools/helper/README.md +0 -56
- hud/tools/helper/__init__.py +0 -9
- hud/tools/helper/mcp_server.py +0 -78
- hud/tools/helper/server_initialization.py +0 -115
- hud/tools/helper/utils.py +0 -58
- hud/trajectory.py +0 -94
- hud/utils/agent.py +0 -37
- hud/utils/common.py +0 -256
- hud/utils/config.py +0 -120
- hud/utils/deprecation.py +0 -115
- hud/utils/misc.py +0 -53
- hud/utils/tests/test_common.py +0 -277
- hud/utils/tests/test_config.py +0 -129
- hud_python-0.3.5.dist-info/METADATA +0 -284
- hud_python-0.3.5.dist-info/RECORD +0 -120
- /hud/{adapters/common → shared}/tests/__init__.py +0 -0
- {hud_python-0.3.5.dist-info → hud_python-0.4.0.dist-info}/WHEEL +0 -0
hud/adapters/common/adapter.py
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING, Any, TypeAlias
|
|
4
|
-
|
|
5
|
-
from pydantic import TypeAdapter, ValidationError
|
|
6
|
-
|
|
7
|
-
from .types import CLA
|
|
8
|
-
|
|
9
|
-
if TYPE_CHECKING:
|
|
10
|
-
import numpy as np
|
|
11
|
-
from PIL import Image
|
|
12
|
-
from typing_extensions import TypeIs
|
|
13
|
-
|
|
14
|
-
ImageType: TypeAlias = np.ndarray[Any, Any] | Image.Image | str | None
|
|
15
|
-
else:
|
|
16
|
-
ImageType: TypeAlias = Any | str | None
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _is_numpy_array(observation: Any) -> TypeIs[np.ndarray]:
|
|
20
|
-
"""Check if the observation is a numpy array, without requiring numpy."""
|
|
21
|
-
try:
|
|
22
|
-
import numpy as np # type: ignore
|
|
23
|
-
|
|
24
|
-
return isinstance(observation, np.ndarray)
|
|
25
|
-
except (ModuleNotFoundError, NameError):
|
|
26
|
-
return False
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class Adapter:
|
|
30
|
-
def __init__(self) -> None:
|
|
31
|
-
self.memory = []
|
|
32
|
-
|
|
33
|
-
self.agent_width = 1920
|
|
34
|
-
self.agent_height = 1080
|
|
35
|
-
self.env_width = 1920
|
|
36
|
-
self.env_height = 1080
|
|
37
|
-
|
|
38
|
-
def preprocess(self, action: Any) -> Any:
|
|
39
|
-
return action
|
|
40
|
-
|
|
41
|
-
def convert(self, action: Any) -> CLA:
|
|
42
|
-
if action is None:
|
|
43
|
-
raise ValueError("Please provide a valid action")
|
|
44
|
-
try:
|
|
45
|
-
return TypeAdapter(CLA).validate_python(action)
|
|
46
|
-
except ValidationError as e:
|
|
47
|
-
raise ValueError(f"Invalid action type in conversion: {action}") from e
|
|
48
|
-
|
|
49
|
-
def json(self, action: CLA) -> Any:
|
|
50
|
-
if action is None:
|
|
51
|
-
raise ValueError("Please provide a valid action")
|
|
52
|
-
try:
|
|
53
|
-
validated = TypeAdapter(CLA).validate_python(action)
|
|
54
|
-
return validated.model_dump()
|
|
55
|
-
except ValidationError as e:
|
|
56
|
-
raise ValueError(f"Invalid action type in json creation: {action}") from e
|
|
57
|
-
|
|
58
|
-
def rescale(self, observation: ImageType) -> str | None:
|
|
59
|
-
"""
|
|
60
|
-
Resize the observation (image) to agent-specific dimensions.
|
|
61
|
-
|
|
62
|
-
Args:
|
|
63
|
-
observation: Image data, which can be:
|
|
64
|
-
- numpy array
|
|
65
|
-
- PIL Image
|
|
66
|
-
- base64 string (PNG) # TODO: JPG
|
|
67
|
-
|
|
68
|
-
Returns:
|
|
69
|
-
Base64-encoded string of the resized image (PNG format)
|
|
70
|
-
"""
|
|
71
|
-
if observation is None:
|
|
72
|
-
return None
|
|
73
|
-
|
|
74
|
-
# Import PIL only when needed
|
|
75
|
-
try:
|
|
76
|
-
from PIL import Image
|
|
77
|
-
except ImportError:
|
|
78
|
-
raise ImportError(
|
|
79
|
-
"PIL (Pillow) is required for image processing. "
|
|
80
|
-
"Please install it with 'pip install Pillow'"
|
|
81
|
-
) from None
|
|
82
|
-
|
|
83
|
-
# Handle different input types.
|
|
84
|
-
if _is_numpy_array(observation):
|
|
85
|
-
# Convert numpy array to PIL Image
|
|
86
|
-
img = Image.fromarray(observation)
|
|
87
|
-
elif isinstance(observation, Image.Image):
|
|
88
|
-
img = observation
|
|
89
|
-
elif isinstance(observation, str):
|
|
90
|
-
# Assume it's a base64 string
|
|
91
|
-
try:
|
|
92
|
-
import base64
|
|
93
|
-
import io
|
|
94
|
-
|
|
95
|
-
# Remove header if present (e.g., 'data:image/png;base64,')
|
|
96
|
-
if "," in observation:
|
|
97
|
-
observation = observation.split(",")[1]
|
|
98
|
-
# Decode base64 string to bytes
|
|
99
|
-
img_bytes = base64.b64decode(observation)
|
|
100
|
-
# Convert to PIL Image
|
|
101
|
-
img = Image.open(io.BytesIO(img_bytes))
|
|
102
|
-
except Exception as e:
|
|
103
|
-
raise ValueError(f"Failed to decode base64 image: {e}") from None
|
|
104
|
-
else:
|
|
105
|
-
raise ValueError(f"Unsupported observation type: {type(observation)}")
|
|
106
|
-
|
|
107
|
-
# Update environment dimensions
|
|
108
|
-
self.env_width, self.env_height = img.size
|
|
109
|
-
|
|
110
|
-
# Resize to agent dimensions
|
|
111
|
-
resized_img = img.resize((self.agent_width, self.agent_height), Image.Resampling.LANCZOS)
|
|
112
|
-
|
|
113
|
-
# Always convert to base64 string
|
|
114
|
-
import base64
|
|
115
|
-
import io
|
|
116
|
-
|
|
117
|
-
buffered = io.BytesIO()
|
|
118
|
-
resized_img.save(buffered, format="PNG")
|
|
119
|
-
return base64.b64encode(buffered.getvalue()).decode("utf-8")
|
|
120
|
-
|
|
121
|
-
def postprocess_action(self, action: dict[str, Any]) -> dict[str, Any]:
|
|
122
|
-
"""
|
|
123
|
-
Rescale action coordinates from agent dimensions to environment dimensions.
|
|
124
|
-
|
|
125
|
-
Args:
|
|
126
|
-
action: Action dictionary with coordinates
|
|
127
|
-
|
|
128
|
-
Returns:
|
|
129
|
-
Action with rescaled coordinates
|
|
130
|
-
"""
|
|
131
|
-
if not action:
|
|
132
|
-
return action
|
|
133
|
-
|
|
134
|
-
# Calculate scaling factors
|
|
135
|
-
x_scale = self.env_width / self.agent_width
|
|
136
|
-
y_scale = self.env_height / self.agent_height
|
|
137
|
-
|
|
138
|
-
# Deep copy to avoid modifying the original
|
|
139
|
-
processed_action = action.copy()
|
|
140
|
-
|
|
141
|
-
# Rescale based on action type and structure
|
|
142
|
-
if "point" in processed_action and processed_action["point"] is not None:
|
|
143
|
-
# For actions with a single point (click, move)
|
|
144
|
-
processed_action["point"]["x"] = int(processed_action["point"]["x"] * x_scale)
|
|
145
|
-
processed_action["point"]["y"] = int(processed_action["point"]["y"] * y_scale)
|
|
146
|
-
|
|
147
|
-
if (path := processed_action.get("path")) is not None:
|
|
148
|
-
# For actions with a path (drag)
|
|
149
|
-
for point in path:
|
|
150
|
-
point["x"] = int(point["x"] * x_scale)
|
|
151
|
-
point["y"] = int(point["y"] * y_scale)
|
|
152
|
-
|
|
153
|
-
if "scroll" in processed_action and processed_action["scroll"] is not None:
|
|
154
|
-
# For scroll actions
|
|
155
|
-
processed_action["scroll"]["x"] = int(processed_action["scroll"]["x"] * x_scale)
|
|
156
|
-
processed_action["scroll"]["y"] = int(processed_action["scroll"]["y"] * y_scale)
|
|
157
|
-
|
|
158
|
-
return processed_action
|
|
159
|
-
|
|
160
|
-
def adapt(self, action: Any) -> CLA:
|
|
161
|
-
# any preprocessing steps
|
|
162
|
-
action = self.preprocess(action)
|
|
163
|
-
|
|
164
|
-
# convert to CLA
|
|
165
|
-
action = self.convert(action)
|
|
166
|
-
self.memory.append(action)
|
|
167
|
-
|
|
168
|
-
# convert to json and apply coordinate rescaling
|
|
169
|
-
action_dict = self.json(action)
|
|
170
|
-
rescaled_action = self.postprocess_action(action_dict)
|
|
171
|
-
|
|
172
|
-
# convert back to CLA
|
|
173
|
-
return TypeAdapter(CLA).validate_python(rescaled_action)
|
|
174
|
-
|
|
175
|
-
def adapt_list(self, actions: list[Any]) -> list[CLA]:
|
|
176
|
-
if not isinstance(actions, list):
|
|
177
|
-
raise ValueError("Please provide a list of actions")
|
|
178
|
-
return [self.adapt(action) for action in actions]
|
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import base64
|
|
4
|
-
import io
|
|
5
|
-
from unittest.mock import MagicMock, patch
|
|
6
|
-
|
|
7
|
-
import pytest
|
|
8
|
-
from PIL import Image
|
|
9
|
-
|
|
10
|
-
try:
|
|
11
|
-
import numpy as np
|
|
12
|
-
|
|
13
|
-
HAS_NUMPY = True
|
|
14
|
-
except ImportError:
|
|
15
|
-
HAS_NUMPY = False
|
|
16
|
-
np = None
|
|
17
|
-
|
|
18
|
-
from hud.adapters.common import Adapter
|
|
19
|
-
from hud.adapters.common.types import ClickAction, Point, TypeAction
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@pytest.fixture
|
|
23
|
-
def adapter():
|
|
24
|
-
"""Fixture providing a clean adapter instance."""
|
|
25
|
-
return Adapter()
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@pytest.fixture
|
|
29
|
-
def test_image():
|
|
30
|
-
"""Fixture providing test image in various formats."""
|
|
31
|
-
img = Image.new("RGB", (100, 80), color="red")
|
|
32
|
-
img_bytes = io.BytesIO()
|
|
33
|
-
img.save(img_bytes, format="PNG")
|
|
34
|
-
img_base64 = base64.b64encode(img_bytes.getvalue()).decode("utf-8")
|
|
35
|
-
|
|
36
|
-
result = {
|
|
37
|
-
"pil": img,
|
|
38
|
-
"bytes": img_bytes.getvalue(),
|
|
39
|
-
"base64": img_base64,
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if HAS_NUMPY:
|
|
43
|
-
img_array = np.array(img) # type: ignore
|
|
44
|
-
result["array"] = img_array
|
|
45
|
-
|
|
46
|
-
return result
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def test_init(adapter):
|
|
50
|
-
"""Test adapter initialization."""
|
|
51
|
-
assert adapter.agent_width == 1920
|
|
52
|
-
assert adapter.agent_height == 1080
|
|
53
|
-
assert adapter.env_width == 1920
|
|
54
|
-
assert adapter.env_height == 1080
|
|
55
|
-
assert adapter.memory == []
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def test_preprocess(adapter):
|
|
59
|
-
"""Test preprocess method (default implementation)."""
|
|
60
|
-
action = {"type": "click", "point": {"x": 100, "y": 100}}
|
|
61
|
-
result = adapter.preprocess(action)
|
|
62
|
-
assert result == action # Default implementation returns unchanged
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def test_convert_valid(adapter):
|
|
66
|
-
"""Test convert method with valid action."""
|
|
67
|
-
action = ClickAction(point=Point(x=100, y=100))
|
|
68
|
-
result = adapter.convert(action)
|
|
69
|
-
# Fix: Instead of checking against CLA, check it's the same type as the input
|
|
70
|
-
assert isinstance(result, ClickAction)
|
|
71
|
-
assert result == action
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def test_convert_invalid(adapter):
|
|
75
|
-
"""Test convert method with invalid action."""
|
|
76
|
-
with pytest.raises(ValueError):
|
|
77
|
-
adapter.convert(None) # type: ignore
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def test_json_valid(adapter):
|
|
81
|
-
"""Test json method with valid action."""
|
|
82
|
-
action = ClickAction(point=Point(x=100, y=100))
|
|
83
|
-
result = adapter.json(action)
|
|
84
|
-
assert isinstance(result, dict)
|
|
85
|
-
assert result["type"] == "click"
|
|
86
|
-
assert result["point"]["x"] == 100
|
|
87
|
-
assert result["point"]["y"] == 100
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def test_json_invalid(adapter):
|
|
91
|
-
"""Test json method with invalid action."""
|
|
92
|
-
with pytest.raises(ValueError):
|
|
93
|
-
adapter.json(None) # type: ignore
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def test_rescale_pil_image(adapter, test_image):
|
|
97
|
-
"""Test rescaling PIL Image."""
|
|
98
|
-
result = adapter.rescale(test_image["pil"])
|
|
99
|
-
|
|
100
|
-
# Verify result is base64 string
|
|
101
|
-
assert isinstance(result, str)
|
|
102
|
-
|
|
103
|
-
# Verify environment dimensions were updated
|
|
104
|
-
assert adapter.env_width == 100
|
|
105
|
-
assert adapter.env_height == 80
|
|
106
|
-
|
|
107
|
-
# Decode and verify image dimensions
|
|
108
|
-
img_bytes = base64.b64decode(result)
|
|
109
|
-
img = Image.open(io.BytesIO(img_bytes))
|
|
110
|
-
assert img.size == (adapter.agent_width, adapter.agent_height)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
@pytest.mark.skipif(not HAS_NUMPY, reason="numpy not available")
|
|
114
|
-
def test_rescale_numpy_array(adapter, test_image):
|
|
115
|
-
"""Test rescaling numpy array."""
|
|
116
|
-
result = adapter.rescale(test_image["array"])
|
|
117
|
-
|
|
118
|
-
# Verify result is base64 string
|
|
119
|
-
assert isinstance(result, str)
|
|
120
|
-
|
|
121
|
-
# Verify environment dimensions were updated
|
|
122
|
-
assert adapter.env_width == 100
|
|
123
|
-
assert adapter.env_height == 80
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def test_rescale_base64(adapter, test_image):
|
|
127
|
-
"""Test rescaling base64 string."""
|
|
128
|
-
result = adapter.rescale(test_image["base64"])
|
|
129
|
-
|
|
130
|
-
# Verify result is base64 string
|
|
131
|
-
assert isinstance(result, str)
|
|
132
|
-
|
|
133
|
-
# Verify environment dimensions were updated
|
|
134
|
-
assert adapter.env_width == 100
|
|
135
|
-
assert adapter.env_height == 80
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def test_rescale_base64_with_header(adapter, test_image):
|
|
139
|
-
"""Test rescaling base64 string with header."""
|
|
140
|
-
base64_with_header = f"data:image/png;base64,{test_image['base64']}"
|
|
141
|
-
result = adapter.rescale(base64_with_header)
|
|
142
|
-
|
|
143
|
-
# Verify result is base64 string
|
|
144
|
-
assert isinstance(result, str)
|
|
145
|
-
|
|
146
|
-
# Verify environment dimensions were updated
|
|
147
|
-
assert adapter.env_width == 100
|
|
148
|
-
assert adapter.env_height == 80
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def test_rescale_invalid_type(adapter):
|
|
152
|
-
"""Test rescaling with invalid type."""
|
|
153
|
-
with pytest.raises(ValueError):
|
|
154
|
-
adapter.rescale(123) # type: ignore
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def test_rescale_none(adapter):
|
|
158
|
-
"""Test rescaling with None."""
|
|
159
|
-
result = adapter.rescale(None)
|
|
160
|
-
assert result is None
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def test_postprocess_action_click(adapter):
|
|
164
|
-
"""Test postprocess_action with click action."""
|
|
165
|
-
# Set different agent and env dimensions
|
|
166
|
-
adapter.agent_width = 1000
|
|
167
|
-
adapter.agent_height = 800
|
|
168
|
-
adapter.env_width = 2000
|
|
169
|
-
adapter.env_height = 1600
|
|
170
|
-
|
|
171
|
-
action = {"type": "click", "point": {"x": 500, "y": 400}}
|
|
172
|
-
result = adapter.postprocess_action(action)
|
|
173
|
-
|
|
174
|
-
# Coordinates should be doubled
|
|
175
|
-
assert result["point"]["x"] == 1000
|
|
176
|
-
assert result["point"]["y"] == 800
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def test_postprocess_action_drag(adapter):
|
|
180
|
-
"""Test postprocess_action with drag action."""
|
|
181
|
-
# Set different agent and env dimensions
|
|
182
|
-
adapter.agent_width = 1000
|
|
183
|
-
adapter.agent_height = 800
|
|
184
|
-
adapter.env_width = 2000
|
|
185
|
-
adapter.env_height = 1600
|
|
186
|
-
|
|
187
|
-
action = {"type": "drag", "path": [{"x": 100, "y": 200}, {"x": 300, "y": 400}]}
|
|
188
|
-
result = adapter.postprocess_action(action)
|
|
189
|
-
|
|
190
|
-
# Coordinates should be doubled
|
|
191
|
-
assert result["path"][0]["x"] == 200
|
|
192
|
-
assert result["path"][0]["y"] == 400
|
|
193
|
-
assert result["path"][1]["x"] == 600
|
|
194
|
-
assert result["path"][1]["y"] == 800
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def test_postprocess_action_scroll(adapter):
|
|
198
|
-
"""Test postprocess_action with scroll action."""
|
|
199
|
-
# Set different agent and env dimensions
|
|
200
|
-
adapter.agent_width = 1000
|
|
201
|
-
adapter.agent_height = 800
|
|
202
|
-
adapter.env_width = 2000
|
|
203
|
-
adapter.env_height = 1600
|
|
204
|
-
|
|
205
|
-
action = {"type": "scroll", "point": {"x": 500, "y": 400}, "scroll": {"x": 0, "y": 10}}
|
|
206
|
-
result = adapter.postprocess_action(action)
|
|
207
|
-
|
|
208
|
-
# Point coordinates should be doubled
|
|
209
|
-
assert result["point"]["x"] == 1000
|
|
210
|
-
assert result["point"]["y"] == 800
|
|
211
|
-
# Scroll amount should be scaled
|
|
212
|
-
assert result["scroll"]["x"] == 0
|
|
213
|
-
assert result["scroll"]["y"] == 20
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
def test_postprocess_action_empty(adapter):
|
|
217
|
-
"""Test postprocess_action with empty action."""
|
|
218
|
-
result = adapter.postprocess_action({})
|
|
219
|
-
assert result == {}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
def test_adapt(adapter):
|
|
223
|
-
"""Test adapt method."""
|
|
224
|
-
# Mock the needed methods
|
|
225
|
-
with (
|
|
226
|
-
patch.object(adapter, "preprocess", return_value={"preprocessed": True}),
|
|
227
|
-
patch.object(adapter, "convert", return_value=TypeAction(text="test")),
|
|
228
|
-
patch.object(adapter, "json", return_value={"type": "type", "text": "test"}),
|
|
229
|
-
patch.object(adapter, "postprocess_action", return_value={"type": "type", "text": "test"}),
|
|
230
|
-
patch("hud.adapters.common.adapter.TypeAdapter") as mock_adapter,
|
|
231
|
-
):
|
|
232
|
-
mock_validator = MagicMock()
|
|
233
|
-
mock_adapter.return_value = mock_validator
|
|
234
|
-
mock_validator.validate_python.return_value = TypeAction(text="test")
|
|
235
|
-
|
|
236
|
-
adapter.adapt({"raw": "action"})
|
|
237
|
-
|
|
238
|
-
# Verify the method chain was called correctly
|
|
239
|
-
adapter.preprocess.assert_called_once_with({"raw": "action"})
|
|
240
|
-
adapter.convert.assert_called_once_with({"preprocessed": True})
|
|
241
|
-
adapter.json.assert_called_once_with(TypeAction(text="test"))
|
|
242
|
-
adapter.postprocess_action.assert_called_once_with({"type": "type", "text": "test"})
|
|
243
|
-
|
|
244
|
-
# Verify the memory was updated
|
|
245
|
-
assert len(adapter.memory) == 1
|
|
246
|
-
assert adapter.memory[0] == TypeAction(text="test")
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
def test_adapt_list(adapter):
|
|
250
|
-
"""Test adapt_list method."""
|
|
251
|
-
# Fix: Use side_effect to return different values for each call to adapt
|
|
252
|
-
click_action = ClickAction(point=Point(x=100, y=100))
|
|
253
|
-
type_action = TypeAction(text="test")
|
|
254
|
-
|
|
255
|
-
mock_adapt = MagicMock(side_effect=[click_action, type_action])
|
|
256
|
-
with patch.object(adapter, "adapt", mock_adapt):
|
|
257
|
-
actions = [{"type": "click"}, {"type": "type"}]
|
|
258
|
-
result = adapter.adapt_list(actions)
|
|
259
|
-
|
|
260
|
-
assert adapter.adapt.call_count == 2
|
|
261
|
-
assert len(result) == 2
|
|
262
|
-
assert result[0] == click_action
|
|
263
|
-
assert result[1] == type_action
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def test_adapt_list_invalid(adapter):
|
|
267
|
-
"""Test adapt_list with invalid input."""
|
|
268
|
-
with pytest.raises(ValueError):
|
|
269
|
-
adapter.adapt_list("not a list") # type: ignore
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
def test_integration(adapter):
|
|
273
|
-
"""Integration test for the full adapter pipeline."""
|
|
274
|
-
adapter.agent_width = 1000
|
|
275
|
-
adapter.agent_height = 800
|
|
276
|
-
adapter.env_width = 2000
|
|
277
|
-
adapter.env_height = 1600
|
|
278
|
-
|
|
279
|
-
# Create a click action
|
|
280
|
-
action = ClickAction(point=Point(x=500, y=400))
|
|
281
|
-
|
|
282
|
-
result = adapter.adapt(action)
|
|
283
|
-
|
|
284
|
-
assert isinstance(result, ClickAction)
|
|
285
|
-
assert result.point is not None
|
|
286
|
-
assert result.point.x == 1000 # Scaled from 500 to 1000
|
|
287
|
-
assert result.point.y == 800 # Scaled from 400 to 800
|
|
288
|
-
|
|
289
|
-
assert len(adapter.memory) == 1
|