hud-python 0.3.5__py3-none-any.whl → 0.4.1__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 +15 -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 +370 -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 +379 -0
- hud/clients/fastmcp.py +222 -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.1.dist-info/METADATA +476 -0
- hud_python-0.4.1.dist-info/RECORD +132 -0
- hud_python-0.4.1.dist-info/entry_points.txt +3 -0
- {hud_python-0.3.5.dist-info → hud_python-0.4.1.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.1.dist-info}/WHEEL +0 -0
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
|
|
5
|
-
from hud.adapters.common.types import (
|
|
6
|
-
ClickAction,
|
|
7
|
-
DragAction,
|
|
8
|
-
MoveAction,
|
|
9
|
-
PressAction,
|
|
10
|
-
ResponseAction,
|
|
11
|
-
ScreenshotFetch,
|
|
12
|
-
ScrollAction,
|
|
13
|
-
TypeAction,
|
|
14
|
-
WaitAction,
|
|
15
|
-
)
|
|
16
|
-
from hud.adapters.operator import OperatorAdapter
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class TestOperatorAdapter:
|
|
20
|
-
"""Test the OperatorAdapter class."""
|
|
21
|
-
|
|
22
|
-
@pytest.fixture
|
|
23
|
-
def adapter(self):
|
|
24
|
-
"""Fixture providing a clean adapter instance."""
|
|
25
|
-
return OperatorAdapter()
|
|
26
|
-
|
|
27
|
-
def test_init(self, adapter):
|
|
28
|
-
"""Test adapter initialization."""
|
|
29
|
-
assert adapter.agent_width == 1024
|
|
30
|
-
assert adapter.agent_height == 768
|
|
31
|
-
assert adapter.env_width == 1920 # Inherited from parent
|
|
32
|
-
assert adapter.env_height == 1080 # Inherited from parent
|
|
33
|
-
|
|
34
|
-
def test_key_map_constants(self, adapter):
|
|
35
|
-
"""Test KEY_MAP constants."""
|
|
36
|
-
assert adapter.KEY_MAP["return"] == "enter"
|
|
37
|
-
assert adapter.KEY_MAP["arrowup"] == "up"
|
|
38
|
-
assert adapter.KEY_MAP["arrowdown"] == "down"
|
|
39
|
-
assert adapter.KEY_MAP["arrowleft"] == "left"
|
|
40
|
-
assert adapter.KEY_MAP["arrowright"] == "right"
|
|
41
|
-
|
|
42
|
-
def test_button_map_constants(self, adapter):
|
|
43
|
-
"""Test BUTTON_MAP constants."""
|
|
44
|
-
assert adapter.BUTTON_MAP["wheel"] == "middle"
|
|
45
|
-
|
|
46
|
-
def test_map_key_mapped(self, adapter):
|
|
47
|
-
"""Test _map_key with mapped keys."""
|
|
48
|
-
assert adapter._map_key("return") == "enter"
|
|
49
|
-
assert adapter._map_key("RETURN") == "enter" # Test case insensitive
|
|
50
|
-
assert adapter._map_key("arrowup") == "up"
|
|
51
|
-
assert adapter._map_key("ArrowDown") == "down"
|
|
52
|
-
|
|
53
|
-
def test_map_key_unmapped(self, adapter):
|
|
54
|
-
"""Test _map_key with unmapped keys."""
|
|
55
|
-
assert adapter._map_key("space") == "space"
|
|
56
|
-
assert adapter._map_key("CTRL") == "ctrl"
|
|
57
|
-
assert adapter._map_key("Unknown") == "unknown"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class TestOperatorAdapterConvert:
|
|
61
|
-
"""Test the convert method of OperatorAdapter."""
|
|
62
|
-
|
|
63
|
-
@pytest.fixture
|
|
64
|
-
def adapter(self):
|
|
65
|
-
"""Fixture providing a clean adapter instance."""
|
|
66
|
-
return OperatorAdapter()
|
|
67
|
-
|
|
68
|
-
def test_convert_click_action(self, adapter):
|
|
69
|
-
"""Test converting click action."""
|
|
70
|
-
data = {"type": "click", "x": 100, "y": 200, "button": "left"}
|
|
71
|
-
result = adapter.convert(data)
|
|
72
|
-
|
|
73
|
-
assert isinstance(result, ClickAction)
|
|
74
|
-
assert result.point is not None
|
|
75
|
-
assert result.point.x == 100
|
|
76
|
-
assert result.point.y == 200
|
|
77
|
-
assert result.button == "left"
|
|
78
|
-
|
|
79
|
-
def test_convert_click_action_default_values(self, adapter):
|
|
80
|
-
"""Test converting click action with default values."""
|
|
81
|
-
data = {"type": "click"}
|
|
82
|
-
result = adapter.convert(data)
|
|
83
|
-
|
|
84
|
-
assert isinstance(result, ClickAction)
|
|
85
|
-
assert result.point is not None
|
|
86
|
-
assert result.point.x == 0
|
|
87
|
-
assert result.point.y == 0
|
|
88
|
-
assert result.button == "left"
|
|
89
|
-
|
|
90
|
-
def test_convert_click_action_mapped_button(self, adapter):
|
|
91
|
-
"""Test converting click action with mapped button."""
|
|
92
|
-
data = {"type": "click", "x": 100, "y": 200, "button": "wheel"}
|
|
93
|
-
result = adapter.convert(data)
|
|
94
|
-
|
|
95
|
-
assert isinstance(result, ClickAction)
|
|
96
|
-
assert result.button == "middle"
|
|
97
|
-
|
|
98
|
-
def test_convert_double_click_action(self, adapter):
|
|
99
|
-
"""Test converting double click action."""
|
|
100
|
-
data = {"type": "double_click", "x": 150, "y": 250}
|
|
101
|
-
result = adapter.convert(data)
|
|
102
|
-
|
|
103
|
-
assert isinstance(result, ClickAction)
|
|
104
|
-
assert result.point is not None
|
|
105
|
-
assert result.point.x == 150
|
|
106
|
-
assert result.point.y == 250
|
|
107
|
-
assert result.button == "left"
|
|
108
|
-
assert result.pattern == [100] # Double click pattern
|
|
109
|
-
|
|
110
|
-
def test_convert_scroll_action(self, adapter):
|
|
111
|
-
"""Test converting scroll action."""
|
|
112
|
-
data = {"type": "scroll", "x": 300, "y": 400, "scroll_x": 10, "scroll_y": -20}
|
|
113
|
-
result = adapter.convert(data)
|
|
114
|
-
|
|
115
|
-
assert isinstance(result, ScrollAction)
|
|
116
|
-
assert result.point is not None
|
|
117
|
-
assert result.scroll is not None
|
|
118
|
-
assert result.point.x == 300
|
|
119
|
-
assert result.point.y == 400
|
|
120
|
-
assert result.scroll.x == 10
|
|
121
|
-
assert result.scroll.y == -20
|
|
122
|
-
|
|
123
|
-
def test_convert_scroll_action_default_values(self, adapter):
|
|
124
|
-
"""Test converting scroll action with default values."""
|
|
125
|
-
data = {"type": "scroll"}
|
|
126
|
-
result = adapter.convert(data)
|
|
127
|
-
|
|
128
|
-
assert isinstance(result, ScrollAction)
|
|
129
|
-
assert result.point is not None
|
|
130
|
-
assert result.scroll is not None
|
|
131
|
-
assert result.point.x == 0
|
|
132
|
-
assert result.point.y == 0
|
|
133
|
-
assert result.scroll.x == 0
|
|
134
|
-
assert result.scroll.y == 0
|
|
135
|
-
|
|
136
|
-
def test_convert_type_action(self, adapter):
|
|
137
|
-
"""Test converting type action."""
|
|
138
|
-
data = {"type": "type", "text": "Hello, World!"}
|
|
139
|
-
result = adapter.convert(data)
|
|
140
|
-
|
|
141
|
-
assert isinstance(result, TypeAction)
|
|
142
|
-
assert result.text == "Hello, World!"
|
|
143
|
-
assert result.enter_after is False
|
|
144
|
-
|
|
145
|
-
def test_convert_type_action_default_text(self, adapter):
|
|
146
|
-
"""Test converting type action with default text."""
|
|
147
|
-
data = {"type": "type"}
|
|
148
|
-
result = adapter.convert(data)
|
|
149
|
-
|
|
150
|
-
assert isinstance(result, TypeAction)
|
|
151
|
-
assert result.text == ""
|
|
152
|
-
assert result.enter_after is False
|
|
153
|
-
|
|
154
|
-
def test_convert_wait_action(self, adapter):
|
|
155
|
-
"""Test converting wait action."""
|
|
156
|
-
data = {"type": "wait", "ms": 2000}
|
|
157
|
-
result = adapter.convert(data)
|
|
158
|
-
|
|
159
|
-
assert isinstance(result, WaitAction)
|
|
160
|
-
assert result.time == 2000
|
|
161
|
-
|
|
162
|
-
def test_convert_wait_action_default_time(self, adapter):
|
|
163
|
-
"""Test converting wait action with default time."""
|
|
164
|
-
data = {"type": "wait"}
|
|
165
|
-
result = adapter.convert(data)
|
|
166
|
-
|
|
167
|
-
assert isinstance(result, WaitAction)
|
|
168
|
-
assert result.time == 1000
|
|
169
|
-
|
|
170
|
-
def test_convert_move_action(self, adapter):
|
|
171
|
-
"""Test converting move action."""
|
|
172
|
-
data = {"type": "move", "x": 500, "y": 600}
|
|
173
|
-
result = adapter.convert(data)
|
|
174
|
-
|
|
175
|
-
assert isinstance(result, MoveAction)
|
|
176
|
-
assert result.point is not None
|
|
177
|
-
assert result.point.x == 500
|
|
178
|
-
assert result.point.y == 600
|
|
179
|
-
|
|
180
|
-
def test_convert_move_action_default_values(self, adapter):
|
|
181
|
-
"""Test converting move action with default values."""
|
|
182
|
-
data = {"type": "move"}
|
|
183
|
-
result = adapter.convert(data)
|
|
184
|
-
|
|
185
|
-
assert isinstance(result, MoveAction)
|
|
186
|
-
assert result.point is not None
|
|
187
|
-
assert result.point.x == 0
|
|
188
|
-
assert result.point.y == 0
|
|
189
|
-
|
|
190
|
-
def test_convert_keypress_action(self, adapter):
|
|
191
|
-
"""Test converting keypress action."""
|
|
192
|
-
data = {"type": "keypress", "keys": ["ctrl", "c"]}
|
|
193
|
-
result = adapter.convert(data)
|
|
194
|
-
|
|
195
|
-
assert isinstance(result, PressAction)
|
|
196
|
-
assert result.keys == ["ctrl", "c"]
|
|
197
|
-
|
|
198
|
-
def test_convert_keypress_action_mapped_keys(self, adapter):
|
|
199
|
-
"""Test converting keypress action with mapped keys."""
|
|
200
|
-
data = {"type": "keypress", "keys": ["return", "arrowup"]}
|
|
201
|
-
result = adapter.convert(data)
|
|
202
|
-
|
|
203
|
-
assert isinstance(result, PressAction)
|
|
204
|
-
assert result.keys == ["enter", "up"]
|
|
205
|
-
|
|
206
|
-
def test_convert_keypress_action_default_keys(self, adapter):
|
|
207
|
-
"""Test converting keypress action with default keys."""
|
|
208
|
-
data = {"type": "keypress"}
|
|
209
|
-
result = adapter.convert(data)
|
|
210
|
-
|
|
211
|
-
assert isinstance(result, PressAction)
|
|
212
|
-
assert result.keys == []
|
|
213
|
-
|
|
214
|
-
def test_convert_drag_action(self, adapter):
|
|
215
|
-
"""Test converting drag action."""
|
|
216
|
-
data = {
|
|
217
|
-
"type": "drag",
|
|
218
|
-
"path": [{"x": 100, "y": 200}, {"x": 150, "y": 250}, {"x": 200, "y": 300}],
|
|
219
|
-
}
|
|
220
|
-
result = adapter.convert(data)
|
|
221
|
-
|
|
222
|
-
assert isinstance(result, DragAction)
|
|
223
|
-
assert len(result.path) == 3
|
|
224
|
-
assert result.path[0].x == 100
|
|
225
|
-
assert result.path[0].y == 200
|
|
226
|
-
assert result.path[1].x == 150
|
|
227
|
-
assert result.path[1].y == 250
|
|
228
|
-
assert result.path[2].x == 200
|
|
229
|
-
assert result.path[2].y == 300
|
|
230
|
-
|
|
231
|
-
def test_convert_drag_action_default_path(self, adapter):
|
|
232
|
-
"""Test converting drag action with default path."""
|
|
233
|
-
data = {"type": "drag"}
|
|
234
|
-
result = adapter.convert(data)
|
|
235
|
-
|
|
236
|
-
assert isinstance(result, DragAction)
|
|
237
|
-
assert result.path == []
|
|
238
|
-
|
|
239
|
-
def test_convert_drag_action_path_with_missing_coords(self, adapter):
|
|
240
|
-
"""Test converting drag action with missing coordinates."""
|
|
241
|
-
data = {
|
|
242
|
-
"type": "drag",
|
|
243
|
-
"path": [
|
|
244
|
-
{"x": 100}, # Missing y
|
|
245
|
-
{"y": 200}, # Missing x
|
|
246
|
-
{}, # Missing both
|
|
247
|
-
],
|
|
248
|
-
}
|
|
249
|
-
result = adapter.convert(data)
|
|
250
|
-
|
|
251
|
-
assert isinstance(result, DragAction)
|
|
252
|
-
assert len(result.path) == 3
|
|
253
|
-
assert result.path[0].x == 100
|
|
254
|
-
assert result.path[0].y == 0 # Default value
|
|
255
|
-
assert result.path[1].x == 0 # Default value
|
|
256
|
-
assert result.path[1].y == 200
|
|
257
|
-
assert result.path[2].x == 0 # Default value
|
|
258
|
-
assert result.path[2].y == 0 # Default value
|
|
259
|
-
|
|
260
|
-
def test_convert_screenshot_action(self, adapter):
|
|
261
|
-
"""Test converting screenshot action."""
|
|
262
|
-
data = {"type": "screenshot"}
|
|
263
|
-
result = adapter.convert(data)
|
|
264
|
-
|
|
265
|
-
assert isinstance(result, ScreenshotFetch)
|
|
266
|
-
|
|
267
|
-
def test_convert_response_action(self, adapter):
|
|
268
|
-
"""Test converting response action."""
|
|
269
|
-
data = {"type": "response", "text": "Task completed successfully"}
|
|
270
|
-
result = adapter.convert(data)
|
|
271
|
-
|
|
272
|
-
assert isinstance(result, ResponseAction)
|
|
273
|
-
assert result.text == "Task completed successfully"
|
|
274
|
-
|
|
275
|
-
def test_convert_response_action_default_text(self, adapter):
|
|
276
|
-
"""Test converting response action with default text."""
|
|
277
|
-
data = {"type": "response"}
|
|
278
|
-
result = adapter.convert(data)
|
|
279
|
-
|
|
280
|
-
assert isinstance(result, ResponseAction)
|
|
281
|
-
assert result.text == ""
|
|
282
|
-
|
|
283
|
-
def test_convert_unsupported_action_type(self, adapter):
|
|
284
|
-
"""Test converting unsupported action type."""
|
|
285
|
-
data = {"type": "unsupported_action"}
|
|
286
|
-
|
|
287
|
-
with pytest.raises(ValueError) as exc_info:
|
|
288
|
-
adapter.convert(data)
|
|
289
|
-
|
|
290
|
-
assert "Unsupported action type: unsupported_action" in str(exc_info.value)
|
|
291
|
-
|
|
292
|
-
def test_convert_invalid_data_structure(self, adapter):
|
|
293
|
-
"""Test converting invalid data structure."""
|
|
294
|
-
# Test with non-dict data
|
|
295
|
-
with pytest.raises(ValueError) as exc_info:
|
|
296
|
-
adapter.convert("invalid_data")
|
|
297
|
-
|
|
298
|
-
assert "Invalid action" in str(exc_info.value)
|
|
299
|
-
|
|
300
|
-
def test_convert_missing_type_field(self, adapter):
|
|
301
|
-
"""Test converting data without type field."""
|
|
302
|
-
data = {"x": 100, "y": 200} # Missing type
|
|
303
|
-
|
|
304
|
-
with pytest.raises(ValueError) as exc_info:
|
|
305
|
-
adapter.convert(data)
|
|
306
|
-
|
|
307
|
-
assert "Unsupported action type: None" in str(exc_info.value)
|
|
308
|
-
|
|
309
|
-
def test_convert_none_data(self, adapter):
|
|
310
|
-
"""Test converting None data."""
|
|
311
|
-
with pytest.raises(ValueError) as exc_info:
|
|
312
|
-
adapter.convert(None)
|
|
313
|
-
|
|
314
|
-
assert "Invalid action" in str(exc_info.value)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
class TestOperatorAdapterIntegration:
|
|
318
|
-
"""Integration tests for OperatorAdapter."""
|
|
319
|
-
|
|
320
|
-
@pytest.fixture
|
|
321
|
-
def adapter(self):
|
|
322
|
-
"""Fixture providing a clean adapter instance."""
|
|
323
|
-
return OperatorAdapter()
|
|
324
|
-
|
|
325
|
-
def test_full_click_pipeline(self, adapter):
|
|
326
|
-
"""Test full click action processing pipeline."""
|
|
327
|
-
# Set adapter dimensions to avoid scaling
|
|
328
|
-
adapter.agent_width = 1920
|
|
329
|
-
adapter.agent_height = 1080
|
|
330
|
-
adapter.env_width = 1920
|
|
331
|
-
adapter.env_height = 1080
|
|
332
|
-
|
|
333
|
-
# Test the full adapt method
|
|
334
|
-
raw_action = {"type": "click", "x": 100, "y": 200, "button": "right"}
|
|
335
|
-
|
|
336
|
-
result = adapter.adapt(raw_action)
|
|
337
|
-
|
|
338
|
-
assert isinstance(result, ClickAction)
|
|
339
|
-
assert result.point is not None
|
|
340
|
-
assert result.point.x == 100
|
|
341
|
-
assert result.point.y == 200
|
|
342
|
-
assert result.button == "right"
|
|
343
|
-
|
|
344
|
-
# Check that it was added to memory
|
|
345
|
-
assert len(adapter.memory) == 1
|
|
346
|
-
assert adapter.memory[0] == result
|
|
347
|
-
|
|
348
|
-
def test_multiple_actions_processing(self, adapter):
|
|
349
|
-
"""Test processing multiple actions."""
|
|
350
|
-
# Set adapter dimensions to avoid scaling
|
|
351
|
-
adapter.agent_width = 1920
|
|
352
|
-
adapter.agent_height = 1080
|
|
353
|
-
adapter.env_width = 1920
|
|
354
|
-
adapter.env_height = 1080
|
|
355
|
-
|
|
356
|
-
actions = [
|
|
357
|
-
{"type": "click", "x": 100, "y": 200},
|
|
358
|
-
{"type": "type", "text": "hello"},
|
|
359
|
-
{"type": "keypress", "keys": ["return"]},
|
|
360
|
-
]
|
|
361
|
-
|
|
362
|
-
results = adapter.adapt_list(actions)
|
|
363
|
-
|
|
364
|
-
assert len(results) == 3
|
|
365
|
-
assert isinstance(results[0], ClickAction)
|
|
366
|
-
assert isinstance(results[1], TypeAction)
|
|
367
|
-
assert isinstance(results[2], PressAction)
|
|
368
|
-
|
|
369
|
-
# Check memory
|
|
370
|
-
assert len(adapter.memory) == 3
|
hud/agent/__init__.py
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from .base import Agent
|
|
2
|
-
from .claude import ClaudeAgent
|
|
3
|
-
from .claude_plays_pokemon import ClaudePlaysPokemon
|
|
4
|
-
from .operator import OperatorAgent
|
|
5
|
-
from .langchain import LangchainAgent
|
|
6
|
-
from .misc import ResponseAgent
|
|
7
|
-
|
|
8
|
-
from hud.adapters import OperatorAdapter, ClaudeAdapter
|
|
9
|
-
|
|
10
|
-
__all__ = [
|
|
11
|
-
"Agent",
|
|
12
|
-
"ClaudeAgent",
|
|
13
|
-
"OperatorAgent",
|
|
14
|
-
"OperatorAdapter",
|
|
15
|
-
"ClaudeAdapter",
|
|
16
|
-
"LangchainAgent",
|
|
17
|
-
"ClaudePlaysPokemon",
|
|
18
|
-
"ResponseAgent",
|
|
19
|
-
]
|
hud/agent/base.py
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Any, Sequence, TypeVar, Generic
|
|
3
|
-
|
|
4
|
-
from hud.adapters import Adapter, CLA
|
|
5
|
-
from hud.types import Gym
|
|
6
|
-
from hud.utils.common import Observation
|
|
7
|
-
import logging
|
|
8
|
-
|
|
9
|
-
logger = logging.getLogger(__name__)
|
|
10
|
-
|
|
11
|
-
# Generic type for different client types (Anthropic, OpenAI, etc.)
|
|
12
|
-
ClientT = TypeVar("ClientT")
|
|
13
|
-
ActionT = TypeVar("ActionT")
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class Agent(Generic[ClientT, ActionT], ABC):
|
|
17
|
-
"""
|
|
18
|
-
Base class for all agents.
|
|
19
|
-
|
|
20
|
-
Implements a three-stage prediction process:
|
|
21
|
-
1. preprocess - Prepare observation data (e.g., rescale screenshot)
|
|
22
|
-
2. fetch_response - Make API calls to get model response
|
|
23
|
-
3. postprocess - Convert model actions to HUD format
|
|
24
|
-
|
|
25
|
-
Subclasses only need to implement the fetch_response method.
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
transfer_gyms: dict[Gym, Gym] = {}
|
|
29
|
-
|
|
30
|
-
def __init__(
|
|
31
|
-
self,
|
|
32
|
-
client: ClientT | None = None,
|
|
33
|
-
adapter: Adapter | None = None,
|
|
34
|
-
name: str | None = None,
|
|
35
|
-
):
|
|
36
|
-
"""
|
|
37
|
-
Initialize the agent.
|
|
38
|
-
|
|
39
|
-
Args:
|
|
40
|
-
client: The client to use for API calls
|
|
41
|
-
adapter: The adapter to use for preprocessing and postprocessing
|
|
42
|
-
"""
|
|
43
|
-
self.client = client
|
|
44
|
-
self.adapter = adapter
|
|
45
|
-
self.name = name
|
|
46
|
-
|
|
47
|
-
def preprocess(self, observation: Observation) -> Observation:
|
|
48
|
-
"""
|
|
49
|
-
Preprocess the observation before sending to the model.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
observation: The raw observation from the environment
|
|
53
|
-
|
|
54
|
-
Returns:
|
|
55
|
-
Observation: The processed observation ready for the model
|
|
56
|
-
"""
|
|
57
|
-
if not self.adapter or not observation.screenshot:
|
|
58
|
-
return observation
|
|
59
|
-
|
|
60
|
-
# Create a new observation with the rescaled screenshot
|
|
61
|
-
processed_obs = Observation(
|
|
62
|
-
text=observation.text, screenshot=self.adapter.rescale(observation.screenshot)
|
|
63
|
-
)
|
|
64
|
-
return processed_obs
|
|
65
|
-
|
|
66
|
-
@abstractmethod
|
|
67
|
-
async def fetch_response(self, observation: Observation) -> tuple[list[ActionT], bool]:
|
|
68
|
-
"""
|
|
69
|
-
Fetch a response from the model based on the observation.
|
|
70
|
-
|
|
71
|
-
Args:
|
|
72
|
-
observation: The preprocessed observation
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
tuple[list[ActionT], bool]: A tuple containing the list of raw actions,
|
|
76
|
-
boolean indicating if the agent believes it has
|
|
77
|
-
completed the task.
|
|
78
|
-
"""
|
|
79
|
-
pass
|
|
80
|
-
|
|
81
|
-
def postprocess(self, actions: list[ActionT]) -> list[CLA]:
|
|
82
|
-
"""
|
|
83
|
-
Convert model actions to HUD actions.
|
|
84
|
-
|
|
85
|
-
Args:
|
|
86
|
-
actions: The raw actions from the model
|
|
87
|
-
Returns:
|
|
88
|
-
Sequence[CLA]: The actions converted to HUD format
|
|
89
|
-
"""
|
|
90
|
-
if not self.adapter:
|
|
91
|
-
raise ValueError("Cannot postprocess actions without an adapter")
|
|
92
|
-
|
|
93
|
-
return self.adapter.adapt_list(actions)
|
|
94
|
-
|
|
95
|
-
async def predict(
|
|
96
|
-
self, observation: Observation, verbose: bool = False
|
|
97
|
-
) -> tuple[list[CLA] | list[ActionT], bool]:
|
|
98
|
-
"""
|
|
99
|
-
Predict the next action based on the observation.
|
|
100
|
-
|
|
101
|
-
Implements the full three-stage prediction process.
|
|
102
|
-
|
|
103
|
-
Args:
|
|
104
|
-
observation: The observation from the environment
|
|
105
|
-
|
|
106
|
-
Returns:
|
|
107
|
-
tuple[list[CLA] | list[ActionT], bool]: A tuple containing the list of actions and a boolean
|
|
108
|
-
indicating if the agent believes it has completed the task
|
|
109
|
-
"""
|
|
110
|
-
if verbose:
|
|
111
|
-
logger.info("Predicting action...")
|
|
112
|
-
# Stage 1: Preprocess the observation
|
|
113
|
-
processed_obs = self.preprocess(observation)
|
|
114
|
-
|
|
115
|
-
# Stage 2: Fetch response from the model
|
|
116
|
-
actions, done = await self.fetch_response(processed_obs)
|
|
117
|
-
if verbose:
|
|
118
|
-
logger.info("Raw action: %s", actions)
|
|
119
|
-
|
|
120
|
-
# Stage 3: Postprocess the actions if we have an adapter
|
|
121
|
-
if self.adapter and actions:
|
|
122
|
-
hud_actions = self.postprocess(actions)
|
|
123
|
-
return hud_actions, done
|
|
124
|
-
|
|
125
|
-
# If no adapter, return actions as is
|
|
126
|
-
return actions, done
|