hud-python 0.4.1__py3-none-any.whl → 0.4.3__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 (130) hide show
  1. hud/__init__.py +22 -22
  2. hud/agents/__init__.py +13 -15
  3. hud/agents/base.py +599 -599
  4. hud/agents/claude.py +373 -373
  5. hud/agents/langchain.py +261 -250
  6. hud/agents/misc/__init__.py +7 -7
  7. hud/agents/misc/response_agent.py +82 -80
  8. hud/agents/openai.py +352 -352
  9. hud/agents/openai_chat_generic.py +154 -154
  10. hud/agents/tests/__init__.py +1 -1
  11. hud/agents/tests/test_base.py +742 -742
  12. hud/agents/tests/test_claude.py +324 -324
  13. hud/agents/tests/test_client.py +363 -363
  14. hud/agents/tests/test_openai.py +237 -237
  15. hud/cli/__init__.py +617 -617
  16. hud/cli/__main__.py +8 -8
  17. hud/cli/analyze.py +371 -371
  18. hud/cli/analyze_metadata.py +230 -230
  19. hud/cli/build.py +498 -427
  20. hud/cli/clone.py +185 -185
  21. hud/cli/cursor.py +92 -92
  22. hud/cli/debug.py +392 -392
  23. hud/cli/docker_utils.py +83 -83
  24. hud/cli/init.py +280 -281
  25. hud/cli/interactive.py +353 -353
  26. hud/cli/mcp_server.py +764 -756
  27. hud/cli/pull.py +330 -336
  28. hud/cli/push.py +404 -370
  29. hud/cli/remote_runner.py +311 -311
  30. hud/cli/runner.py +160 -160
  31. hud/cli/tests/__init__.py +3 -3
  32. hud/cli/tests/test_analyze.py +284 -284
  33. hud/cli/tests/test_cli_init.py +265 -265
  34. hud/cli/tests/test_cli_main.py +27 -27
  35. hud/cli/tests/test_clone.py +142 -142
  36. hud/cli/tests/test_cursor.py +253 -253
  37. hud/cli/tests/test_debug.py +453 -453
  38. hud/cli/tests/test_mcp_server.py +139 -139
  39. hud/cli/tests/test_utils.py +388 -388
  40. hud/cli/utils.py +263 -263
  41. hud/clients/README.md +143 -143
  42. hud/clients/__init__.py +16 -16
  43. hud/clients/base.py +378 -379
  44. hud/clients/fastmcp.py +222 -222
  45. hud/clients/mcp_use.py +298 -278
  46. hud/clients/tests/__init__.py +1 -1
  47. hud/clients/tests/test_client_integration.py +111 -111
  48. hud/clients/tests/test_fastmcp.py +342 -342
  49. hud/clients/tests/test_protocol.py +188 -188
  50. hud/clients/utils/__init__.py +1 -1
  51. hud/clients/utils/retry_transport.py +160 -160
  52. hud/datasets.py +327 -322
  53. hud/misc/__init__.py +1 -1
  54. hud/misc/claude_plays_pokemon.py +292 -292
  55. hud/otel/__init__.py +35 -35
  56. hud/otel/collector.py +142 -142
  57. hud/otel/config.py +164 -164
  58. hud/otel/context.py +536 -536
  59. hud/otel/exporters.py +366 -366
  60. hud/otel/instrumentation.py +97 -97
  61. hud/otel/processors.py +118 -118
  62. hud/otel/tests/__init__.py +1 -1
  63. hud/otel/tests/test_processors.py +197 -197
  64. hud/server/__init__.py +5 -5
  65. hud/server/context.py +114 -114
  66. hud/server/helper/__init__.py +5 -5
  67. hud/server/low_level.py +132 -132
  68. hud/server/server.py +170 -166
  69. hud/server/tests/__init__.py +3 -3
  70. hud/settings.py +73 -73
  71. hud/shared/__init__.py +5 -5
  72. hud/shared/exceptions.py +180 -180
  73. hud/shared/requests.py +264 -264
  74. hud/shared/tests/test_exceptions.py +157 -157
  75. hud/shared/tests/test_requests.py +275 -275
  76. hud/telemetry/__init__.py +25 -25
  77. hud/telemetry/instrument.py +379 -379
  78. hud/telemetry/job.py +309 -309
  79. hud/telemetry/replay.py +74 -74
  80. hud/telemetry/trace.py +83 -83
  81. hud/tools/__init__.py +33 -33
  82. hud/tools/base.py +365 -365
  83. hud/tools/bash.py +161 -161
  84. hud/tools/computer/__init__.py +15 -15
  85. hud/tools/computer/anthropic.py +437 -437
  86. hud/tools/computer/hud.py +376 -376
  87. hud/tools/computer/openai.py +295 -295
  88. hud/tools/computer/settings.py +82 -82
  89. hud/tools/edit.py +314 -314
  90. hud/tools/executors/__init__.py +30 -30
  91. hud/tools/executors/base.py +539 -539
  92. hud/tools/executors/pyautogui.py +621 -621
  93. hud/tools/executors/tests/__init__.py +1 -1
  94. hud/tools/executors/tests/test_base_executor.py +338 -338
  95. hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
  96. hud/tools/executors/xdo.py +511 -511
  97. hud/tools/playwright.py +412 -412
  98. hud/tools/tests/__init__.py +3 -3
  99. hud/tools/tests/test_base.py +282 -282
  100. hud/tools/tests/test_bash.py +158 -158
  101. hud/tools/tests/test_bash_extended.py +197 -197
  102. hud/tools/tests/test_computer.py +425 -425
  103. hud/tools/tests/test_computer_actions.py +34 -34
  104. hud/tools/tests/test_edit.py +259 -259
  105. hud/tools/tests/test_init.py +27 -27
  106. hud/tools/tests/test_playwright_tool.py +183 -183
  107. hud/tools/tests/test_tools.py +145 -145
  108. hud/tools/tests/test_utils.py +156 -156
  109. hud/tools/types.py +72 -72
  110. hud/tools/utils.py +50 -50
  111. hud/types.py +136 -136
  112. hud/utils/__init__.py +10 -10
  113. hud/utils/async_utils.py +65 -65
  114. hud/utils/design.py +236 -168
  115. hud/utils/mcp.py +55 -55
  116. hud/utils/progress.py +149 -149
  117. hud/utils/telemetry.py +66 -66
  118. hud/utils/tests/test_async_utils.py +173 -173
  119. hud/utils/tests/test_init.py +17 -17
  120. hud/utils/tests/test_progress.py +261 -261
  121. hud/utils/tests/test_telemetry.py +82 -82
  122. hud/utils/tests/test_version.py +8 -8
  123. hud/version.py +7 -7
  124. {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/METADATA +10 -8
  125. hud_python-0.4.3.dist-info/RECORD +131 -0
  126. {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/licenses/LICENSE +21 -21
  127. hud/agents/art.py +0 -101
  128. hud_python-0.4.1.dist-info/RECORD +0 -132
  129. {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/WHEEL +0 -0
  130. {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/entry_points.txt +0 -0
hud/tools/bash.py CHANGED
@@ -1,161 +1,161 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
- import os
5
- import sys
6
- from typing import TYPE_CHECKING, Any
7
-
8
- from .base import BaseTool
9
- from .types import ContentResult, ToolError
10
-
11
- if TYPE_CHECKING:
12
- from mcp.types import ContentBlock
13
-
14
-
15
- class _BashSession:
16
- """A session of a bash shell."""
17
-
18
- _started: bool
19
- _process: asyncio.subprocess.Process
20
-
21
- command: str = "/bin/bash"
22
- _output_delay: float = 0.2 # seconds
23
- _timeout: float = 120.0 # seconds
24
- _sentinel: str = "<<exit>>"
25
-
26
- def __init__(self) -> None:
27
- self._started = False
28
- self._timed_out = False
29
-
30
- async def start(self) -> None:
31
- if self._started:
32
- await asyncio.sleep(0)
33
- return
34
-
35
- # Platform-specific subprocess creation
36
- kwargs = {
37
- "shell": True,
38
- "bufsize": 0,
39
- "stdin": asyncio.subprocess.PIPE,
40
- "stdout": asyncio.subprocess.PIPE,
41
- "stderr": asyncio.subprocess.PIPE,
42
- }
43
-
44
- # Only use setsid on Unix-like systems
45
- if sys.platform != "win32":
46
- kwargs["preexec_fn"] = os.setsid
47
-
48
- self._process = await asyncio.create_subprocess_shell(self.command, **kwargs)
49
-
50
- self._started = True
51
-
52
- def stop(self) -> None:
53
- """Terminate the bash shell."""
54
- if not self._started:
55
- raise ToolError("Session has not started.")
56
- if self._process.returncode is not None:
57
- return
58
- self._process.terminate()
59
-
60
- async def run(self, command: str) -> ContentResult:
61
- """Execute a command in the bash shell."""
62
- if not self._started:
63
- raise ToolError("Session has not started.")
64
- if self._process.returncode is not None:
65
- await asyncio.sleep(0)
66
- return ContentResult(
67
- system="tool must be restarted",
68
- error=f"bash has exited with returncode {self._process.returncode}",
69
- )
70
- if self._timed_out:
71
- raise ToolError(
72
- f"timed out: bash did not return in {self._timeout} seconds and must be restarted",
73
- ) from None
74
-
75
- if self._process.stdin is None:
76
- raise ToolError("stdin is None")
77
- if self._process.stdout is None:
78
- raise ToolError("stdout is None")
79
- if self._process.stderr is None:
80
- raise ToolError("stderr is None")
81
-
82
- # Send command to the process
83
- self._process.stdin.write(command.encode() + f"; echo '{self._sentinel}'\n".encode())
84
- await self._process.stdin.drain()
85
-
86
- # Read output from the process, until the sentinel is found
87
- sentinel_line = f"{self._sentinel}\n"
88
- sentinel_bytes = sentinel_line.encode()
89
-
90
- try:
91
- raw_out: bytes = await asyncio.wait_for(
92
- self._process.stdout.readuntil(sentinel_bytes),
93
- timeout=self._timeout,
94
- )
95
- output = raw_out.decode()[: -len(sentinel_line)]
96
- except (TimeoutError, asyncio.LimitOverrunError):
97
- self._timed_out = True
98
- raise ToolError(
99
- f"timed out: bash did not return in {self._timeout} seconds and must be restarted",
100
- ) from None
101
-
102
- # Attempt non-blocking stderr fetch (may return empty)
103
- try:
104
- error_bytes = await asyncio.wait_for(self._process.stderr.read(), timeout=0.01)
105
- error = error_bytes.decode().rstrip("\n")
106
- except TimeoutError:
107
- error = ""
108
-
109
- return ContentResult(output=output, error=error)
110
-
111
-
112
- class BashTool(BaseTool):
113
- """
114
- A tool that allows the agent to run bash commands.
115
- The tool maintains a persistent bash session that can be restarted.
116
- """
117
-
118
- def __init__(self, session: _BashSession | None = None) -> None:
119
- """Initialize BashTool with an optional session.
120
-
121
- Args:
122
- session: Optional pre-configured bash session. If not provided,
123
- a new session will be created on first use.
124
- """
125
- super().__init__(
126
- env=session,
127
- name="bash",
128
- title="Bash Shell",
129
- description="Execute bash commands in a persistent shell session",
130
- )
131
-
132
- @property
133
- def session(self) -> _BashSession | None:
134
- """Get the current bash session (alias for context)."""
135
- return self.env
136
-
137
- @session.setter
138
- def session(self, value: _BashSession | None) -> None:
139
- """Set the bash session (alias for context)."""
140
- self.env = value
141
-
142
- async def __call__(
143
- self, command: str | None = None, restart: bool = False, **kwargs: Any
144
- ) -> list[ContentBlock]:
145
- if restart:
146
- if self.session:
147
- self.session.stop()
148
- self.session = _BashSession()
149
- await self.session.start()
150
-
151
- return ContentResult(output="Bash session restarted.").to_content_blocks()
152
-
153
- if self.session is None:
154
- self.session = _BashSession()
155
- await self.session.start()
156
-
157
- if command is not None:
158
- result = await self.session.run(command)
159
- return result.to_content_blocks()
160
-
161
- raise ToolError("No command provided.")
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import os
5
+ import sys
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from .base import BaseTool
9
+ from .types import ContentResult, ToolError
10
+
11
+ if TYPE_CHECKING:
12
+ from mcp.types import ContentBlock
13
+
14
+
15
+ class _BashSession:
16
+ """A session of a bash shell."""
17
+
18
+ _started: bool
19
+ _process: asyncio.subprocess.Process
20
+
21
+ command: str = "/bin/bash"
22
+ _output_delay: float = 0.2 # seconds
23
+ _timeout: float = 120.0 # seconds
24
+ _sentinel: str = "<<exit>>"
25
+
26
+ def __init__(self) -> None:
27
+ self._started = False
28
+ self._timed_out = False
29
+
30
+ async def start(self) -> None:
31
+ if self._started:
32
+ await asyncio.sleep(0)
33
+ return
34
+
35
+ # Platform-specific subprocess creation
36
+ kwargs = {
37
+ "shell": True,
38
+ "bufsize": 0,
39
+ "stdin": asyncio.subprocess.PIPE,
40
+ "stdout": asyncio.subprocess.PIPE,
41
+ "stderr": asyncio.subprocess.PIPE,
42
+ }
43
+
44
+ # Only use setsid on Unix-like systems
45
+ if sys.platform != "win32":
46
+ kwargs["preexec_fn"] = os.setsid
47
+
48
+ self._process = await asyncio.create_subprocess_shell(self.command, **kwargs)
49
+
50
+ self._started = True
51
+
52
+ def stop(self) -> None:
53
+ """Terminate the bash shell."""
54
+ if not self._started:
55
+ raise ToolError("Session has not started.")
56
+ if self._process.returncode is not None:
57
+ return
58
+ self._process.terminate()
59
+
60
+ async def run(self, command: str) -> ContentResult:
61
+ """Execute a command in the bash shell."""
62
+ if not self._started:
63
+ raise ToolError("Session has not started.")
64
+ if self._process.returncode is not None:
65
+ await asyncio.sleep(0)
66
+ return ContentResult(
67
+ system="tool must be restarted",
68
+ error=f"bash has exited with returncode {self._process.returncode}",
69
+ )
70
+ if self._timed_out:
71
+ raise ToolError(
72
+ f"timed out: bash did not return in {self._timeout} seconds and must be restarted",
73
+ ) from None
74
+
75
+ if self._process.stdin is None:
76
+ raise ToolError("stdin is None")
77
+ if self._process.stdout is None:
78
+ raise ToolError("stdout is None")
79
+ if self._process.stderr is None:
80
+ raise ToolError("stderr is None")
81
+
82
+ # Send command to the process
83
+ self._process.stdin.write(command.encode() + f"; echo '{self._sentinel}'\n".encode())
84
+ await self._process.stdin.drain()
85
+
86
+ # Read output from the process, until the sentinel is found
87
+ sentinel_line = f"{self._sentinel}\n"
88
+ sentinel_bytes = sentinel_line.encode()
89
+
90
+ try:
91
+ raw_out: bytes = await asyncio.wait_for(
92
+ self._process.stdout.readuntil(sentinel_bytes),
93
+ timeout=self._timeout,
94
+ )
95
+ output = raw_out.decode()[: -len(sentinel_line)]
96
+ except (TimeoutError, asyncio.LimitOverrunError):
97
+ self._timed_out = True
98
+ raise ToolError(
99
+ f"timed out: bash did not return in {self._timeout} seconds and must be restarted",
100
+ ) from None
101
+
102
+ # Attempt non-blocking stderr fetch (may return empty)
103
+ try:
104
+ error_bytes = await asyncio.wait_for(self._process.stderr.read(), timeout=0.01)
105
+ error = error_bytes.decode().rstrip("\n")
106
+ except TimeoutError:
107
+ error = ""
108
+
109
+ return ContentResult(output=output, error=error)
110
+
111
+
112
+ class BashTool(BaseTool):
113
+ """
114
+ A tool that allows the agent to run bash commands.
115
+ The tool maintains a persistent bash session that can be restarted.
116
+ """
117
+
118
+ def __init__(self, session: _BashSession | None = None) -> None:
119
+ """Initialize BashTool with an optional session.
120
+
121
+ Args:
122
+ session: Optional pre-configured bash session. If not provided,
123
+ a new session will be created on first use.
124
+ """
125
+ super().__init__(
126
+ env=session,
127
+ name="bash",
128
+ title="Bash Shell",
129
+ description="Execute bash commands in a persistent shell session",
130
+ )
131
+
132
+ @property
133
+ def session(self) -> _BashSession | None:
134
+ """Get the current bash session (alias for context)."""
135
+ return self.env
136
+
137
+ @session.setter
138
+ def session(self, value: _BashSession | None) -> None:
139
+ """Set the bash session (alias for context)."""
140
+ self.env = value
141
+
142
+ async def __call__(
143
+ self, command: str | None = None, restart: bool = False, **kwargs: Any
144
+ ) -> list[ContentBlock]:
145
+ if restart:
146
+ if self.session:
147
+ self.session.stop()
148
+ self.session = _BashSession()
149
+ await self.session.start()
150
+
151
+ return ContentResult(output="Bash session restarted.").to_content_blocks()
152
+
153
+ if self.session is None:
154
+ self.session = _BashSession()
155
+ await self.session.start()
156
+
157
+ if command is not None:
158
+ result = await self.session.run(command)
159
+ return result.to_content_blocks()
160
+
161
+ raise ToolError("No command provided.")
@@ -1,15 +1,15 @@
1
- """Computer control tools for different agent APIs."""
2
-
3
- from __future__ import annotations
4
-
5
- from .anthropic import AnthropicComputerTool
6
- from .hud import HudComputerTool
7
- from .openai import OpenAIComputerTool
8
- from .settings import computer_settings
9
-
10
- __all__ = [
11
- "AnthropicComputerTool",
12
- "HudComputerTool",
13
- "OpenAIComputerTool",
14
- "computer_settings",
15
- ]
1
+ """Computer control tools for different agent APIs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .anthropic import AnthropicComputerTool
6
+ from .hud import HudComputerTool
7
+ from .openai import OpenAIComputerTool
8
+ from .settings import computer_settings
9
+
10
+ __all__ = [
11
+ "AnthropicComputerTool",
12
+ "HudComputerTool",
13
+ "OpenAIComputerTool",
14
+ "computer_settings",
15
+ ]