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
@@ -1,97 +1,97 @@
1
- """MCP instrumentation support for HUD.
2
-
3
- This module provides functions to enable MCP OpenTelemetry instrumentation
4
- for automatic tracing of MCP protocol communication.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import logging
10
- from typing import TYPE_CHECKING, Any
11
-
12
- if TYPE_CHECKING:
13
- from collections.abc import AsyncGenerator, Callable
14
-
15
- from opentelemetry.trace import TracerProvider
16
-
17
- logger = logging.getLogger(__name__)
18
-
19
- LIFECYCLE_TOOLS = {"setup", "evaluate"}
20
-
21
-
22
- def install_mcp_instrumentation(provider: TracerProvider) -> None:
23
- """Enable community MCP OpenTelemetry instrumentation if present.
24
-
25
- Args:
26
- provider: The TracerProvider to use for instrumentation
27
- """
28
- import logging
29
-
30
- logger = logging.getLogger(__name__)
31
-
32
- try:
33
- from opentelemetry.instrumentation.mcp.instrumentation import (
34
- McpInstrumentor,
35
- )
36
-
37
- # First, patch the instrumentation to handle 3-value transports correctly
38
- _patch_mcp_instrumentation()
39
-
40
- McpInstrumentor().instrument(tracer_provider=provider)
41
- logger.debug("MCP instrumentation installed with fastmcp compatibility patch")
42
- except ImportError:
43
- logger.debug("opentelemetry-instrumentation-mcp not available, skipping")
44
- except Exception as exc:
45
- logger.warning("Failed to install MCP instrumentation: %s", exc)
46
-
47
-
48
- def _patch_mcp_instrumentation() -> None:
49
- """Patch MCP instrumentation to handle 3-value transport yields correctly."""
50
- from contextlib import asynccontextmanager
51
-
52
- try:
53
- from opentelemetry.instrumentation.mcp.instrumentation import McpInstrumentor
54
-
55
- def patched_transport_wrapper(self: Any, tracer: Any) -> Callable[..., Any]:
56
- @asynccontextmanager
57
- async def traced_method(
58
- wrapped: Callable[..., Any], instance: Any, args: Any, kwargs: Any
59
- ) -> AsyncGenerator[Any, None]:
60
- async with wrapped(*args, **kwargs) as result:
61
- # Check if we got a tuple with 3 values
62
- if isinstance(result, tuple) and len(result) == 3:
63
- read_stream, write_stream, third_value = result
64
- # Import here to avoid circular imports
65
- from opentelemetry.instrumentation.mcp.instrumentation import (
66
- InstrumentedStreamReader,
67
- InstrumentedStreamWriter,
68
- )
69
-
70
- yield (
71
- InstrumentedStreamReader(read_stream, tracer),
72
- InstrumentedStreamWriter(write_stream, tracer),
73
- third_value,
74
- )
75
- else:
76
- # Fall back to 2-value case
77
- read_stream, write_stream = result
78
- from opentelemetry.instrumentation.mcp.instrumentation import (
79
- InstrumentedStreamReader,
80
- InstrumentedStreamWriter,
81
- )
82
-
83
- yield (
84
- InstrumentedStreamReader(read_stream, tracer),
85
- InstrumentedStreamWriter(write_stream, tracer),
86
- )
87
-
88
- return traced_method
89
-
90
- # Apply the patch
91
- McpInstrumentor._transport_wrapper = patched_transport_wrapper
92
-
93
- except Exception as e:
94
- import logging
95
-
96
- logger = logging.getLogger(__name__)
97
- logger.warning("Failed to patch MCP instrumentation: %s", e)
1
+ """MCP instrumentation support for HUD.
2
+
3
+ This module provides functions to enable MCP OpenTelemetry instrumentation
4
+ for automatic tracing of MCP protocol communication.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ from typing import TYPE_CHECKING, Any
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import AsyncGenerator, Callable
14
+
15
+ from opentelemetry.trace import TracerProvider
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ LIFECYCLE_TOOLS = {"setup", "evaluate"}
20
+
21
+
22
+ def install_mcp_instrumentation(provider: TracerProvider) -> None:
23
+ """Enable community MCP OpenTelemetry instrumentation if present.
24
+
25
+ Args:
26
+ provider: The TracerProvider to use for instrumentation
27
+ """
28
+ import logging
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+ try:
33
+ from opentelemetry.instrumentation.mcp.instrumentation import (
34
+ McpInstrumentor,
35
+ )
36
+
37
+ # First, patch the instrumentation to handle 3-value transports correctly
38
+ _patch_mcp_instrumentation()
39
+
40
+ McpInstrumentor().instrument(tracer_provider=provider)
41
+ logger.debug("MCP instrumentation installed with fastmcp compatibility patch")
42
+ except ImportError:
43
+ logger.debug("opentelemetry-instrumentation-mcp not available, skipping")
44
+ except Exception as exc:
45
+ logger.warning("Failed to install MCP instrumentation: %s", exc)
46
+
47
+
48
+ def _patch_mcp_instrumentation() -> None:
49
+ """Patch MCP instrumentation to handle 3-value transport yields correctly."""
50
+ from contextlib import asynccontextmanager
51
+
52
+ try:
53
+ from opentelemetry.instrumentation.mcp.instrumentation import McpInstrumentor
54
+
55
+ def patched_transport_wrapper(self: Any, tracer: Any) -> Callable[..., Any]:
56
+ @asynccontextmanager
57
+ async def traced_method(
58
+ wrapped: Callable[..., Any], instance: Any, args: Any, kwargs: Any
59
+ ) -> AsyncGenerator[Any, None]:
60
+ async with wrapped(*args, **kwargs) as result:
61
+ # Check if we got a tuple with 3 values
62
+ if isinstance(result, tuple) and len(result) == 3:
63
+ read_stream, write_stream, third_value = result
64
+ # Import here to avoid circular imports
65
+ from opentelemetry.instrumentation.mcp.instrumentation import (
66
+ InstrumentedStreamReader,
67
+ InstrumentedStreamWriter,
68
+ )
69
+
70
+ yield (
71
+ InstrumentedStreamReader(read_stream, tracer),
72
+ InstrumentedStreamWriter(write_stream, tracer),
73
+ third_value,
74
+ )
75
+ else:
76
+ # Fall back to 2-value case
77
+ read_stream, write_stream = result
78
+ from opentelemetry.instrumentation.mcp.instrumentation import (
79
+ InstrumentedStreamReader,
80
+ InstrumentedStreamWriter,
81
+ )
82
+
83
+ yield (
84
+ InstrumentedStreamReader(read_stream, tracer),
85
+ InstrumentedStreamWriter(write_stream, tracer),
86
+ )
87
+
88
+ return traced_method
89
+
90
+ # Apply the patch
91
+ McpInstrumentor._transport_wrapper = patched_transport_wrapper
92
+
93
+ except Exception as e:
94
+ import logging
95
+
96
+ logger = logging.getLogger(__name__)
97
+ logger.warning("Failed to patch MCP instrumentation: %s", e)
hud/otel/processors.py CHANGED
@@ -1,118 +1,118 @@
1
- from __future__ import annotations
2
-
3
- import logging
4
- from typing import Any
5
-
6
- from opentelemetry import baggage
7
- from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
8
-
9
- from .context import (
10
- get_agent_steps,
11
- get_base_mcp_steps,
12
- get_mcp_tool_steps,
13
- increment_agent_steps,
14
- increment_base_mcp_steps,
15
- increment_mcp_tool_steps,
16
- )
17
-
18
- logger = logging.getLogger(__name__)
19
-
20
-
21
- class HudEnrichmentProcessor(SpanProcessor):
22
- """Span processor that enriches every span with HUD-specific context.
23
-
24
- • Adds ``hud.task_run_id`` attribute if available.
25
- • Adds ``hud.job_id`` attribute if available in baggage.
26
- • Adds ``hud.step_count`` attribute if available in baggage.
27
- """
28
-
29
- def __init__(self) -> None:
30
- # No state, everything comes from context vars
31
- super().__init__()
32
-
33
- # --- callback hooks -------------------------------------------------
34
- def on_start(self, span: Span, parent_context: Any) -> None: # type: ignore[override]
35
- try:
36
- # Get task_run_id from baggage in parent context
37
- run_id = baggage.get_baggage("hud.task_run_id", context=parent_context)
38
- if run_id and span.is_recording():
39
- span.set_attribute("hud.task_run_id", str(run_id))
40
-
41
- # Get job_id from baggage if available
42
- job_id = baggage.get_baggage("hud.job_id", context=parent_context)
43
- if job_id and span.is_recording():
44
- span.set_attribute("hud.job_id", str(job_id))
45
-
46
- # Check what type of step this is and increment appropriate counters
47
- if span.is_recording():
48
- step_type = self._get_step_type(span)
49
-
50
- if step_type == "agent":
51
- # Increment agent steps
52
- new_agent_count = increment_agent_steps()
53
- span.set_attribute("hud.agent_steps", new_agent_count)
54
- logger.debug("Incremented agent steps to %d", new_agent_count)
55
-
56
- elif step_type == "base_mcp":
57
- # Increment base MCP steps
58
- new_base_count = increment_base_mcp_steps()
59
- span.set_attribute("hud.base_mcp_steps", new_base_count)
60
- logger.debug("Incremented base MCP steps to %d", new_base_count)
61
-
62
- elif step_type == "mcp_tool":
63
- # Increment both base MCP and MCP tool steps
64
- new_base_count = increment_base_mcp_steps()
65
- new_tool_count = increment_mcp_tool_steps()
66
- span.set_attribute("hud.base_mcp_steps", new_base_count)
67
- span.set_attribute("hud.mcp_tool_steps", new_tool_count)
68
- logger.debug(
69
- "Incremented MCP steps to base=%d, tool=%d", new_base_count, new_tool_count
70
- )
71
-
72
- # Always set all current step counts on the span
73
- span.set_attribute("hud.base_mcp_steps", get_base_mcp_steps())
74
- span.set_attribute("hud.mcp_tool_steps", get_mcp_tool_steps())
75
- span.set_attribute("hud.agent_steps", get_agent_steps())
76
-
77
- except Exception as exc: # defensive; never fail the tracer
78
- logger.debug("HudEnrichmentProcessor.on_start error: %s", exc, exc_info=False)
79
-
80
- def _get_step_type(self, span: Span) -> str | None:
81
- """Determine what type of step this span represents.
82
-
83
- Returns:
84
- 'base_mcp' for any MCP span
85
- 'mcp_tool' for MCP tool calls (tools/call.mcp)
86
- 'agent' for agent spans
87
- None if not a step
88
- """
89
- # Check span attributes
90
- attrs = span.attributes or {}
91
- span_name = span.name
92
-
93
- # Check for agent steps (instrumented with span_type="agent")
94
- if attrs.get("category") == "agent":
95
- return "agent"
96
-
97
- # Check span name pattern for MCP calls
98
- if span_name:
99
- # tools/call.mcp is an mcp_tool step
100
- if span_name == "tools/call.mcp":
101
- return "mcp_tool"
102
-
103
- # Any other .mcp suffixed span is a base MCP step
104
- elif span_name.endswith(".mcp"):
105
- return "base_mcp"
106
-
107
- return None
108
-
109
- def on_end(self, span: ReadableSpan) -> None:
110
- # Nothing to do enrichment is on_start only
111
- pass
112
-
113
- # Required to fully implement abstract base, but we don't batch spans
114
- def shutdown(self) -> None: # type: ignore[override]
115
- pass
116
-
117
- def force_flush(self, timeout_millis: int | None = None) -> bool: # type: ignore[override]
118
- return True
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import Any
5
+
6
+ from opentelemetry import baggage
7
+ from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
8
+
9
+ from .context import (
10
+ get_agent_steps,
11
+ get_base_mcp_steps,
12
+ get_mcp_tool_steps,
13
+ increment_agent_steps,
14
+ increment_base_mcp_steps,
15
+ increment_mcp_tool_steps,
16
+ )
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class HudEnrichmentProcessor(SpanProcessor):
22
+ """Span processor that enriches every span with HUD-specific context.
23
+
24
+ • Adds ``hud.task_run_id`` attribute if available.
25
+ • Adds ``hud.job_id`` attribute if available in baggage.
26
+ • Adds ``hud.step_count`` attribute if available in baggage.
27
+ """
28
+
29
+ def __init__(self) -> None:
30
+ # No state, everything comes from context vars
31
+ super().__init__()
32
+
33
+ # --- callback hooks -------------------------------------------------
34
+ def on_start(self, span: Span, parent_context: Any) -> None: # type: ignore[override]
35
+ try:
36
+ # Get task_run_id from baggage in parent context
37
+ run_id = baggage.get_baggage("hud.task_run_id", context=parent_context)
38
+ if run_id and span.is_recording():
39
+ span.set_attribute("hud.task_run_id", str(run_id))
40
+
41
+ # Get job_id from baggage if available
42
+ job_id = baggage.get_baggage("hud.job_id", context=parent_context)
43
+ if job_id and span.is_recording():
44
+ span.set_attribute("hud.job_id", str(job_id))
45
+
46
+ # Check what type of step this is and increment appropriate counters
47
+ if span.is_recording():
48
+ step_type = self._get_step_type(span)
49
+
50
+ if step_type == "agent":
51
+ # Increment agent steps
52
+ new_agent_count = increment_agent_steps()
53
+ span.set_attribute("hud.agent_steps", new_agent_count)
54
+ logger.debug("Incremented agent steps to %d", new_agent_count)
55
+
56
+ elif step_type == "base_mcp":
57
+ # Increment base MCP steps
58
+ new_base_count = increment_base_mcp_steps()
59
+ span.set_attribute("hud.base_mcp_steps", new_base_count)
60
+ logger.debug("Incremented base MCP steps to %d", new_base_count)
61
+
62
+ elif step_type == "mcp_tool":
63
+ # Increment both base MCP and MCP tool steps
64
+ new_base_count = increment_base_mcp_steps()
65
+ new_tool_count = increment_mcp_tool_steps()
66
+ span.set_attribute("hud.base_mcp_steps", new_base_count)
67
+ span.set_attribute("hud.mcp_tool_steps", new_tool_count)
68
+ logger.debug(
69
+ "Incremented MCP steps to base=%d, tool=%d", new_base_count, new_tool_count
70
+ )
71
+
72
+ # Always set all current step counts on the span
73
+ span.set_attribute("hud.base_mcp_steps", get_base_mcp_steps())
74
+ span.set_attribute("hud.mcp_tool_steps", get_mcp_tool_steps())
75
+ span.set_attribute("hud.agent_steps", get_agent_steps())
76
+
77
+ except Exception as exc: # defensive; never fail the tracer
78
+ logger.debug("HudEnrichmentProcessor.on_start error: %s", exc, exc_info=False)
79
+
80
+ def _get_step_type(self, span: Span) -> str | None:
81
+ """Determine what type of step this span represents.
82
+
83
+ Returns:
84
+ 'base_mcp' for any MCP span
85
+ 'mcp_tool' for MCP tool calls (tools/call.mcp)
86
+ 'agent' for agent spans
87
+ None if not a step
88
+ """
89
+ # Check span attributes
90
+ attrs = span.attributes or {}
91
+ span_name = span.name
92
+
93
+ # Check for agent steps (instrumented with span_type="agent")
94
+ if attrs.get("category") == "agent":
95
+ return "agent"
96
+
97
+ # Check span name pattern for MCP calls
98
+ if span_name:
99
+ # tools/call.mcp is an mcp_tool step
100
+ if span_name == "tools/call.mcp":
101
+ return "mcp_tool"
102
+
103
+ # Any other .mcp suffixed span is a base MCP step
104
+ elif span_name.endswith(".mcp"):
105
+ return "base_mcp"
106
+
107
+ return None
108
+
109
+ def on_end(self, span: ReadableSpan) -> None:
110
+ # Nothing to do enrichment is on_start only
111
+ pass
112
+
113
+ # Required to fully implement abstract base, but we don't batch spans
114
+ def shutdown(self) -> None: # type: ignore[override]
115
+ pass
116
+
117
+ def force_flush(self, timeout_millis: int | None = None) -> bool: # type: ignore[override]
118
+ return True
@@ -1 +1 @@
1
- """Tests for OpenTelemetry integration."""
1
+ """Tests for OpenTelemetry integration."""