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.

Files changed (192) hide show
  1. hud/__init__.py +22 -89
  2. hud/agents/__init__.py +17 -0
  3. hud/agents/art.py +101 -0
  4. hud/agents/base.py +599 -0
  5. hud/{mcp → agents}/claude.py +373 -321
  6. hud/{mcp → agents}/langchain.py +250 -250
  7. hud/agents/misc/__init__.py +7 -0
  8. hud/{agent → agents}/misc/response_agent.py +80 -80
  9. hud/{mcp → agents}/openai.py +352 -334
  10. hud/agents/openai_chat_generic.py +154 -0
  11. hud/{mcp → agents}/tests/__init__.py +1 -1
  12. hud/agents/tests/test_base.py +742 -0
  13. hud/agents/tests/test_claude.py +324 -0
  14. hud/{mcp → agents}/tests/test_client.py +363 -324
  15. hud/{mcp → agents}/tests/test_openai.py +237 -238
  16. hud/cli/__init__.py +617 -0
  17. hud/cli/__main__.py +8 -0
  18. hud/cli/analyze.py +371 -0
  19. hud/cli/analyze_metadata.py +230 -0
  20. hud/cli/build.py +427 -0
  21. hud/cli/clone.py +185 -0
  22. hud/cli/cursor.py +92 -0
  23. hud/cli/debug.py +392 -0
  24. hud/cli/docker_utils.py +83 -0
  25. hud/cli/init.py +281 -0
  26. hud/cli/interactive.py +353 -0
  27. hud/cli/mcp_server.py +756 -0
  28. hud/cli/pull.py +336 -0
  29. hud/cli/push.py +379 -0
  30. hud/cli/remote_runner.py +311 -0
  31. hud/cli/runner.py +160 -0
  32. hud/cli/tests/__init__.py +3 -0
  33. hud/cli/tests/test_analyze.py +284 -0
  34. hud/cli/tests/test_cli_init.py +265 -0
  35. hud/cli/tests/test_cli_main.py +27 -0
  36. hud/cli/tests/test_clone.py +142 -0
  37. hud/cli/tests/test_cursor.py +253 -0
  38. hud/cli/tests/test_debug.py +453 -0
  39. hud/cli/tests/test_mcp_server.py +139 -0
  40. hud/cli/tests/test_utils.py +388 -0
  41. hud/cli/utils.py +263 -0
  42. hud/clients/README.md +143 -0
  43. hud/clients/__init__.py +16 -0
  44. hud/clients/base.py +354 -0
  45. hud/clients/fastmcp.py +202 -0
  46. hud/clients/mcp_use.py +278 -0
  47. hud/clients/tests/__init__.py +1 -0
  48. hud/clients/tests/test_client_integration.py +111 -0
  49. hud/clients/tests/test_fastmcp.py +342 -0
  50. hud/clients/tests/test_protocol.py +188 -0
  51. hud/clients/utils/__init__.py +1 -0
  52. hud/clients/utils/retry_transport.py +160 -0
  53. hud/datasets.py +322 -192
  54. hud/misc/__init__.py +1 -0
  55. hud/{agent → misc}/claude_plays_pokemon.py +292 -283
  56. hud/otel/__init__.py +35 -0
  57. hud/otel/collector.py +142 -0
  58. hud/otel/config.py +164 -0
  59. hud/otel/context.py +536 -0
  60. hud/otel/exporters.py +366 -0
  61. hud/otel/instrumentation.py +97 -0
  62. hud/otel/processors.py +118 -0
  63. hud/otel/tests/__init__.py +1 -0
  64. hud/otel/tests/test_processors.py +197 -0
  65. hud/server/__init__.py +5 -5
  66. hud/server/context.py +114 -0
  67. hud/server/helper/__init__.py +5 -0
  68. hud/server/low_level.py +132 -0
  69. hud/server/server.py +166 -0
  70. hud/server/tests/__init__.py +3 -0
  71. hud/settings.py +73 -79
  72. hud/shared/__init__.py +5 -0
  73. hud/{exceptions.py → shared/exceptions.py} +180 -180
  74. hud/{server → shared}/requests.py +264 -264
  75. hud/shared/tests/test_exceptions.py +157 -0
  76. hud/{server → shared}/tests/test_requests.py +275 -275
  77. hud/telemetry/__init__.py +25 -30
  78. hud/telemetry/instrument.py +379 -0
  79. hud/telemetry/job.py +309 -141
  80. hud/telemetry/replay.py +74 -0
  81. hud/telemetry/trace.py +83 -0
  82. hud/tools/__init__.py +33 -34
  83. hud/tools/base.py +365 -65
  84. hud/tools/bash.py +161 -137
  85. hud/tools/computer/__init__.py +15 -13
  86. hud/tools/computer/anthropic.py +437 -420
  87. hud/tools/computer/hud.py +376 -334
  88. hud/tools/computer/openai.py +295 -292
  89. hud/tools/computer/settings.py +82 -0
  90. hud/tools/edit.py +314 -290
  91. hud/tools/executors/__init__.py +30 -30
  92. hud/tools/executors/base.py +539 -532
  93. hud/tools/executors/pyautogui.py +621 -619
  94. hud/tools/executors/tests/__init__.py +1 -1
  95. hud/tools/executors/tests/test_base_executor.py +338 -338
  96. hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
  97. hud/tools/executors/xdo.py +511 -503
  98. hud/tools/{playwright_tool.py → playwright.py} +412 -379
  99. hud/tools/tests/__init__.py +3 -3
  100. hud/tools/tests/test_base.py +282 -0
  101. hud/tools/tests/test_bash.py +158 -152
  102. hud/tools/tests/test_bash_extended.py +197 -0
  103. hud/tools/tests/test_computer.py +425 -52
  104. hud/tools/tests/test_computer_actions.py +34 -34
  105. hud/tools/tests/test_edit.py +259 -240
  106. hud/tools/tests/test_init.py +27 -27
  107. hud/tools/tests/test_playwright_tool.py +183 -183
  108. hud/tools/tests/test_tools.py +145 -157
  109. hud/tools/tests/test_utils.py +156 -156
  110. hud/tools/types.py +72 -0
  111. hud/tools/utils.py +50 -50
  112. hud/types.py +136 -89
  113. hud/utils/__init__.py +10 -16
  114. hud/utils/async_utils.py +65 -0
  115. hud/utils/design.py +168 -0
  116. hud/utils/mcp.py +55 -0
  117. hud/utils/progress.py +149 -149
  118. hud/utils/telemetry.py +66 -66
  119. hud/utils/tests/test_async_utils.py +173 -0
  120. hud/utils/tests/test_init.py +17 -21
  121. hud/utils/tests/test_progress.py +261 -225
  122. hud/utils/tests/test_telemetry.py +82 -37
  123. hud/utils/tests/test_version.py +8 -8
  124. hud/version.py +7 -7
  125. hud_python-0.4.0.dist-info/METADATA +474 -0
  126. hud_python-0.4.0.dist-info/RECORD +132 -0
  127. hud_python-0.4.0.dist-info/entry_points.txt +3 -0
  128. {hud_python-0.3.5.dist-info → hud_python-0.4.0.dist-info}/licenses/LICENSE +21 -21
  129. hud/adapters/__init__.py +0 -8
  130. hud/adapters/claude/__init__.py +0 -5
  131. hud/adapters/claude/adapter.py +0 -180
  132. hud/adapters/claude/tests/__init__.py +0 -1
  133. hud/adapters/claude/tests/test_adapter.py +0 -519
  134. hud/adapters/common/__init__.py +0 -6
  135. hud/adapters/common/adapter.py +0 -178
  136. hud/adapters/common/tests/test_adapter.py +0 -289
  137. hud/adapters/common/types.py +0 -446
  138. hud/adapters/operator/__init__.py +0 -5
  139. hud/adapters/operator/adapter.py +0 -108
  140. hud/adapters/operator/tests/__init__.py +0 -1
  141. hud/adapters/operator/tests/test_adapter.py +0 -370
  142. hud/agent/__init__.py +0 -19
  143. hud/agent/base.py +0 -126
  144. hud/agent/claude.py +0 -271
  145. hud/agent/langchain.py +0 -215
  146. hud/agent/misc/__init__.py +0 -3
  147. hud/agent/operator.py +0 -268
  148. hud/agent/tests/__init__.py +0 -1
  149. hud/agent/tests/test_base.py +0 -202
  150. hud/env/__init__.py +0 -11
  151. hud/env/client.py +0 -35
  152. hud/env/docker_client.py +0 -349
  153. hud/env/environment.py +0 -446
  154. hud/env/local_docker_client.py +0 -358
  155. hud/env/remote_client.py +0 -212
  156. hud/env/remote_docker_client.py +0 -292
  157. hud/gym.py +0 -130
  158. hud/job.py +0 -773
  159. hud/mcp/__init__.py +0 -17
  160. hud/mcp/base.py +0 -631
  161. hud/mcp/client.py +0 -312
  162. hud/mcp/tests/test_base.py +0 -512
  163. hud/mcp/tests/test_claude.py +0 -294
  164. hud/task.py +0 -149
  165. hud/taskset.py +0 -237
  166. hud/telemetry/_trace.py +0 -347
  167. hud/telemetry/context.py +0 -230
  168. hud/telemetry/exporter.py +0 -575
  169. hud/telemetry/instrumentation/__init__.py +0 -3
  170. hud/telemetry/instrumentation/mcp.py +0 -259
  171. hud/telemetry/instrumentation/registry.py +0 -59
  172. hud/telemetry/mcp_models.py +0 -270
  173. hud/telemetry/tests/__init__.py +0 -1
  174. hud/telemetry/tests/test_context.py +0 -210
  175. hud/telemetry/tests/test_trace.py +0 -312
  176. hud/tools/helper/README.md +0 -56
  177. hud/tools/helper/__init__.py +0 -9
  178. hud/tools/helper/mcp_server.py +0 -78
  179. hud/tools/helper/server_initialization.py +0 -115
  180. hud/tools/helper/utils.py +0 -58
  181. hud/trajectory.py +0 -94
  182. hud/utils/agent.py +0 -37
  183. hud/utils/common.py +0 -256
  184. hud/utils/config.py +0 -120
  185. hud/utils/deprecation.py +0 -115
  186. hud/utils/misc.py +0 -53
  187. hud/utils/tests/test_common.py +0 -277
  188. hud/utils/tests/test_config.py +0 -129
  189. hud_python-0.3.5.dist-info/METADATA +0 -284
  190. hud_python-0.3.5.dist-info/RECORD +0 -120
  191. /hud/{adapters/common → shared}/tests/__init__.py +0 -0
  192. {hud_python-0.3.5.dist-info → hud_python-0.4.0.dist-info}/WHEEL +0 -0
@@ -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