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.

Files changed (192) hide show
  1. hud/__init__.py +22 -89
  2. hud/agents/__init__.py +15 -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 +370 -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 +379 -0
  45. hud/clients/fastmcp.py +222 -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.1.dist-info/METADATA +476 -0
  126. hud_python-0.4.1.dist-info/RECORD +132 -0
  127. hud_python-0.4.1.dist-info/entry_points.txt +3 -0
  128. {hud_python-0.3.5.dist-info → hud_python-0.4.1.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.1.dist-info}/WHEEL +0 -0
hud/env/docker_client.py DELETED
@@ -1,349 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import abc
4
- import json
5
- import logging
6
- import os
7
- import uuid
8
- from pathlib import Path
9
- from typing import TYPE_CHECKING, Any
10
-
11
- from hud.env.client import Client
12
- from hud.types import EnvironmentStatus
13
- from hud.utils.common import _compile_pathspec, directory_to_tar_bytes
14
-
15
- if TYPE_CHECKING:
16
- from hud.utils import ExecuteResult
17
- from hud.utils.config import FunctionConfig
18
-
19
- logger = logging.getLogger("hud.env.docker_client")
20
-
21
- STATUS_MESSAGES = {
22
- EnvironmentStatus.RUNNING.value: "is running",
23
- EnvironmentStatus.ERROR.value: "had an error initializing",
24
- EnvironmentStatus.COMPLETED.value: "completed",
25
- }
26
-
27
- PACKAGE_NAME = "hud_controller"
28
-
29
-
30
- class InvokeError(Exception):
31
- """
32
- Error raised when an invoke fails.
33
- """
34
-
35
-
36
- def invoke_template(config: FunctionConfig, package_name: str, divider: str) -> str:
37
- """
38
- Return a python script to run the given config.
39
- """
40
- func_parts = config.function.split(".")
41
- module_str = ".".join([package_name] + func_parts[:-1])
42
- func_str = func_parts[-1]
43
-
44
- # the reason we call `json.dumps` twice is to escape the json string
45
- return f"""import json
46
- from {module_str} import {func_str}
47
- args = json.loads({json.dumps(json.dumps(config.args))})
48
- result = {func_str}(*args)
49
- result_str = json.dumps(result)
50
- print("{divider}")
51
- print(result_str)
52
- """
53
-
54
-
55
- class DockerClient(Client):
56
- """
57
- Base class for environment clients.
58
-
59
- Handles updating the environment when local files change.
60
- """
61
-
62
- _last_pyproject_toml_str: str | None = None
63
- _last_update_time: int = 0
64
- _last_file_mtimes: dict[str, float] = {} # noqa: RUF012 - Not recognized as Pydantic model
65
- _source_path: Path | None = None
66
-
67
- @property
68
- def source_path(self) -> Path | None:
69
- """Get the source path."""
70
- return self._source_path
71
-
72
- def set_source_path(self, source_path: Path) -> None:
73
- """
74
- Set the source path for this environment controller.
75
- Can only be set once, and cannot be set if source_path is already set.
76
-
77
- Args:
78
- source_path: Path to the source code to use in the environment
79
-
80
- Raises:
81
- ValueError: If source_path has already been set
82
- """
83
- if self._source_path:
84
- raise ValueError("Source path has already been set")
85
-
86
- # Validate source path
87
- if not source_path.exists():
88
- raise FileNotFoundError(f"Source path {source_path} does not exist")
89
- if not source_path.is_dir():
90
- raise NotADirectoryError(f"Source path {source_path} is not a directory")
91
-
92
- # Parse pyproject.toml to get package name
93
- pyproject_path = source_path / "pyproject.toml"
94
- if not pyproject_path.exists():
95
- raise FileNotFoundError(f"pyproject.toml not found in {source_path}")
96
-
97
- # validate package name
98
- try:
99
- import toml
100
- except ImportError:
101
- raise ImportError(
102
- "toml is required for parsing pyproject.toml files. "
103
- "Please install it with 'pip install toml'"
104
- ) from None
105
- pyproject_data = toml.load(pyproject_path)
106
- package_name = pyproject_data.get("project", {}).get("name")
107
- if not package_name:
108
- raise ValueError("Could not find package name in pyproject.toml")
109
- if package_name != PACKAGE_NAME:
110
- raise ValueError(f"Package name in pyproject.toml must be {PACKAGE_NAME}")
111
-
112
- self._source_path = source_path
113
-
114
- # set current mtimes
115
- self._last_file_mtimes = self._get_all_file_mtimes()
116
-
117
- @classmethod
118
- @abc.abstractmethod
119
- async def build_image(cls, build_context: Path) -> tuple[str, dict[str, Any]]:
120
- """
121
- Build an image from a build context.
122
-
123
- Returns:
124
- tuple[str, dict[str, Any]]: The image tag and build output
125
- """
126
-
127
- @classmethod
128
- @abc.abstractmethod
129
- async def create(cls, image: str) -> DockerClient:
130
- """
131
- Creates an environment client from an image.
132
-
133
- Args:
134
- image: The image to build the environment from
135
-
136
- Returns:
137
- EnvClient: An instance of the environment client
138
- """
139
-
140
- @abc.abstractmethod
141
- async def get_status(self) -> EnvironmentStatus:
142
- """
143
- Get the current status of the environment.
144
-
145
- Returns:
146
- EnvironmentStatus: A status enum indicating the current state of the environment
147
- """
148
-
149
- def _get_all_file_mtimes(self) -> dict[str, float]:
150
- """
151
- Get modification times for all files in the source path.
152
-
153
- Returns:
154
- Dict[str, float]: Dictionary mapping file paths to modification times
155
- """
156
- if not self._source_path:
157
- return {}
158
-
159
- # Build ignore spec (currently we only care about .hudignore but reuse
160
- # the common helper for consistency).
161
- spec = _compile_pathspec(
162
- self._source_path,
163
- respect_gitignore=False,
164
- respect_dockerignore=False,
165
- respect_hudignore=True,
166
- )
167
-
168
- file_mtimes: dict[str, float] = {}
169
-
170
- for root, _, files in os.walk(self._source_path):
171
- for file in files:
172
- file_path = Path(root) / file
173
- rel_path = file_path.relative_to(self._source_path).as_posix()
174
-
175
- # Skip ignored files
176
- if spec and spec.match_file(rel_path):
177
- continue
178
-
179
- try:
180
- file_mtimes[str(file_path)] = file_path.stat().st_mtime
181
- except (FileNotFoundError, PermissionError):
182
- # Skip files that can't be accessed
183
- continue
184
-
185
- return file_mtimes
186
-
187
- async def needs_update(self) -> bool:
188
- """
189
- Check if the environment needs an update by:
190
- 1. Checking if any file has been modified since the last update
191
-
192
- Returns:
193
- bool: True if the environment needs an update, False otherwise.
194
- """
195
- # If no source path, no update needed
196
- if not self.source_path:
197
- return False
198
-
199
- # Check if any file has been modified since the last update
200
- current_mtimes = self._get_all_file_mtimes()
201
-
202
- # If we don't have previous modification times, we need an update
203
- if not self._last_file_mtimes:
204
- return True
205
-
206
- # Check for removed files
207
- for file_path in self._last_file_mtimes:
208
- if file_path not in current_mtimes:
209
- return True
210
-
211
- # Check for new or modified files
212
- for file_path, mtime in current_mtimes.items():
213
- if file_path not in self._last_file_mtimes or mtime > self._last_file_mtimes[file_path]:
214
- return True
215
-
216
- return False
217
-
218
- async def update(self) -> None:
219
- """
220
- Base update method for environment controllers.
221
- For controllers with no source path, this is a no-op.
222
- """
223
- # If no source path, nothing to update
224
- if not self._source_path:
225
- return
226
-
227
- logger.info("Updating environment")
228
-
229
- # Save current file modification times
230
- self._last_file_mtimes = self._get_all_file_mtimes()
231
-
232
- # Create tar archive of the source code and send it to the container
233
- tar_bytes = directory_to_tar_bytes(self._source_path)
234
- await self.execute(["mkdir", "-p", "/controller"], timeout=5)
235
- await self.put_archive("/controller", tar_bytes)
236
-
237
- # Check if pyproject.toml exists and parse it
238
- pyproject_path = self._source_path / "pyproject.toml"
239
- if not pyproject_path.exists():
240
- raise FileNotFoundError(f"pyproject.toml not found in {self._source_path}")
241
-
242
- # Read and parse the current content of pyproject.toml
243
- current_pyproject_content = pyproject_path.read_text()
244
- if (
245
- self._last_pyproject_toml_str is None
246
- or self._last_pyproject_toml_str != current_pyproject_content
247
- ):
248
- # Update package name if pyproject.toml changed
249
- try:
250
- import toml
251
- except ImportError:
252
- raise ImportError(
253
- "toml is required for parsing pyproject.toml files. "
254
- "Please install it with 'pip install toml'"
255
- ) from None
256
- pyproject_data = toml.loads(current_pyproject_content)
257
- self._package_name = pyproject_data.get("project", {}).get("name")
258
- if not self._package_name:
259
- raise ValueError("Could not find package name in pyproject.toml")
260
- logger.info("Installing %s in /controller", self._package_name)
261
- result = await self.execute(
262
- ["bash", "-c", "cd /controller && pip install -e . --break-system-packages"],
263
- timeout=60,
264
- )
265
- if result["stdout"]:
266
- logger.info("STDOUT:\n%s", result["stdout"])
267
- if result["stderr"]:
268
- logger.warning("STDERR:\n%s", result["stderr"])
269
- # Save current pyproject.toml content
270
- self._last_pyproject_toml_str = current_pyproject_content
271
-
272
- @abc.abstractmethod
273
- async def execute(
274
- self,
275
- command: list[str],
276
- *,
277
- timeout: int | None = None, # noqa: ASYNC109
278
- ) -> ExecuteResult:
279
- """
280
- Execute a command in the environment. May not be supported by all environments.
281
-
282
- Args:
283
- command: The command to execute
284
- workdir: The working directory to execute the command in
285
- timeout: The timeout for the command
286
-
287
- Returns:
288
- ExecuteResult: The result of the command
289
- """
290
-
291
- async def invoke(self, config: FunctionConfig) -> tuple[Any, bytes, bytes]:
292
- """
293
- Invoke a function in the environment. Supported by all environments.
294
-
295
- Args:
296
- config: The configuration to invoke
297
-
298
- Returns:
299
- tuple[Any, bytes, bytes]: The result of the invocation, stdout, and stderr
300
- """
301
-
302
- if await self.needs_update():
303
- logger.info("Environment needs update, updating")
304
- await self.update()
305
-
306
- # generate a random uuid as a divider
307
- divider = str(uuid.uuid4())
308
-
309
- template = invoke_template(config, PACKAGE_NAME, divider)
310
- logger.debug("Invoking template: %s", template)
311
-
312
- result = await self.execute(["python3", "-c", template])
313
-
314
- # parse the result
315
- # we take the whole stderr as the stderr, and the stdout is the result pre-divider
316
- stderr = result["stderr"]
317
- stdout_parts = result["stdout"].split(divider.encode())
318
- stdout = stdout_parts[0]
319
-
320
- # parse the json part of the stdout (if it exists)
321
- if len(stdout_parts) > 1:
322
- result = json.loads(stdout_parts[1])
323
- else:
324
- logger.warning("Potential error: %s", stderr)
325
- result = None
326
-
327
- return result, stdout, stderr
328
-
329
- @abc.abstractmethod
330
- async def get_archive(self, path: str) -> bytes:
331
- """
332
- Get an archive of a path from the environment.
333
- May not be supported by all environments. (notably browser environments)
334
- Args:
335
- path: The path to get the archive of
336
-
337
- Returns:
338
- bytes: The archive of the path
339
- """
340
-
341
- @abc.abstractmethod
342
- async def put_archive(self, path: str, data: bytes) -> bool:
343
- """
344
- Put an archive of data at a path in the environment.
345
- May not be supported by all environments. (notably browser environments)
346
- Args:
347
- path: The path to put the archive at
348
- data: The data to put in the archive
349
- """