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.
- hud/__init__.py +22 -22
- hud/agents/__init__.py +13 -15
- hud/agents/base.py +599 -599
- hud/agents/claude.py +373 -373
- hud/agents/langchain.py +261 -250
- hud/agents/misc/__init__.py +7 -7
- hud/agents/misc/response_agent.py +82 -80
- hud/agents/openai.py +352 -352
- hud/agents/openai_chat_generic.py +154 -154
- hud/agents/tests/__init__.py +1 -1
- hud/agents/tests/test_base.py +742 -742
- hud/agents/tests/test_claude.py +324 -324
- hud/agents/tests/test_client.py +363 -363
- hud/agents/tests/test_openai.py +237 -237
- hud/cli/__init__.py +617 -617
- hud/cli/__main__.py +8 -8
- hud/cli/analyze.py +371 -371
- hud/cli/analyze_metadata.py +230 -230
- hud/cli/build.py +498 -427
- hud/cli/clone.py +185 -185
- hud/cli/cursor.py +92 -92
- hud/cli/debug.py +392 -392
- hud/cli/docker_utils.py +83 -83
- hud/cli/init.py +280 -281
- hud/cli/interactive.py +353 -353
- hud/cli/mcp_server.py +764 -756
- hud/cli/pull.py +330 -336
- hud/cli/push.py +404 -370
- hud/cli/remote_runner.py +311 -311
- hud/cli/runner.py +160 -160
- hud/cli/tests/__init__.py +3 -3
- hud/cli/tests/test_analyze.py +284 -284
- hud/cli/tests/test_cli_init.py +265 -265
- hud/cli/tests/test_cli_main.py +27 -27
- hud/cli/tests/test_clone.py +142 -142
- hud/cli/tests/test_cursor.py +253 -253
- hud/cli/tests/test_debug.py +453 -453
- hud/cli/tests/test_mcp_server.py +139 -139
- hud/cli/tests/test_utils.py +388 -388
- hud/cli/utils.py +263 -263
- hud/clients/README.md +143 -143
- hud/clients/__init__.py +16 -16
- hud/clients/base.py +378 -379
- hud/clients/fastmcp.py +222 -222
- hud/clients/mcp_use.py +298 -278
- hud/clients/tests/__init__.py +1 -1
- hud/clients/tests/test_client_integration.py +111 -111
- hud/clients/tests/test_fastmcp.py +342 -342
- hud/clients/tests/test_protocol.py +188 -188
- hud/clients/utils/__init__.py +1 -1
- hud/clients/utils/retry_transport.py +160 -160
- hud/datasets.py +327 -322
- hud/misc/__init__.py +1 -1
- hud/misc/claude_plays_pokemon.py +292 -292
- hud/otel/__init__.py +35 -35
- hud/otel/collector.py +142 -142
- hud/otel/config.py +164 -164
- hud/otel/context.py +536 -536
- hud/otel/exporters.py +366 -366
- hud/otel/instrumentation.py +97 -97
- hud/otel/processors.py +118 -118
- hud/otel/tests/__init__.py +1 -1
- hud/otel/tests/test_processors.py +197 -197
- hud/server/__init__.py +5 -5
- hud/server/context.py +114 -114
- hud/server/helper/__init__.py +5 -5
- hud/server/low_level.py +132 -132
- hud/server/server.py +170 -166
- hud/server/tests/__init__.py +3 -3
- hud/settings.py +73 -73
- hud/shared/__init__.py +5 -5
- hud/shared/exceptions.py +180 -180
- hud/shared/requests.py +264 -264
- hud/shared/tests/test_exceptions.py +157 -157
- hud/shared/tests/test_requests.py +275 -275
- hud/telemetry/__init__.py +25 -25
- hud/telemetry/instrument.py +379 -379
- hud/telemetry/job.py +309 -309
- hud/telemetry/replay.py +74 -74
- hud/telemetry/trace.py +83 -83
- hud/tools/__init__.py +33 -33
- hud/tools/base.py +365 -365
- hud/tools/bash.py +161 -161
- hud/tools/computer/__init__.py +15 -15
- hud/tools/computer/anthropic.py +437 -437
- hud/tools/computer/hud.py +376 -376
- hud/tools/computer/openai.py +295 -295
- hud/tools/computer/settings.py +82 -82
- hud/tools/edit.py +314 -314
- hud/tools/executors/__init__.py +30 -30
- hud/tools/executors/base.py +539 -539
- hud/tools/executors/pyautogui.py +621 -621
- hud/tools/executors/tests/__init__.py +1 -1
- hud/tools/executors/tests/test_base_executor.py +338 -338
- hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
- hud/tools/executors/xdo.py +511 -511
- hud/tools/playwright.py +412 -412
- hud/tools/tests/__init__.py +3 -3
- hud/tools/tests/test_base.py +282 -282
- hud/tools/tests/test_bash.py +158 -158
- hud/tools/tests/test_bash_extended.py +197 -197
- hud/tools/tests/test_computer.py +425 -425
- hud/tools/tests/test_computer_actions.py +34 -34
- hud/tools/tests/test_edit.py +259 -259
- hud/tools/tests/test_init.py +27 -27
- hud/tools/tests/test_playwright_tool.py +183 -183
- hud/tools/tests/test_tools.py +145 -145
- hud/tools/tests/test_utils.py +156 -156
- hud/tools/types.py +72 -72
- hud/tools/utils.py +50 -50
- hud/types.py +136 -136
- hud/utils/__init__.py +10 -10
- hud/utils/async_utils.py +65 -65
- hud/utils/design.py +236 -168
- hud/utils/mcp.py +55 -55
- hud/utils/progress.py +149 -149
- hud/utils/telemetry.py +66 -66
- hud/utils/tests/test_async_utils.py +173 -173
- hud/utils/tests/test_init.py +17 -17
- hud/utils/tests/test_progress.py +261 -261
- hud/utils/tests/test_telemetry.py +82 -82
- hud/utils/tests/test_version.py +8 -8
- hud/version.py +7 -7
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/METADATA +10 -8
- hud_python-0.4.3.dist-info/RECORD +131 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/licenses/LICENSE +21 -21
- hud/agents/art.py +0 -101
- hud_python-0.4.1.dist-info/RECORD +0 -132
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/WHEEL +0 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/entry_points.txt +0 -0
|
@@ -1,197 +1,197 @@
|
|
|
1
|
-
"""Tests for OpenTelemetry processors."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from unittest.mock import MagicMock, patch
|
|
6
|
-
|
|
7
|
-
from hud.otel.processors import HudEnrichmentProcessor
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class TestHudEnrichmentProcessor:
|
|
11
|
-
"""Test HudEnrichmentProcessor."""
|
|
12
|
-
|
|
13
|
-
def test_on_start_with_run_id(self):
|
|
14
|
-
"""Test on_start with current task run ID."""
|
|
15
|
-
|
|
16
|
-
processor = HudEnrichmentProcessor()
|
|
17
|
-
|
|
18
|
-
# Mock span
|
|
19
|
-
span = MagicMock()
|
|
20
|
-
span.set_attribute = MagicMock()
|
|
21
|
-
span.is_recording.return_value = True
|
|
22
|
-
|
|
23
|
-
# Mock baggage to return run ID
|
|
24
|
-
parent_context = {}
|
|
25
|
-
with patch("hud.otel.processors.baggage.get_baggage") as mock_get_baggage:
|
|
26
|
-
# Return run ID for task_run_id, None for job_id
|
|
27
|
-
mock_get_baggage.side_effect = (
|
|
28
|
-
lambda key, context: "test-run-123" if key == "hud.task_run_id" else None
|
|
29
|
-
)
|
|
30
|
-
processor.on_start(span, parent_context)
|
|
31
|
-
|
|
32
|
-
# Verify attribute was set
|
|
33
|
-
span.set_attribute.assert_called_with("hud.task_run_id", "test-run-123")
|
|
34
|
-
|
|
35
|
-
def test_on_start_no_run_id(self):
|
|
36
|
-
"""Test on_start without current task run ID."""
|
|
37
|
-
|
|
38
|
-
processor = HudEnrichmentProcessor()
|
|
39
|
-
|
|
40
|
-
# Mock span
|
|
41
|
-
span = MagicMock()
|
|
42
|
-
span.set_attribute = MagicMock()
|
|
43
|
-
span.is_recording.return_value = True
|
|
44
|
-
span.name = "test_span"
|
|
45
|
-
|
|
46
|
-
# Set up attributes to return None (not matching any step type)
|
|
47
|
-
span.attributes = {}
|
|
48
|
-
|
|
49
|
-
# Mock baggage to return None
|
|
50
|
-
parent_context = {}
|
|
51
|
-
with patch("hud.otel.processors.baggage.get_baggage", return_value=None):
|
|
52
|
-
processor.on_start(span, parent_context)
|
|
53
|
-
|
|
54
|
-
# Verify only step count attributes were set (no run_id or job_id)
|
|
55
|
-
calls = span.set_attribute.call_args_list
|
|
56
|
-
set_attrs = {call[0][0] for call in calls}
|
|
57
|
-
|
|
58
|
-
# Should have step counts but not run_id/job_id
|
|
59
|
-
assert "hud.task_run_id" not in set_attrs
|
|
60
|
-
assert "hud.job_id" not in set_attrs
|
|
61
|
-
assert "hud.base_mcp_steps" in set_attrs
|
|
62
|
-
assert "hud.mcp_tool_steps" in set_attrs
|
|
63
|
-
assert "hud.agent_steps" in set_attrs
|
|
64
|
-
|
|
65
|
-
def test_on_end(self):
|
|
66
|
-
"""Test on_end does nothing."""
|
|
67
|
-
|
|
68
|
-
processor = HudEnrichmentProcessor()
|
|
69
|
-
span = MagicMock()
|
|
70
|
-
|
|
71
|
-
# Should not raise
|
|
72
|
-
processor.on_end(span)
|
|
73
|
-
|
|
74
|
-
def test_shutdown(self):
|
|
75
|
-
"""Test shutdown does nothing."""
|
|
76
|
-
|
|
77
|
-
processor = HudEnrichmentProcessor()
|
|
78
|
-
|
|
79
|
-
# Should not raise
|
|
80
|
-
processor.shutdown()
|
|
81
|
-
|
|
82
|
-
def test_force_flush(self):
|
|
83
|
-
"""Test force_flush returns True."""
|
|
84
|
-
|
|
85
|
-
processor = HudEnrichmentProcessor()
|
|
86
|
-
|
|
87
|
-
# Should return True
|
|
88
|
-
result = processor.force_flush()
|
|
89
|
-
assert result is True
|
|
90
|
-
|
|
91
|
-
def test_on_start_with_job_id(self):
|
|
92
|
-
"""Test on_start with job ID in baggage."""
|
|
93
|
-
|
|
94
|
-
processor = HudEnrichmentProcessor()
|
|
95
|
-
|
|
96
|
-
# Mock span
|
|
97
|
-
span = MagicMock()
|
|
98
|
-
span.set_attribute = MagicMock()
|
|
99
|
-
span.is_recording.return_value = True
|
|
100
|
-
|
|
101
|
-
# Mock baggage with job ID
|
|
102
|
-
parent_context = {}
|
|
103
|
-
with patch("hud.otel.processors.baggage.get_baggage") as mock_get_baggage:
|
|
104
|
-
# Return None for task_run_id, job-123 for job_id
|
|
105
|
-
mock_get_baggage.side_effect = (
|
|
106
|
-
lambda key, context: "job-123" if key == "hud.job_id" else None
|
|
107
|
-
)
|
|
108
|
-
processor.on_start(span, parent_context)
|
|
109
|
-
|
|
110
|
-
# Verify job ID attribute was set
|
|
111
|
-
span.set_attribute.assert_called_with("hud.job_id", "job-123")
|
|
112
|
-
|
|
113
|
-
def test_on_start_exception_handling(self):
|
|
114
|
-
"""Test on_start handles exceptions gracefully."""
|
|
115
|
-
|
|
116
|
-
processor = HudEnrichmentProcessor()
|
|
117
|
-
|
|
118
|
-
# Mock span that raises exception
|
|
119
|
-
span = MagicMock()
|
|
120
|
-
span.is_recording.side_effect = Exception("Test error")
|
|
121
|
-
|
|
122
|
-
# Should not raise
|
|
123
|
-
processor.on_start(span, parent_context=None)
|
|
124
|
-
|
|
125
|
-
def test_on_start_exception_handling_extended(self):
|
|
126
|
-
"""Test that exceptions in on_start are caught and logged."""
|
|
127
|
-
from hud.otel.processors import HudEnrichmentProcessor
|
|
128
|
-
|
|
129
|
-
processor = HudEnrichmentProcessor()
|
|
130
|
-
|
|
131
|
-
# Create a mock span that raises when setting attributes
|
|
132
|
-
mock_span = MagicMock()
|
|
133
|
-
mock_span.is_recording.return_value = True
|
|
134
|
-
mock_span.set_attribute.side_effect = RuntimeError("Attribute error")
|
|
135
|
-
|
|
136
|
-
parent_context = {}
|
|
137
|
-
|
|
138
|
-
# Patch logger and baggage to force an exception when setting attribute
|
|
139
|
-
with (
|
|
140
|
-
patch("hud.otel.processors.logger") as mock_logger,
|
|
141
|
-
patch("hud.otel.processors.baggage.get_baggage", return_value="test-id"),
|
|
142
|
-
):
|
|
143
|
-
# Should not raise, exception should be caught
|
|
144
|
-
processor.on_start(mock_span, parent_context)
|
|
145
|
-
|
|
146
|
-
# Verify logger.debug was called with the exception
|
|
147
|
-
mock_logger.debug.assert_called_once()
|
|
148
|
-
args = mock_logger.debug.call_args[0]
|
|
149
|
-
assert "HudEnrichmentProcessor.on_start error" in args[0]
|
|
150
|
-
assert "Attribute error" in str(args[1])
|
|
151
|
-
|
|
152
|
-
def test_on_start_with_baggage_get_exception(self):
|
|
153
|
-
"""Test exception handling when baggage.get_baggage fails for task_run_id."""
|
|
154
|
-
processor = HudEnrichmentProcessor()
|
|
155
|
-
|
|
156
|
-
mock_span = MagicMock()
|
|
157
|
-
mock_span.is_recording.return_value = True
|
|
158
|
-
|
|
159
|
-
parent_context = {}
|
|
160
|
-
|
|
161
|
-
# Make baggage.get_baggage raise an exception for task_run_id
|
|
162
|
-
with (
|
|
163
|
-
patch(
|
|
164
|
-
"hud.otel.processors.baggage.get_baggage",
|
|
165
|
-
side_effect=ValueError("Context error"),
|
|
166
|
-
),
|
|
167
|
-
patch("hud.otel.processors.logger") as mock_logger,
|
|
168
|
-
):
|
|
169
|
-
# Should not raise
|
|
170
|
-
processor.on_start(mock_span, parent_context)
|
|
171
|
-
|
|
172
|
-
# Verify logger.debug was called
|
|
173
|
-
mock_logger.debug.assert_called_once()
|
|
174
|
-
args = mock_logger.debug.call_args[0]
|
|
175
|
-
assert "Context error" in str(args[1])
|
|
176
|
-
|
|
177
|
-
def test_on_start_with_baggage_exception(self):
|
|
178
|
-
"""Test exception handling when baggage.get_baggage fails."""
|
|
179
|
-
processor = HudEnrichmentProcessor()
|
|
180
|
-
|
|
181
|
-
mock_span = MagicMock()
|
|
182
|
-
mock_span.is_recording.return_value = True
|
|
183
|
-
|
|
184
|
-
parent_context = {}
|
|
185
|
-
|
|
186
|
-
# Make baggage.get_baggage raise an exception
|
|
187
|
-
with (
|
|
188
|
-
patch("hud.otel.processors.baggage.get_baggage", side_effect=KeyError("Baggage error")),
|
|
189
|
-
patch("hud.otel.processors.logger") as mock_logger,
|
|
190
|
-
):
|
|
191
|
-
# Should not raise
|
|
192
|
-
processor.on_start(mock_span, parent_context)
|
|
193
|
-
|
|
194
|
-
# Verify logger.debug was called
|
|
195
|
-
mock_logger.debug.assert_called_once()
|
|
196
|
-
args = mock_logger.debug.call_args[0]
|
|
197
|
-
assert "Baggage error" in str(args[1])
|
|
1
|
+
"""Tests for OpenTelemetry processors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from unittest.mock import MagicMock, patch
|
|
6
|
+
|
|
7
|
+
from hud.otel.processors import HudEnrichmentProcessor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestHudEnrichmentProcessor:
|
|
11
|
+
"""Test HudEnrichmentProcessor."""
|
|
12
|
+
|
|
13
|
+
def test_on_start_with_run_id(self):
|
|
14
|
+
"""Test on_start with current task run ID."""
|
|
15
|
+
|
|
16
|
+
processor = HudEnrichmentProcessor()
|
|
17
|
+
|
|
18
|
+
# Mock span
|
|
19
|
+
span = MagicMock()
|
|
20
|
+
span.set_attribute = MagicMock()
|
|
21
|
+
span.is_recording.return_value = True
|
|
22
|
+
|
|
23
|
+
# Mock baggage to return run ID
|
|
24
|
+
parent_context = {}
|
|
25
|
+
with patch("hud.otel.processors.baggage.get_baggage") as mock_get_baggage:
|
|
26
|
+
# Return run ID for task_run_id, None for job_id
|
|
27
|
+
mock_get_baggage.side_effect = (
|
|
28
|
+
lambda key, context: "test-run-123" if key == "hud.task_run_id" else None
|
|
29
|
+
)
|
|
30
|
+
processor.on_start(span, parent_context)
|
|
31
|
+
|
|
32
|
+
# Verify attribute was set
|
|
33
|
+
span.set_attribute.assert_called_with("hud.task_run_id", "test-run-123")
|
|
34
|
+
|
|
35
|
+
def test_on_start_no_run_id(self):
|
|
36
|
+
"""Test on_start without current task run ID."""
|
|
37
|
+
|
|
38
|
+
processor = HudEnrichmentProcessor()
|
|
39
|
+
|
|
40
|
+
# Mock span
|
|
41
|
+
span = MagicMock()
|
|
42
|
+
span.set_attribute = MagicMock()
|
|
43
|
+
span.is_recording.return_value = True
|
|
44
|
+
span.name = "test_span"
|
|
45
|
+
|
|
46
|
+
# Set up attributes to return None (not matching any step type)
|
|
47
|
+
span.attributes = {}
|
|
48
|
+
|
|
49
|
+
# Mock baggage to return None
|
|
50
|
+
parent_context = {}
|
|
51
|
+
with patch("hud.otel.processors.baggage.get_baggage", return_value=None):
|
|
52
|
+
processor.on_start(span, parent_context)
|
|
53
|
+
|
|
54
|
+
# Verify only step count attributes were set (no run_id or job_id)
|
|
55
|
+
calls = span.set_attribute.call_args_list
|
|
56
|
+
set_attrs = {call[0][0] for call in calls}
|
|
57
|
+
|
|
58
|
+
# Should have step counts but not run_id/job_id
|
|
59
|
+
assert "hud.task_run_id" not in set_attrs
|
|
60
|
+
assert "hud.job_id" not in set_attrs
|
|
61
|
+
assert "hud.base_mcp_steps" in set_attrs
|
|
62
|
+
assert "hud.mcp_tool_steps" in set_attrs
|
|
63
|
+
assert "hud.agent_steps" in set_attrs
|
|
64
|
+
|
|
65
|
+
def test_on_end(self):
|
|
66
|
+
"""Test on_end does nothing."""
|
|
67
|
+
|
|
68
|
+
processor = HudEnrichmentProcessor()
|
|
69
|
+
span = MagicMock()
|
|
70
|
+
|
|
71
|
+
# Should not raise
|
|
72
|
+
processor.on_end(span)
|
|
73
|
+
|
|
74
|
+
def test_shutdown(self):
|
|
75
|
+
"""Test shutdown does nothing."""
|
|
76
|
+
|
|
77
|
+
processor = HudEnrichmentProcessor()
|
|
78
|
+
|
|
79
|
+
# Should not raise
|
|
80
|
+
processor.shutdown()
|
|
81
|
+
|
|
82
|
+
def test_force_flush(self):
|
|
83
|
+
"""Test force_flush returns True."""
|
|
84
|
+
|
|
85
|
+
processor = HudEnrichmentProcessor()
|
|
86
|
+
|
|
87
|
+
# Should return True
|
|
88
|
+
result = processor.force_flush()
|
|
89
|
+
assert result is True
|
|
90
|
+
|
|
91
|
+
def test_on_start_with_job_id(self):
|
|
92
|
+
"""Test on_start with job ID in baggage."""
|
|
93
|
+
|
|
94
|
+
processor = HudEnrichmentProcessor()
|
|
95
|
+
|
|
96
|
+
# Mock span
|
|
97
|
+
span = MagicMock()
|
|
98
|
+
span.set_attribute = MagicMock()
|
|
99
|
+
span.is_recording.return_value = True
|
|
100
|
+
|
|
101
|
+
# Mock baggage with job ID
|
|
102
|
+
parent_context = {}
|
|
103
|
+
with patch("hud.otel.processors.baggage.get_baggage") as mock_get_baggage:
|
|
104
|
+
# Return None for task_run_id, job-123 for job_id
|
|
105
|
+
mock_get_baggage.side_effect = (
|
|
106
|
+
lambda key, context: "job-123" if key == "hud.job_id" else None
|
|
107
|
+
)
|
|
108
|
+
processor.on_start(span, parent_context)
|
|
109
|
+
|
|
110
|
+
# Verify job ID attribute was set
|
|
111
|
+
span.set_attribute.assert_called_with("hud.job_id", "job-123")
|
|
112
|
+
|
|
113
|
+
def test_on_start_exception_handling(self):
|
|
114
|
+
"""Test on_start handles exceptions gracefully."""
|
|
115
|
+
|
|
116
|
+
processor = HudEnrichmentProcessor()
|
|
117
|
+
|
|
118
|
+
# Mock span that raises exception
|
|
119
|
+
span = MagicMock()
|
|
120
|
+
span.is_recording.side_effect = Exception("Test error")
|
|
121
|
+
|
|
122
|
+
# Should not raise
|
|
123
|
+
processor.on_start(span, parent_context=None)
|
|
124
|
+
|
|
125
|
+
def test_on_start_exception_handling_extended(self):
|
|
126
|
+
"""Test that exceptions in on_start are caught and logged."""
|
|
127
|
+
from hud.otel.processors import HudEnrichmentProcessor
|
|
128
|
+
|
|
129
|
+
processor = HudEnrichmentProcessor()
|
|
130
|
+
|
|
131
|
+
# Create a mock span that raises when setting attributes
|
|
132
|
+
mock_span = MagicMock()
|
|
133
|
+
mock_span.is_recording.return_value = True
|
|
134
|
+
mock_span.set_attribute.side_effect = RuntimeError("Attribute error")
|
|
135
|
+
|
|
136
|
+
parent_context = {}
|
|
137
|
+
|
|
138
|
+
# Patch logger and baggage to force an exception when setting attribute
|
|
139
|
+
with (
|
|
140
|
+
patch("hud.otel.processors.logger") as mock_logger,
|
|
141
|
+
patch("hud.otel.processors.baggage.get_baggage", return_value="test-id"),
|
|
142
|
+
):
|
|
143
|
+
# Should not raise, exception should be caught
|
|
144
|
+
processor.on_start(mock_span, parent_context)
|
|
145
|
+
|
|
146
|
+
# Verify logger.debug was called with the exception
|
|
147
|
+
mock_logger.debug.assert_called_once()
|
|
148
|
+
args = mock_logger.debug.call_args[0]
|
|
149
|
+
assert "HudEnrichmentProcessor.on_start error" in args[0]
|
|
150
|
+
assert "Attribute error" in str(args[1])
|
|
151
|
+
|
|
152
|
+
def test_on_start_with_baggage_get_exception(self):
|
|
153
|
+
"""Test exception handling when baggage.get_baggage fails for task_run_id."""
|
|
154
|
+
processor = HudEnrichmentProcessor()
|
|
155
|
+
|
|
156
|
+
mock_span = MagicMock()
|
|
157
|
+
mock_span.is_recording.return_value = True
|
|
158
|
+
|
|
159
|
+
parent_context = {}
|
|
160
|
+
|
|
161
|
+
# Make baggage.get_baggage raise an exception for task_run_id
|
|
162
|
+
with (
|
|
163
|
+
patch(
|
|
164
|
+
"hud.otel.processors.baggage.get_baggage",
|
|
165
|
+
side_effect=ValueError("Context error"),
|
|
166
|
+
),
|
|
167
|
+
patch("hud.otel.processors.logger") as mock_logger,
|
|
168
|
+
):
|
|
169
|
+
# Should not raise
|
|
170
|
+
processor.on_start(mock_span, parent_context)
|
|
171
|
+
|
|
172
|
+
# Verify logger.debug was called
|
|
173
|
+
mock_logger.debug.assert_called_once()
|
|
174
|
+
args = mock_logger.debug.call_args[0]
|
|
175
|
+
assert "Context error" in str(args[1])
|
|
176
|
+
|
|
177
|
+
def test_on_start_with_baggage_exception(self):
|
|
178
|
+
"""Test exception handling when baggage.get_baggage fails."""
|
|
179
|
+
processor = HudEnrichmentProcessor()
|
|
180
|
+
|
|
181
|
+
mock_span = MagicMock()
|
|
182
|
+
mock_span.is_recording.return_value = True
|
|
183
|
+
|
|
184
|
+
parent_context = {}
|
|
185
|
+
|
|
186
|
+
# Make baggage.get_baggage raise an exception
|
|
187
|
+
with (
|
|
188
|
+
patch("hud.otel.processors.baggage.get_baggage", side_effect=KeyError("Baggage error")),
|
|
189
|
+
patch("hud.otel.processors.logger") as mock_logger,
|
|
190
|
+
):
|
|
191
|
+
# Should not raise
|
|
192
|
+
processor.on_start(mock_span, parent_context)
|
|
193
|
+
|
|
194
|
+
# Verify logger.debug was called
|
|
195
|
+
mock_logger.debug.assert_called_once()
|
|
196
|
+
args = mock_logger.debug.call_args[0]
|
|
197
|
+
assert "Baggage error" in str(args[1])
|
hud/server/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from .server import MCPServer
|
|
4
|
-
|
|
5
|
-
__all__ = ["MCPServer"]
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .server import MCPServer
|
|
4
|
+
|
|
5
|
+
__all__ = ["MCPServer"]
|
hud/server/context.py
CHANGED
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
"""
|
|
2
|
-
HUD context helpers for persistent state across hot-reloads.
|
|
3
|
-
|
|
4
|
-
Provides utilities for creating shared context servers that survive
|
|
5
|
-
code reloads during development.
|
|
6
|
-
|
|
7
|
-
Usage in your environment:
|
|
8
|
-
# In your context_server.py:
|
|
9
|
-
from hud.server.context import serve_context
|
|
10
|
-
|
|
11
|
-
class MyContext:
|
|
12
|
-
def __init__(self):
|
|
13
|
-
self.state = {}
|
|
14
|
-
def startup(self):
|
|
15
|
-
# Initialize resources
|
|
16
|
-
pass
|
|
17
|
-
|
|
18
|
-
if __name__ == "__main__":
|
|
19
|
-
serve_context(MyContext())
|
|
20
|
-
|
|
21
|
-
# In your MCP server:
|
|
22
|
-
from hud.server.context import attach_context
|
|
23
|
-
ctx = attach_context() # Gets the persistent context
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
from __future__ import annotations
|
|
27
|
-
|
|
28
|
-
import asyncio
|
|
29
|
-
import logging
|
|
30
|
-
import os
|
|
31
|
-
from multiprocessing.managers import BaseManager
|
|
32
|
-
from typing import Any
|
|
33
|
-
|
|
34
|
-
logger = logging.getLogger(__name__)
|
|
35
|
-
# Default Unix socket path (can be overridden with HUD_CTX_SOCK)
|
|
36
|
-
DEFAULT_SOCK_PATH = "/tmp/hud_ctx.sock" # noqa: S108
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def serve_context(
|
|
40
|
-
context_instance: Any, sock_path: str | None = None, authkey: bytes = b"hud-context"
|
|
41
|
-
) -> BaseManager:
|
|
42
|
-
"""
|
|
43
|
-
Serve a context object via multiprocessing Manager.
|
|
44
|
-
|
|
45
|
-
Args:
|
|
46
|
-
context_instance: The context object to serve
|
|
47
|
-
sock_path: Unix socket path (defaults to HUD_CTX_SOCK env var or /tmp/hud_ctx.sock)
|
|
48
|
-
authkey: Authentication key for the manager
|
|
49
|
-
|
|
50
|
-
Returns:
|
|
51
|
-
The manager instance (can be used to shutdown)
|
|
52
|
-
"""
|
|
53
|
-
sock_path = sock_path or os.getenv("HUD_CTX_SOCK", DEFAULT_SOCK_PATH)
|
|
54
|
-
|
|
55
|
-
class ContextManager(BaseManager):
|
|
56
|
-
pass
|
|
57
|
-
|
|
58
|
-
ContextManager.register("get_context", callable=lambda: context_instance)
|
|
59
|
-
|
|
60
|
-
manager = ContextManager(address=sock_path, authkey=authkey)
|
|
61
|
-
manager.start()
|
|
62
|
-
|
|
63
|
-
return manager
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def attach_context(sock_path: str | None = None, authkey: bytes = b"hud-context") -> Any:
|
|
67
|
-
"""
|
|
68
|
-
Attach to a running context server.
|
|
69
|
-
|
|
70
|
-
Args:
|
|
71
|
-
sock_path: Unix socket path (defaults to HUD_CTX_SOCK env var or /tmp/hud_ctx.sock)
|
|
72
|
-
authkey: Authentication key for the manager
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
The shared context object
|
|
76
|
-
"""
|
|
77
|
-
sock_path = sock_path or os.getenv("HUD_CTX_SOCK", DEFAULT_SOCK_PATH)
|
|
78
|
-
|
|
79
|
-
class ContextManager(BaseManager):
|
|
80
|
-
pass
|
|
81
|
-
|
|
82
|
-
ContextManager.register("get_context")
|
|
83
|
-
|
|
84
|
-
manager = ContextManager(address=sock_path, authkey=authkey)
|
|
85
|
-
manager.connect()
|
|
86
|
-
|
|
87
|
-
return manager.get_context() # type: ignore
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
async def run_context_server(
|
|
91
|
-
context_instance: Any, sock_path: str | None = None, authkey: bytes = b"hud-context"
|
|
92
|
-
) -> None:
|
|
93
|
-
"""
|
|
94
|
-
Run a context server until interrupted.
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
context_instance: The context object to serve
|
|
98
|
-
sock_path: Unix socket path
|
|
99
|
-
authkey: Authentication key
|
|
100
|
-
"""
|
|
101
|
-
sock_path = sock_path or os.getenv("HUD_CTX_SOCK", DEFAULT_SOCK_PATH)
|
|
102
|
-
|
|
103
|
-
logger.info("[Context Server] Starting on %s...", sock_path)
|
|
104
|
-
|
|
105
|
-
# Start the manager
|
|
106
|
-
manager = serve_context(context_instance, sock_path, authkey)
|
|
107
|
-
logger.info("[Context Server] Ready on %s", sock_path)
|
|
108
|
-
|
|
109
|
-
# Wait forever (until killed)
|
|
110
|
-
try:
|
|
111
|
-
await asyncio.Event().wait()
|
|
112
|
-
except KeyboardInterrupt:
|
|
113
|
-
logger.info("[Context Server] Shutting down...")
|
|
114
|
-
manager.shutdown()
|
|
1
|
+
"""
|
|
2
|
+
HUD context helpers for persistent state across hot-reloads.
|
|
3
|
+
|
|
4
|
+
Provides utilities for creating shared context servers that survive
|
|
5
|
+
code reloads during development.
|
|
6
|
+
|
|
7
|
+
Usage in your environment:
|
|
8
|
+
# In your context_server.py:
|
|
9
|
+
from hud.server.context import serve_context
|
|
10
|
+
|
|
11
|
+
class MyContext:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.state = {}
|
|
14
|
+
def startup(self):
|
|
15
|
+
# Initialize resources
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
serve_context(MyContext())
|
|
20
|
+
|
|
21
|
+
# In your MCP server:
|
|
22
|
+
from hud.server.context import attach_context
|
|
23
|
+
ctx = attach_context() # Gets the persistent context
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import asyncio
|
|
29
|
+
import logging
|
|
30
|
+
import os
|
|
31
|
+
from multiprocessing.managers import BaseManager
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
# Default Unix socket path (can be overridden with HUD_CTX_SOCK)
|
|
36
|
+
DEFAULT_SOCK_PATH = "/tmp/hud_ctx.sock" # noqa: S108
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def serve_context(
|
|
40
|
+
context_instance: Any, sock_path: str | None = None, authkey: bytes = b"hud-context"
|
|
41
|
+
) -> BaseManager:
|
|
42
|
+
"""
|
|
43
|
+
Serve a context object via multiprocessing Manager.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
context_instance: The context object to serve
|
|
47
|
+
sock_path: Unix socket path (defaults to HUD_CTX_SOCK env var or /tmp/hud_ctx.sock)
|
|
48
|
+
authkey: Authentication key for the manager
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
The manager instance (can be used to shutdown)
|
|
52
|
+
"""
|
|
53
|
+
sock_path = sock_path or os.getenv("HUD_CTX_SOCK", DEFAULT_SOCK_PATH)
|
|
54
|
+
|
|
55
|
+
class ContextManager(BaseManager):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
ContextManager.register("get_context", callable=lambda: context_instance)
|
|
59
|
+
|
|
60
|
+
manager = ContextManager(address=sock_path, authkey=authkey)
|
|
61
|
+
manager.start()
|
|
62
|
+
|
|
63
|
+
return manager
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def attach_context(sock_path: str | None = None, authkey: bytes = b"hud-context") -> Any:
|
|
67
|
+
"""
|
|
68
|
+
Attach to a running context server.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
sock_path: Unix socket path (defaults to HUD_CTX_SOCK env var or /tmp/hud_ctx.sock)
|
|
72
|
+
authkey: Authentication key for the manager
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
The shared context object
|
|
76
|
+
"""
|
|
77
|
+
sock_path = sock_path or os.getenv("HUD_CTX_SOCK", DEFAULT_SOCK_PATH)
|
|
78
|
+
|
|
79
|
+
class ContextManager(BaseManager):
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
ContextManager.register("get_context")
|
|
83
|
+
|
|
84
|
+
manager = ContextManager(address=sock_path, authkey=authkey)
|
|
85
|
+
manager.connect()
|
|
86
|
+
|
|
87
|
+
return manager.get_context() # type: ignore
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def run_context_server(
|
|
91
|
+
context_instance: Any, sock_path: str | None = None, authkey: bytes = b"hud-context"
|
|
92
|
+
) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Run a context server until interrupted.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
context_instance: The context object to serve
|
|
98
|
+
sock_path: Unix socket path
|
|
99
|
+
authkey: Authentication key
|
|
100
|
+
"""
|
|
101
|
+
sock_path = sock_path or os.getenv("HUD_CTX_SOCK", DEFAULT_SOCK_PATH)
|
|
102
|
+
|
|
103
|
+
logger.info("[Context Server] Starting on %s...", sock_path)
|
|
104
|
+
|
|
105
|
+
# Start the manager
|
|
106
|
+
manager = serve_context(context_instance, sock_path, authkey)
|
|
107
|
+
logger.info("[Context Server] Ready on %s", sock_path)
|
|
108
|
+
|
|
109
|
+
# Wait forever (until killed)
|
|
110
|
+
try:
|
|
111
|
+
await asyncio.Event().wait()
|
|
112
|
+
except KeyboardInterrupt:
|
|
113
|
+
logger.info("[Context Server] Shutting down...")
|
|
114
|
+
manager.shutdown()
|
hud/server/helper/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"""Helper sub-package: utilities, registration helpers, shims."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
__all__ = []
|
|
1
|
+
"""Helper sub-package: utilities, registration helpers, shims."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = []
|