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/types.py CHANGED
@@ -1,136 +1,136 @@
1
- from __future__ import annotations
2
-
3
- import uuid
4
- from typing import Any, Literal
5
-
6
- from mcp.types import CallToolRequestParams, CallToolResult
7
- from pydantic import BaseModel, ConfigDict, Field
8
-
9
-
10
- class MCPToolCall(CallToolRequestParams):
11
- """A tool call."""
12
-
13
- id: str = Field(default_factory=lambda: str(uuid.uuid4())) # Unique identifier for reference
14
-
15
- def __str__(self) -> str:
16
- response = f"Tool: {self.name}"
17
- if self.arguments:
18
- response += f"\nArguments: {self.arguments}"
19
- return response
20
-
21
-
22
- class MCPToolResult(CallToolResult):
23
- """A tool result."""
24
-
25
- def __str__(self) -> str:
26
- response = f"Content: {self.content}"
27
- if self.structuredContent:
28
- response += f"\nStructured Content: {self.structuredContent}"
29
- if self.isError:
30
- response += f"\nError: {self.isError}"
31
- return response
32
-
33
-
34
- class AgentResponse(BaseModel):
35
- """A model response in the conversation."""
36
-
37
- # --- FUNCTIONAL ---
38
- tool_calls: list[MCPToolCall] = Field(default_factory=list)
39
- done: bool = Field(default=False)
40
-
41
- # --- TELEMETRY [app.hud.so] ---
42
- # Responses
43
- content: str | None = Field(default=None)
44
- reasoning: str | None = Field(default=None)
45
- info: dict[str, Any] = Field(default_factory=dict)
46
- isError: bool = Field(default=False)
47
- raw: Any | None = Field(default=None) # Include raw response for access to Choice objects
48
-
49
- # Timestamps
50
- start_timestamp: str | None = None
51
- end_timestamp: str | None = None
52
-
53
- def __str__(self) -> str:
54
- response = ""
55
- if self.reasoning:
56
- response += f"Reasoning: {self.reasoning}\n"
57
- if self.content:
58
- response += f"Content: {self.content}\n"
59
- if self.tool_calls:
60
- response += f"""Tool Calls: {
61
- ", ".join([f"{tc.name}: {tc.arguments}" for tc in self.tool_calls])
62
- }"""
63
- if self.raw:
64
- response += f"Raw: {self.raw}"
65
- return response
66
-
67
-
68
- class TraceStep(BaseModel):
69
- """Canonical data for a single span (shared with telemetry)."""
70
-
71
- # HUD identifiers
72
- task_run_id: str | None = Field(default=None)
73
- job_id: str | None = Field(default=None)
74
-
75
- # Span category - can be any string, but "mcp" and "agent" are privileged on the platform
76
- category: Literal["mcp", "agent"] | str = Field(default="mcp") # noqa: PYI051
77
-
78
- # Generic I/O fields - works for any category
79
- request: Any | None = None
80
- result: Any | None = None
81
-
82
- # Generic span info
83
- type: str = Field(default="CLIENT")
84
-
85
- # Timestamps (optional, for local tracking)
86
- start_timestamp: str | None = None
87
- end_timestamp: str | None = None
88
-
89
- model_config = ConfigDict(populate_by_name=True, extra="allow")
90
-
91
-
92
- class Trace(BaseModel):
93
- """Unified result from agent execution (task or prompt).
94
-
95
- Fields:
96
- - done: Whether the run is complete
97
- - reward: The reward for the run
98
- - info: Additional metadata for the run
99
- - content: The final content/response from the agent
100
- - isError: Whether the execution resulted in an error
101
- - trace: The steps taken in the run (empty if not tracing)
102
- """
103
-
104
- done: bool = Field(default=True)
105
- reward: float = Field(default=0.0)
106
- info: dict[str, Any] = Field(default_factory=dict)
107
- content: str | None = Field(default=None)
108
- isError: bool = Field(default=False)
109
- trace: list[TraceStep] = Field(default_factory=list)
110
-
111
- def append(self, step: TraceStep) -> None:
112
- self.trace.append(step)
113
-
114
- def populate_from_context(self) -> None:
115
- """Populate trace steps from the current trace context if available.
116
-
117
- This checks if we're executing within a hud.trace() context and
118
- automatically populates the trace field with collected steps.
119
- """
120
- from hud.otel.context import get_current_task_run_id
121
- from hud.telemetry.replay import get_trace
122
-
123
- task_run_id = get_current_task_run_id()
124
- if task_run_id:
125
- collected_trace = get_trace(task_run_id)
126
- if collected_trace:
127
- self.trace = collected_trace.trace
128
-
129
-
130
- __all__ = [
131
- "AgentResponse",
132
- "MCPToolCall",
133
- "MCPToolResult",
134
- "Trace",
135
- "TraceStep",
136
- ]
1
+ from __future__ import annotations
2
+
3
+ import uuid
4
+ from typing import Any, Literal
5
+
6
+ from mcp.types import CallToolRequestParams, CallToolResult
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+
10
+ class MCPToolCall(CallToolRequestParams):
11
+ """A tool call."""
12
+
13
+ id: str = Field(default_factory=lambda: str(uuid.uuid4())) # Unique identifier for reference
14
+
15
+ def __str__(self) -> str:
16
+ response = f"Tool: {self.name}"
17
+ if self.arguments:
18
+ response += f"\nArguments: {self.arguments}"
19
+ return response
20
+
21
+
22
+ class MCPToolResult(CallToolResult):
23
+ """A tool result."""
24
+
25
+ def __str__(self) -> str:
26
+ response = f"Content: {self.content}"
27
+ if self.structuredContent:
28
+ response += f"\nStructured Content: {self.structuredContent}"
29
+ if self.isError:
30
+ response += f"\nError: {self.isError}"
31
+ return response
32
+
33
+
34
+ class AgentResponse(BaseModel):
35
+ """A model response in the conversation."""
36
+
37
+ # --- FUNCTIONAL ---
38
+ tool_calls: list[MCPToolCall] = Field(default_factory=list)
39
+ done: bool = Field(default=False)
40
+
41
+ # --- TELEMETRY [app.hud.so] ---
42
+ # Responses
43
+ content: str | None = Field(default=None)
44
+ reasoning: str | None = Field(default=None)
45
+ info: dict[str, Any] = Field(default_factory=dict)
46
+ isError: bool = Field(default=False)
47
+ raw: Any | None = Field(default=None) # Include raw response for access to Choice objects
48
+
49
+ # Timestamps
50
+ start_timestamp: str | None = None
51
+ end_timestamp: str | None = None
52
+
53
+ def __str__(self) -> str:
54
+ response = ""
55
+ if self.reasoning:
56
+ response += f"Reasoning: {self.reasoning}\n"
57
+ if self.content:
58
+ response += f"Content: {self.content}\n"
59
+ if self.tool_calls:
60
+ response += f"""Tool Calls: {
61
+ ", ".join([f"{tc.name}: {tc.arguments}" for tc in self.tool_calls])
62
+ }"""
63
+ if self.raw:
64
+ response += f"Raw: {self.raw}"
65
+ return response
66
+
67
+
68
+ class TraceStep(BaseModel):
69
+ """Canonical data for a single span (shared with telemetry)."""
70
+
71
+ # HUD identifiers
72
+ task_run_id: str | None = Field(default=None)
73
+ job_id: str | None = Field(default=None)
74
+
75
+ # Span category - can be any string, but "mcp" and "agent" are privileged on the platform
76
+ category: Literal["mcp", "agent"] | str = Field(default="mcp") # noqa: PYI051
77
+
78
+ # Generic I/O fields - works for any category
79
+ request: Any | None = None
80
+ result: Any | None = None
81
+
82
+ # Generic span info
83
+ type: str = Field(default="CLIENT")
84
+
85
+ # Timestamps (optional, for local tracking)
86
+ start_timestamp: str | None = None
87
+ end_timestamp: str | None = None
88
+
89
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
90
+
91
+
92
+ class Trace(BaseModel):
93
+ """Unified result from agent execution (task or prompt).
94
+
95
+ Fields:
96
+ - done: Whether the run is complete
97
+ - reward: The reward for the run
98
+ - info: Additional metadata for the run
99
+ - content: The final content/response from the agent
100
+ - isError: Whether the execution resulted in an error
101
+ - trace: The steps taken in the run (empty if not tracing)
102
+ """
103
+
104
+ done: bool = Field(default=True)
105
+ reward: float = Field(default=0.0)
106
+ info: dict[str, Any] = Field(default_factory=dict)
107
+ content: str | None = Field(default=None)
108
+ isError: bool = Field(default=False)
109
+ trace: list[TraceStep] = Field(default_factory=list)
110
+
111
+ def append(self, step: TraceStep) -> None:
112
+ self.trace.append(step)
113
+
114
+ def populate_from_context(self) -> None:
115
+ """Populate trace steps from the current trace context if available.
116
+
117
+ This checks if we're executing within a hud.trace() context and
118
+ automatically populates the trace field with collected steps.
119
+ """
120
+ from hud.otel.context import get_current_task_run_id
121
+ from hud.telemetry.replay import get_trace
122
+
123
+ task_run_id = get_current_task_run_id()
124
+ if task_run_id:
125
+ collected_trace = get_trace(task_run_id)
126
+ if collected_trace:
127
+ self.trace = collected_trace.trace
128
+
129
+
130
+ __all__ = [
131
+ "AgentResponse",
132
+ "MCPToolCall",
133
+ "MCPToolResult",
134
+ "Trace",
135
+ "TraceStep",
136
+ ]
hud/utils/__init__.py CHANGED
@@ -1,10 +1,10 @@
1
- from __future__ import annotations
2
-
3
- from .design import HUDDesign, design
4
- from .telemetry import stream
5
-
6
- __all__ = [
7
- "HUDDesign",
8
- "design",
9
- "stream",
10
- ]
1
+ from __future__ import annotations
2
+
3
+ from .design import HUDDesign, design
4
+ from .telemetry import stream
5
+
6
+ __all__ = [
7
+ "HUDDesign",
8
+ "design",
9
+ "stream",
10
+ ]
hud/utils/async_utils.py CHANGED
@@ -1,65 +1,65 @@
1
- """Async utilities for HUD SDK.
2
-
3
- This module provides utilities for running async code in various environments,
4
- including Jupyter notebooks and synchronous contexts.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import asyncio
10
- import logging
11
- import threading
12
- from typing import TYPE_CHECKING, Any
13
-
14
- if TYPE_CHECKING:
15
- from collections.abc import Coroutine
16
-
17
- logger = logging.getLogger(__name__)
18
-
19
-
20
- def fire_and_forget(coro: Coroutine[Any, Any, Any], description: str = "task") -> None:
21
- """Execute a coroutine in a fire-and-forget manner.
22
-
23
- This function handles running async code in various contexts:
24
- - When an event loop is already running (normal async context)
25
- - When no event loop exists (sync context, some Jupyter setups)
26
- - Gracefully handles interpreter shutdown
27
-
28
- Args:
29
- coro: The coroutine to execute
30
- description: Description of the task for logging (e.g., "update job status")
31
-
32
- Example:
33
- fire_and_forget(
34
- some_async_function(),
35
- description="update status"
36
- )
37
- """
38
- try:
39
- # Try to get current event loop
40
- loop = asyncio.get_running_loop()
41
- # Schedule the coroutine
42
- task = loop.create_task(coro)
43
- # Add error handler to prevent unhandled exceptions
44
- task.add_done_callback(lambda t: t.exception() if not t.cancelled() else None)
45
- except RuntimeError:
46
- # No running event loop (e.g., Jupyter without %autoawait, sync context)
47
- try:
48
- # Try to run in a thread as a fallback
49
- def run_in_thread() -> None:
50
- loop = asyncio.new_event_loop()
51
- asyncio.set_event_loop(loop)
52
- try:
53
- loop.run_until_complete(coro)
54
- except Exception as e:
55
- # Suppress warnings about interpreter shutdown
56
- if "interpreter shutdown" not in str(e):
57
- logger.debug("Error in threaded %s: %s", description, e)
58
-
59
- thread = threading.Thread(target=run_in_thread, daemon=True)
60
- thread.start()
61
- except Exception as e:
62
- # If that fails too, just log and continue
63
- # Special case: suppress "cannot schedule new futures after interpreter shutdown"
64
- if "interpreter shutdown" not in str(e):
65
- logger.debug("Could not %s - no event loop available: %s", description, e)
1
+ """Async utilities for HUD SDK.
2
+
3
+ This module provides utilities for running async code in various environments,
4
+ including Jupyter notebooks and synchronous contexts.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ import logging
11
+ import threading
12
+ from typing import TYPE_CHECKING, Any
13
+
14
+ if TYPE_CHECKING:
15
+ from collections.abc import Coroutine
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ def fire_and_forget(coro: Coroutine[Any, Any, Any], description: str = "task") -> None:
21
+ """Execute a coroutine in a fire-and-forget manner.
22
+
23
+ This function handles running async code in various contexts:
24
+ - When an event loop is already running (normal async context)
25
+ - When no event loop exists (sync context, some Jupyter setups)
26
+ - Gracefully handles interpreter shutdown
27
+
28
+ Args:
29
+ coro: The coroutine to execute
30
+ description: Description of the task for logging (e.g., "update job status")
31
+
32
+ Example:
33
+ fire_and_forget(
34
+ some_async_function(),
35
+ description="update status"
36
+ )
37
+ """
38
+ try:
39
+ # Try to get current event loop
40
+ loop = asyncio.get_running_loop()
41
+ # Schedule the coroutine
42
+ task = loop.create_task(coro)
43
+ # Add error handler to prevent unhandled exceptions
44
+ task.add_done_callback(lambda t: t.exception() if not t.cancelled() else None)
45
+ except RuntimeError:
46
+ # No running event loop (e.g., Jupyter without %autoawait, sync context)
47
+ try:
48
+ # Try to run in a thread as a fallback
49
+ def run_in_thread() -> None:
50
+ loop = asyncio.new_event_loop()
51
+ asyncio.set_event_loop(loop)
52
+ try:
53
+ loop.run_until_complete(coro)
54
+ except Exception as e:
55
+ # Suppress warnings about interpreter shutdown
56
+ if "interpreter shutdown" not in str(e):
57
+ logger.debug("Error in threaded %s: %s", description, e)
58
+
59
+ thread = threading.Thread(target=run_in_thread, daemon=True)
60
+ thread.start()
61
+ except Exception as e:
62
+ # If that fails too, just log and continue
63
+ # Special case: suppress "cannot schedule new futures after interpreter shutdown"
64
+ if "interpreter shutdown" not in str(e):
65
+ logger.debug("Could not %s - no event loop available: %s", description, e)