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
@@ -1,292 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import logging
4
- from base64 import b64decode, b64encode
5
- from typing import TYPE_CHECKING, Any
6
-
7
- import httpx
8
-
9
- from hud.env.docker_client import DockerClient
10
- from hud.exceptions import HudResponseError
11
- from hud.server import make_request
12
- from hud.settings import settings
13
- from hud.types import EnvironmentStatus
14
- from hud.utils import ExecuteResult
15
- from hud.utils.common import directory_to_zip_bytes, get_gym_id
16
-
17
- if TYPE_CHECKING:
18
- from pathlib import Path
19
-
20
- logger = logging.getLogger("hud.env.remote_env_client")
21
-
22
-
23
- async def upload_bytes_to_presigned_url(
24
- presigned_url: str,
25
- data_bytes: bytes,
26
- timeout: float = 600, # noqa: ASYNC109
27
- ) -> None:
28
- try:
29
- async with httpx.AsyncClient() as client:
30
- response = await client.put(presigned_url, content=data_bytes, timeout=timeout)
31
- response.raise_for_status()
32
- except httpx.HTTPStatusError as e:
33
- logger.exception("Failed to upload to presigned URL")
34
- raise HudResponseError(message=f"Failed to upload to presigned URL: {e}") from e
35
- except httpx.RequestError as e:
36
- logger.exception("Network error uploading to presigned URL")
37
- raise HudResponseError(message=f"Network error uploading to presigned URL: {e}") from e
38
-
39
-
40
- class RemoteDockerClient(DockerClient):
41
- """
42
- Remote environment client implementation.
43
-
44
- Uses the HUD API to manage a remote environment.
45
- """
46
-
47
- @classmethod
48
- async def build_image(cls, build_context: Path) -> tuple[str, dict[str, Any]]:
49
- """
50
- Build an image from a build context.
51
- """
52
- # create the presigned url by making a POST request to /v2/builds
53
- logger.info("Creating build")
54
- response = await make_request(
55
- method="POST",
56
- url=f"{settings.base_url}/v2/builds",
57
- api_key=settings.api_key,
58
- )
59
- logger.info("Build created")
60
- presigned_url = response["presigned_url"]
61
-
62
- # List files in the build context
63
- files = list(build_context.glob("**/*"))
64
- logger.info("Found %d files in build context %s", len(files), build_context)
65
-
66
- if len(files) == 0:
67
- raise HudResponseError(message="Build context is empty")
68
-
69
- # zip the build context
70
- logger.info("Zipping build context")
71
- zip_bytes = directory_to_zip_bytes(build_context)
72
- logger.info("Created zip archive of size %d kb", len(zip_bytes) // 1024)
73
- # upload the zip bytes to the presigned url
74
- logger.info("Uploading build context")
75
- await upload_bytes_to_presigned_url(presigned_url, zip_bytes)
76
- logger.info("Build context uploaded")
77
-
78
- # start the build and return uri and logs
79
- logger.info("Starting build")
80
- response = await make_request(
81
- method="POST",
82
- url=f"{settings.base_url}/v2/builds/{response['id']}/start",
83
- api_key=settings.api_key,
84
- )
85
- logger.info("Build completed")
86
-
87
- return response["uri"], {"logs": response["logs"]}
88
-
89
- @classmethod
90
- async def create(
91
- cls,
92
- image_uri: str,
93
- *,
94
- job_id: str | None = None,
95
- task_id: str | None = None,
96
- metadata: dict[str, Any] | None = None,
97
- ) -> RemoteDockerClient:
98
- """
99
- Creates a remote environment client from an image.
100
-
101
- Args:
102
- image_uri: The image uri to create the environment from
103
- job_id: The job_id of the environment to create
104
- task_id: The task_id of the environment to create
105
- metadata: Metadata to associate with the environment
106
-
107
- Returns:
108
- A tuple containing the remote environment client and the build metadata
109
-
110
- Raises:
111
- HudResponseError: If the environment creation fails.
112
- """
113
-
114
- # Validate arguments
115
- if metadata is None:
116
- metadata = {}
117
-
118
- logger.info("Creating remote environment")
119
-
120
- # true_gym_id = await get_gym_id("local-docker")
121
- true_gym_id = await get_gym_id("docker")
122
-
123
- # augment metadata with dockerfile
124
- if "environment_config" not in metadata:
125
- metadata["environment_config"] = {}
126
-
127
- metadata["environment_config"]["image_uri"] = image_uri
128
-
129
- # Create a new environment via the HUD API
130
- response = await make_request(
131
- method="POST",
132
- url=f"{settings.base_url}/v2/create_environment",
133
- json={
134
- # still named run_id for backwards compatibility
135
- "run_id": job_id,
136
- "metadata": metadata,
137
- "gym_id": true_gym_id,
138
- "task_id": task_id,
139
- },
140
- api_key=settings.api_key,
141
- )
142
-
143
- # Get the environment ID from the response
144
- env_id = response.get("id")
145
- if not env_id:
146
- raise HudResponseError(
147
- message=(
148
- "Failed to create remote environment: No ID returned in API response. "
149
- "Please contact support if this issue persists."
150
- ),
151
- response_json=response,
152
- )
153
-
154
- return cls(env_id)
155
-
156
- def __init__(self, env_id: str) -> None:
157
- """
158
- Initialize the RemoteClient.
159
-
160
- Args:
161
- env_id: ID of the remote environment to control
162
- """
163
- super().__init__()
164
- self._env_id = env_id
165
-
166
- @property
167
- def env_id(self) -> str:
168
- """The ID of the remote environment."""
169
- return self._env_id
170
-
171
- async def get_status(self) -> EnvironmentStatus:
172
- """
173
- Get the current status of the remote environment.
174
-
175
- Returns:
176
- EnvironmentStatus: The current status of the environment
177
- """
178
- try:
179
- response = await make_request(
180
- method="GET",
181
- url=f"{settings.base_url}/v2/environments/{self.env_id}/state",
182
- api_key=settings.api_key,
183
- )
184
- logger.debug("Environment status response: %s", response)
185
-
186
- status = response.get("state", "").lower()
187
-
188
- if status == "running":
189
- return EnvironmentStatus.RUNNING
190
- elif status == "initializing" or status == "pending":
191
- return EnvironmentStatus.INITIALIZING
192
- elif status == "completed" or status == "terminated":
193
- return EnvironmentStatus.COMPLETED
194
- else:
195
- # Any other status is considered an error
196
- logger.warning("Abnormal environment status response: %s", response)
197
- return EnvironmentStatus.ERROR
198
-
199
- except Exception:
200
- # If we can't connect to the API or there's any other error
201
- logger.info("(potentially transient) Error getting environment status")
202
- return EnvironmentStatus.ERROR
203
-
204
- async def execute(
205
- self,
206
- command: list[str],
207
- *,
208
- workdir: str | None = None,
209
- timeout: float | None = None, # noqa: ASYNC109
210
- ) -> ExecuteResult:
211
- """
212
- Execute a command in the environment.
213
- No-op in some environments (like browser use).
214
-
215
- Args:
216
- command: Command to execute
217
- workdir: Working directory for the command (ignored for remote environments)
218
-
219
- Returns:
220
- ExecuteResult: Result of the command execution
221
- """
222
- data = await make_request(
223
- method="POST",
224
- url=f"{settings.base_url}/v2/environments/{self.env_id}/execute",
225
- json={
226
- "command": command,
227
- "workdir": workdir,
228
- "timeout": timeout,
229
- },
230
- api_key=settings.api_key,
231
- )
232
-
233
- return ExecuteResult(
234
- stdout=b64decode(data["stdout"]),
235
- stderr=b64decode(data["stderr"]),
236
- exit_code=data["exit_code"],
237
- )
238
-
239
- async def get_archive(self, path: str) -> bytes:
240
- """
241
- Get an archive of a path from the environment.
242
- May not be supported for all environments.
243
-
244
- Args:
245
- path: Path in the environment to archive
246
-
247
- Returns:
248
- bytes: Content of the file or archive
249
- """
250
- data = await make_request(
251
- method="POST",
252
- url=f"{settings.base_url}/v2/environments/{self.env_id}/get_archive",
253
- json={"path": path},
254
- api_key=settings.api_key,
255
- )
256
-
257
- # Return the content decoded from base64
258
- return b64decode(data["content"])
259
-
260
- async def put_archive(self, path: str, data: bytes) -> bool:
261
- """
262
- Put an archive of data at a path in the environment.
263
- May not be supported for all environments.
264
-
265
- Args:
266
- path: Path in the environment to extract the archive to
267
- data: Bytes of the data to send
268
-
269
- Returns:
270
- bool: True if successful
271
- """
272
- await make_request(
273
- method="POST",
274
- url=f"{settings.base_url}/v2/environments/{self.env_id}/put_archive",
275
- json={
276
- "path": path,
277
- "content": b64encode(data).decode("utf-8"),
278
- },
279
- api_key=settings.api_key,
280
- )
281
-
282
- return True
283
-
284
- async def close(self) -> None:
285
- """
286
- Close the remote environment by making a request to the server.
287
- """
288
- await make_request(
289
- method="POST",
290
- url=f"{settings.base_url}/v2/environments/{self.env_id}/close",
291
- api_key=settings.api_key,
292
- )
hud/gym.py DELETED
@@ -1,130 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import logging
4
- from pathlib import Path
5
- from typing import TYPE_CHECKING, Any
6
-
7
- from hud.env.environment import Environment
8
- from hud.env.local_docker_client import LocalDockerClient
9
- from hud.env.remote_client import RemoteClient
10
- from hud.env.remote_docker_client import RemoteDockerClient
11
- from hud.exceptions import GymMakeException
12
- from hud.task import Task
13
- from hud.telemetry.context import get_current_task_run_id
14
- from hud.types import CustomGym, Gym
15
- from hud.utils.common import get_gym_id
16
-
17
- if TYPE_CHECKING:
18
- from hud.job import Job
19
-
20
- logger = logging.getLogger("hud.gym")
21
-
22
-
23
- async def make(
24
- env_src: Gym | Task,
25
- *,
26
- job: Job | None = None,
27
- job_id: str | None = None,
28
- metadata: dict[str, Any] | None = None,
29
- ) -> Environment:
30
- """
31
- Create an environment from an environment ID or a Task object.
32
-
33
- Args:
34
- env_src: Environment ID or Task object
35
- job: Job object to associate with this environment
36
- job_id: ID of job to associate with this environment (deprecated, use job instead)
37
- metadata: Additional metadata for the environment
38
- """
39
- task = None
40
- if isinstance(env_src, str | CustomGym):
41
- gym = env_src
42
- elif isinstance(env_src, Task):
43
- gym = env_src.gym
44
- task = env_src
45
- else:
46
- raise GymMakeException(f"Invalid gym source: {env_src}", {})
47
-
48
- effective_job_id = None
49
- if job is not None:
50
- effective_job_id = job.id
51
- elif job_id is not None:
52
- effective_job_id = job_id
53
-
54
- build_data = {}
55
- try:
56
- metadata_copy = {} if metadata is None else metadata.copy()
57
-
58
- current_task_run_id = get_current_task_run_id()
59
- if current_task_run_id:
60
- metadata_copy["task_run_id"] = current_task_run_id
61
- logger.debug(
62
- "Passing task_run_id %s from hud.telemetry context to environment metadata.",
63
- current_task_run_id,
64
- )
65
-
66
- if isinstance(gym, CustomGym):
67
- if isinstance(gym.image_or_build_context, str):
68
- uri = gym.image_or_build_context
69
- elif isinstance(gym.image_or_build_context, Path):
70
- if gym.location == "local":
71
- uri, build_data = await LocalDockerClient.build_image(
72
- gym.image_or_build_context
73
- )
74
- elif gym.location == "remote":
75
- uri, build_data = await RemoteDockerClient.build_image(
76
- gym.image_or_build_context
77
- )
78
- else:
79
- raise ValueError(f"Invalid environment location: {gym.location}")
80
- else:
81
- raise ValueError(f"Invalid image or build context: {gym.image_or_build_context}")
82
-
83
- if gym.location == "local":
84
- logger.info("Creating local environment")
85
- if gym.host_config:
86
- logger.info("Using host config: %s", gym.host_config)
87
- client = await LocalDockerClient.create(uri, gym.host_config)
88
- else:
89
- client = await LocalDockerClient.create(uri)
90
-
91
- elif gym.location == "remote":
92
- logger.info("Creating remote environment")
93
-
94
- if gym.host_config:
95
- raise ValueError("host_config is not supported for remote environments")
96
-
97
- client = await RemoteDockerClient.create(
98
- image_uri=uri,
99
- job_id=effective_job_id,
100
- task_id=task.id if task else None,
101
- metadata=metadata_copy,
102
- )
103
- else:
104
- raise ValueError(f"Invalid environment location: {gym.location}")
105
-
106
- if isinstance(gym.image_or_build_context, Path):
107
- logger.info("Setting source path %s", gym.image_or_build_context)
108
- client.set_source_path(gym.image_or_build_context)
109
- elif isinstance(gym, str):
110
- logger.debug("Creating private environment")
111
- true_gym_id = await get_gym_id(gym)
112
- client, build_data = await RemoteClient.create(
113
- gym_id=true_gym_id,
114
- job_id=effective_job_id,
115
- task_id=task.id if task else None,
116
- metadata=metadata_copy,
117
- )
118
- else:
119
- raise ValueError(f"Invalid gym source: {gym}")
120
-
121
- environment = Environment(
122
- client=client, metadata=metadata_copy, task=task, build_data=build_data
123
- )
124
-
125
- if task:
126
- await environment._setup()
127
- return environment
128
- except Exception as e:
129
- build_data["exception"] = str(e)
130
- raise GymMakeException("Failed to create environment", build_data) from e