hud-python 0.4.45__py3-none-any.whl → 0.5.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.
Files changed (274) hide show
  1. hud/__init__.py +27 -7
  2. hud/agents/__init__.py +11 -5
  3. hud/agents/base.py +220 -500
  4. hud/agents/claude.py +200 -240
  5. hud/agents/gemini.py +275 -0
  6. hud/agents/gemini_cua.py +335 -0
  7. hud/agents/grounded_openai.py +98 -100
  8. hud/agents/misc/integration_test_agent.py +51 -20
  9. hud/agents/misc/response_agent.py +41 -36
  10. hud/agents/openai.py +291 -292
  11. hud/agents/{openai_chat_generic.py → openai_chat.py} +80 -34
  12. hud/agents/operator.py +211 -0
  13. hud/agents/tests/conftest.py +133 -0
  14. hud/agents/tests/test_base.py +300 -622
  15. hud/agents/tests/test_base_runtime.py +233 -0
  16. hud/agents/tests/test_claude.py +379 -210
  17. hud/agents/tests/test_client.py +9 -10
  18. hud/agents/tests/test_gemini.py +369 -0
  19. hud/agents/tests/test_grounded_openai_agent.py +65 -50
  20. hud/agents/tests/test_openai.py +376 -140
  21. hud/agents/tests/test_operator.py +362 -0
  22. hud/agents/tests/test_run_eval.py +179 -0
  23. hud/cli/__init__.py +461 -545
  24. hud/cli/analyze.py +43 -5
  25. hud/cli/build.py +664 -110
  26. hud/cli/debug.py +8 -5
  27. hud/cli/dev.py +882 -734
  28. hud/cli/eval.py +782 -668
  29. hud/cli/flows/dev.py +167 -0
  30. hud/cli/flows/init.py +191 -0
  31. hud/cli/flows/tasks.py +153 -56
  32. hud/cli/flows/templates.py +151 -0
  33. hud/cli/flows/tests/__init__.py +1 -0
  34. hud/cli/flows/tests/test_dev.py +126 -0
  35. hud/cli/init.py +60 -58
  36. hud/cli/push.py +29 -11
  37. hud/cli/rft.py +311 -0
  38. hud/cli/rft_status.py +145 -0
  39. hud/cli/tests/test_analyze.py +5 -5
  40. hud/cli/tests/test_analyze_metadata.py +3 -2
  41. hud/cli/tests/test_analyze_module.py +120 -0
  42. hud/cli/tests/test_build.py +108 -6
  43. hud/cli/tests/test_build_failure.py +41 -0
  44. hud/cli/tests/test_build_module.py +50 -0
  45. hud/cli/tests/test_cli_init.py +6 -1
  46. hud/cli/tests/test_cli_more_wrappers.py +30 -0
  47. hud/cli/tests/test_cli_root.py +140 -0
  48. hud/cli/tests/test_convert.py +361 -0
  49. hud/cli/tests/test_debug.py +12 -10
  50. hud/cli/tests/test_dev.py +197 -0
  51. hud/cli/tests/test_eval.py +251 -0
  52. hud/cli/tests/test_eval_bedrock.py +51 -0
  53. hud/cli/tests/test_init.py +124 -0
  54. hud/cli/tests/test_main_module.py +11 -5
  55. hud/cli/tests/test_mcp_server.py +12 -100
  56. hud/cli/tests/test_push_happy.py +74 -0
  57. hud/cli/tests/test_push_wrapper.py +23 -0
  58. hud/cli/tests/test_registry.py +1 -1
  59. hud/cli/tests/test_utils.py +1 -1
  60. hud/cli/{rl → utils}/celebrate.py +14 -12
  61. hud/cli/utils/config.py +18 -1
  62. hud/cli/utils/docker.py +130 -4
  63. hud/cli/utils/env_check.py +9 -9
  64. hud/cli/utils/git.py +136 -0
  65. hud/cli/utils/interactive.py +39 -5
  66. hud/cli/utils/metadata.py +69 -0
  67. hud/cli/utils/runner.py +1 -1
  68. hud/cli/utils/server.py +2 -2
  69. hud/cli/utils/source_hash.py +3 -3
  70. hud/cli/utils/tasks.py +4 -1
  71. hud/cli/utils/tests/__init__.py +0 -0
  72. hud/cli/utils/tests/test_config.py +58 -0
  73. hud/cli/utils/tests/test_docker.py +93 -0
  74. hud/cli/utils/tests/test_docker_hints.py +71 -0
  75. hud/cli/utils/tests/test_env_check.py +74 -0
  76. hud/cli/utils/tests/test_environment.py +42 -0
  77. hud/cli/utils/tests/test_git.py +142 -0
  78. hud/cli/utils/tests/test_interactive_module.py +60 -0
  79. hud/cli/utils/tests/test_local_runner.py +50 -0
  80. hud/cli/utils/tests/test_logging_utils.py +23 -0
  81. hud/cli/utils/tests/test_metadata.py +49 -0
  82. hud/cli/utils/tests/test_package_runner.py +35 -0
  83. hud/cli/utils/tests/test_registry_utils.py +49 -0
  84. hud/cli/utils/tests/test_remote_runner.py +25 -0
  85. hud/cli/utils/tests/test_runner_modules.py +52 -0
  86. hud/cli/utils/tests/test_source_hash.py +36 -0
  87. hud/cli/utils/tests/test_tasks.py +80 -0
  88. hud/cli/utils/version_check.py +258 -0
  89. hud/cli/{rl → utils}/viewer.py +2 -2
  90. hud/clients/README.md +12 -11
  91. hud/clients/__init__.py +4 -3
  92. hud/clients/base.py +166 -26
  93. hud/clients/environment.py +51 -0
  94. hud/clients/fastmcp.py +13 -6
  95. hud/clients/mcp_use.py +40 -15
  96. hud/clients/tests/test_analyze_scenarios.py +206 -0
  97. hud/clients/tests/test_protocol.py +9 -3
  98. hud/datasets/__init__.py +23 -20
  99. hud/datasets/loader.py +327 -0
  100. hud/datasets/runner.py +192 -105
  101. hud/datasets/tests/__init__.py +0 -0
  102. hud/datasets/tests/test_loader.py +221 -0
  103. hud/datasets/tests/test_utils.py +315 -0
  104. hud/datasets/utils.py +270 -90
  105. hud/environment/__init__.py +50 -0
  106. hud/environment/connection.py +206 -0
  107. hud/environment/connectors/__init__.py +33 -0
  108. hud/environment/connectors/base.py +68 -0
  109. hud/environment/connectors/local.py +177 -0
  110. hud/environment/connectors/mcp_config.py +109 -0
  111. hud/environment/connectors/openai.py +101 -0
  112. hud/environment/connectors/remote.py +172 -0
  113. hud/environment/environment.py +694 -0
  114. hud/environment/integrations/__init__.py +45 -0
  115. hud/environment/integrations/adk.py +67 -0
  116. hud/environment/integrations/anthropic.py +196 -0
  117. hud/environment/integrations/gemini.py +92 -0
  118. hud/environment/integrations/langchain.py +82 -0
  119. hud/environment/integrations/llamaindex.py +68 -0
  120. hud/environment/integrations/openai.py +238 -0
  121. hud/environment/mock.py +306 -0
  122. hud/environment/router.py +112 -0
  123. hud/environment/scenarios.py +493 -0
  124. hud/environment/tests/__init__.py +1 -0
  125. hud/environment/tests/test_connection.py +317 -0
  126. hud/environment/tests/test_connectors.py +218 -0
  127. hud/environment/tests/test_environment.py +161 -0
  128. hud/environment/tests/test_integrations.py +257 -0
  129. hud/environment/tests/test_local_connectors.py +201 -0
  130. hud/environment/tests/test_scenarios.py +280 -0
  131. hud/environment/tests/test_tools.py +208 -0
  132. hud/environment/types.py +23 -0
  133. hud/environment/utils/__init__.py +35 -0
  134. hud/environment/utils/formats.py +215 -0
  135. hud/environment/utils/schema.py +171 -0
  136. hud/environment/utils/tool_wrappers.py +113 -0
  137. hud/eval/__init__.py +67 -0
  138. hud/eval/context.py +674 -0
  139. hud/eval/display.py +299 -0
  140. hud/eval/instrument.py +185 -0
  141. hud/eval/manager.py +466 -0
  142. hud/eval/parallel.py +268 -0
  143. hud/eval/task.py +340 -0
  144. hud/eval/tests/__init__.py +1 -0
  145. hud/eval/tests/test_context.py +178 -0
  146. hud/eval/tests/test_eval.py +210 -0
  147. hud/eval/tests/test_manager.py +152 -0
  148. hud/eval/tests/test_parallel.py +168 -0
  149. hud/eval/tests/test_task.py +145 -0
  150. hud/eval/types.py +63 -0
  151. hud/eval/utils.py +183 -0
  152. hud/patches/__init__.py +19 -0
  153. hud/patches/mcp_patches.py +151 -0
  154. hud/patches/warnings.py +54 -0
  155. hud/samples/browser.py +4 -4
  156. hud/server/__init__.py +2 -1
  157. hud/server/low_level.py +2 -1
  158. hud/server/router.py +164 -0
  159. hud/server/server.py +567 -80
  160. hud/server/tests/test_mcp_server_integration.py +11 -11
  161. hud/server/tests/test_mcp_server_more.py +1 -1
  162. hud/server/tests/test_server_extra.py +2 -0
  163. hud/settings.py +45 -3
  164. hud/shared/exceptions.py +36 -10
  165. hud/shared/hints.py +26 -1
  166. hud/shared/requests.py +15 -3
  167. hud/shared/tests/test_exceptions.py +40 -31
  168. hud/shared/tests/test_hints.py +167 -0
  169. hud/telemetry/__init__.py +20 -19
  170. hud/telemetry/exporter.py +201 -0
  171. hud/telemetry/instrument.py +158 -253
  172. hud/telemetry/tests/test_eval_telemetry.py +356 -0
  173. hud/telemetry/tests/test_exporter.py +258 -0
  174. hud/telemetry/tests/test_instrument.py +401 -0
  175. hud/tools/__init__.py +16 -2
  176. hud/tools/apply_patch.py +639 -0
  177. hud/tools/base.py +54 -4
  178. hud/tools/bash.py +2 -2
  179. hud/tools/computer/__init__.py +4 -0
  180. hud/tools/computer/anthropic.py +2 -2
  181. hud/tools/computer/gemini.py +385 -0
  182. hud/tools/computer/hud.py +23 -6
  183. hud/tools/computer/openai.py +20 -21
  184. hud/tools/computer/qwen.py +434 -0
  185. hud/tools/computer/settings.py +37 -0
  186. hud/tools/edit.py +3 -7
  187. hud/tools/executors/base.py +4 -2
  188. hud/tools/executors/pyautogui.py +1 -1
  189. hud/tools/grounding/grounded_tool.py +13 -18
  190. hud/tools/grounding/grounder.py +10 -31
  191. hud/tools/grounding/tests/test_grounded_tool.py +26 -44
  192. hud/tools/jupyter.py +330 -0
  193. hud/tools/playwright.py +18 -3
  194. hud/tools/shell.py +308 -0
  195. hud/tools/tests/test_apply_patch.py +718 -0
  196. hud/tools/tests/test_computer.py +4 -9
  197. hud/tools/tests/test_computer_actions.py +24 -2
  198. hud/tools/tests/test_jupyter_tool.py +181 -0
  199. hud/tools/tests/test_shell.py +596 -0
  200. hud/tools/tests/test_submit.py +85 -0
  201. hud/tools/tests/test_types.py +193 -0
  202. hud/tools/types.py +21 -1
  203. hud/types.py +167 -57
  204. hud/utils/__init__.py +2 -0
  205. hud/utils/env.py +67 -0
  206. hud/utils/hud_console.py +61 -3
  207. hud/utils/mcp.py +15 -58
  208. hud/utils/strict_schema.py +162 -0
  209. hud/utils/tests/test_init.py +1 -2
  210. hud/utils/tests/test_mcp.py +1 -28
  211. hud/utils/tests/test_pretty_errors.py +186 -0
  212. hud/utils/tests/test_tool_shorthand.py +154 -0
  213. hud/utils/tests/test_version.py +1 -1
  214. hud/utils/types.py +20 -0
  215. hud/version.py +1 -1
  216. hud_python-0.5.1.dist-info/METADATA +264 -0
  217. hud_python-0.5.1.dist-info/RECORD +299 -0
  218. {hud_python-0.4.45.dist-info → hud_python-0.5.1.dist-info}/WHEEL +1 -1
  219. hud/agents/langchain.py +0 -261
  220. hud/agents/lite_llm.py +0 -72
  221. hud/cli/rl/__init__.py +0 -180
  222. hud/cli/rl/config.py +0 -101
  223. hud/cli/rl/display.py +0 -133
  224. hud/cli/rl/gpu.py +0 -63
  225. hud/cli/rl/gpu_utils.py +0 -321
  226. hud/cli/rl/local_runner.py +0 -595
  227. hud/cli/rl/presets.py +0 -96
  228. hud/cli/rl/remote_runner.py +0 -463
  229. hud/cli/rl/rl_api.py +0 -150
  230. hud/cli/rl/vllm.py +0 -177
  231. hud/cli/rl/wait_utils.py +0 -89
  232. hud/datasets/parallel.py +0 -687
  233. hud/misc/__init__.py +0 -1
  234. hud/misc/claude_plays_pokemon.py +0 -292
  235. hud/otel/__init__.py +0 -35
  236. hud/otel/collector.py +0 -142
  237. hud/otel/config.py +0 -181
  238. hud/otel/context.py +0 -570
  239. hud/otel/exporters.py +0 -369
  240. hud/otel/instrumentation.py +0 -135
  241. hud/otel/processors.py +0 -121
  242. hud/otel/tests/__init__.py +0 -1
  243. hud/otel/tests/test_processors.py +0 -197
  244. hud/rl/README.md +0 -30
  245. hud/rl/__init__.py +0 -1
  246. hud/rl/actor.py +0 -176
  247. hud/rl/buffer.py +0 -405
  248. hud/rl/chat_template.jinja +0 -101
  249. hud/rl/config.py +0 -192
  250. hud/rl/distributed.py +0 -132
  251. hud/rl/learner.py +0 -637
  252. hud/rl/tests/__init__.py +0 -1
  253. hud/rl/tests/test_learner.py +0 -186
  254. hud/rl/train.py +0 -382
  255. hud/rl/types.py +0 -101
  256. hud/rl/utils/start_vllm_server.sh +0 -30
  257. hud/rl/utils.py +0 -524
  258. hud/rl/vllm_adapter.py +0 -143
  259. hud/telemetry/job.py +0 -352
  260. hud/telemetry/replay.py +0 -74
  261. hud/telemetry/tests/test_replay.py +0 -40
  262. hud/telemetry/tests/test_trace.py +0 -63
  263. hud/telemetry/trace.py +0 -158
  264. hud/utils/agent_factories.py +0 -86
  265. hud/utils/async_utils.py +0 -65
  266. hud/utils/group_eval.py +0 -223
  267. hud/utils/progress.py +0 -149
  268. hud/utils/tasks.py +0 -127
  269. hud/utils/tests/test_async_utils.py +0 -173
  270. hud/utils/tests/test_progress.py +0 -261
  271. hud_python-0.4.45.dist-info/METADATA +0 -552
  272. hud_python-0.4.45.dist-info/RECORD +0 -228
  273. {hud_python-0.4.45.dist-info → hud_python-0.5.1.dist-info}/entry_points.txt +0 -0
  274. {hud_python-0.4.45.dist-info → hud_python-0.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,401 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ import pytest
6
+
7
+ from hud.telemetry.instrument import _serialize_value, instrument
8
+
9
+
10
+ def test_serialize_value_simple_types():
11
+ """Test _serialize_value with simple types."""
12
+ assert _serialize_value("string") == "string"
13
+ assert _serialize_value(42) == 42
14
+ assert _serialize_value(3.14) == 3.14
15
+ assert _serialize_value(True) is True
16
+ assert _serialize_value(None) is None
17
+
18
+
19
+ def test_serialize_value_list():
20
+ """Test _serialize_value with lists."""
21
+ result = _serialize_value([1, 2, 3])
22
+ assert result == [1, 2, 3]
23
+
24
+
25
+ def test_serialize_value_list_truncation():
26
+ """Test _serialize_value truncates long lists."""
27
+ long_list = list(range(20))
28
+ result = _serialize_value(long_list, max_items=5)
29
+ assert len(result) == 5
30
+ assert result == [0, 1, 2, 3, 4]
31
+
32
+
33
+ def test_serialize_value_tuple():
34
+ """Test _serialize_value with tuples."""
35
+ result = _serialize_value((1, 2, 3))
36
+ assert result == [1, 2, 3] # Converted to list by JSON
37
+
38
+
39
+ def test_serialize_value_tuple_truncation():
40
+ """Test _serialize_value truncates long tuples."""
41
+ long_tuple = tuple(range(20))
42
+ result = _serialize_value(long_tuple, max_items=5)
43
+ assert len(result) == 5
44
+
45
+
46
+ def test_serialize_value_dict():
47
+ """Test _serialize_value with dicts."""
48
+ result = _serialize_value({"key": "value"})
49
+ assert result == {"key": "value"}
50
+
51
+
52
+ def test_serialize_value_dict_truncation():
53
+ """Test _serialize_value truncates large dicts."""
54
+ large_dict = {f"key{i}": i for i in range(20)}
55
+ result = _serialize_value(large_dict, max_items=5)
56
+ assert len(result) == 5
57
+
58
+
59
+ def test_serialize_value_complex_object():
60
+ """Test _serialize_value with custom objects."""
61
+
62
+ @dataclass
63
+ class CustomObj:
64
+ name: str
65
+ value: int
66
+
67
+ obj = CustomObj(name="test", value=42)
68
+ result = _serialize_value(obj)
69
+ assert isinstance(result, dict)
70
+ assert result["name"] == "test"
71
+ assert result["value"] == 42
72
+
73
+
74
+ def test_serialize_value_fallback():
75
+ """Test _serialize_value fallback for non-serializable objects."""
76
+
77
+ class WeirdObj:
78
+ def __init__(self):
79
+ raise Exception("Can't access")
80
+
81
+ obj = WeirdObj.__new__(WeirdObj)
82
+ result = _serialize_value(obj)
83
+ # The result is a string representation of the object
84
+ assert isinstance(result, str)
85
+ assert "WeirdObj" in result
86
+
87
+
88
+ @pytest.mark.asyncio
89
+ async def test_instrument_async_basic():
90
+ """Test instrument decorator on async function."""
91
+
92
+ @instrument
93
+ async def test_func(x: int, y: int) -> int:
94
+ return x + y
95
+
96
+ result = await test_func(2, 3)
97
+ assert result == 5
98
+
99
+
100
+ @pytest.mark.asyncio
101
+ async def test_instrument_async_with_params():
102
+ """Test instrument with custom parameters."""
103
+
104
+ @instrument(name="custom_name", category="custom_type")
105
+ async def test_func(x: int) -> int:
106
+ return x * 2
107
+
108
+ result = await test_func(5)
109
+ assert result == 10
110
+
111
+
112
+ @pytest.mark.asyncio
113
+ async def test_instrument_async_with_exception():
114
+ """Test instrument handles exceptions."""
115
+
116
+ @instrument
117
+ async def test_func():
118
+ raise ValueError("Test error")
119
+
120
+ with pytest.raises(ValueError, match="Test error"):
121
+ await test_func()
122
+
123
+
124
+ @pytest.mark.asyncio
125
+ async def test_instrument_async_no_record_args():
126
+ """Test instrument with record_args=False."""
127
+
128
+ @instrument(record_args=False)
129
+ async def test_func(x: int) -> int:
130
+ return x
131
+
132
+ result = await test_func(42)
133
+ assert result == 42
134
+
135
+
136
+ @pytest.mark.asyncio
137
+ async def test_instrument_async_no_record_result():
138
+ """Test instrument with record_result=False."""
139
+
140
+ @instrument(record_result=False)
141
+ async def test_func() -> str:
142
+ return "test"
143
+
144
+ result = await test_func()
145
+ assert result == "test"
146
+
147
+
148
+ @pytest.mark.asyncio
149
+ async def test_instrument_async_with_category():
150
+ """Test instrument with custom category."""
151
+
152
+ @instrument(category="agent")
153
+ async def test_func() -> int:
154
+ return 42
155
+
156
+ result = await test_func()
157
+ assert result == 42
158
+
159
+
160
+ def test_instrument_sync_basic():
161
+ """Test instrument decorator on sync function."""
162
+
163
+ @instrument
164
+ def test_func(x: int, y: int) -> int:
165
+ return x + y
166
+
167
+ result = test_func(2, 3)
168
+ assert result == 5
169
+
170
+
171
+ def test_instrument_sync_with_params():
172
+ """Test instrument on sync function with parameters."""
173
+
174
+ @instrument(name="sync_custom", category="sync_type")
175
+ def test_func(x: int) -> int:
176
+ return x * 2
177
+
178
+ result = test_func(5)
179
+ assert result == 10
180
+
181
+
182
+ def test_instrument_sync_with_exception():
183
+ """Test instrument handles exceptions in sync functions."""
184
+
185
+ @instrument
186
+ def test_func():
187
+ raise ValueError("Sync error")
188
+
189
+ with pytest.raises(ValueError, match="Sync error"):
190
+ test_func()
191
+
192
+
193
+ def test_instrument_sync_no_record_args():
194
+ """Test instrument sync with record_args=False."""
195
+
196
+ @instrument(record_args=False)
197
+ def test_func(x: int) -> int:
198
+ return x
199
+
200
+ result = test_func(42)
201
+ assert result == 42
202
+
203
+
204
+ def test_instrument_sync_no_record_result():
205
+ """Test instrument sync with record_result=False."""
206
+
207
+ @instrument(record_result=False)
208
+ def test_func() -> str:
209
+ return "test"
210
+
211
+ result = test_func()
212
+ assert result == "test"
213
+
214
+
215
+ def test_instrument_sync_with_category():
216
+ """Test instrument sync with custom category."""
217
+
218
+ @instrument(category="tool")
219
+ def test_func() -> int:
220
+ return 42
221
+
222
+ result = test_func()
223
+ assert result == 42
224
+
225
+
226
+ def test_instrument_already_instrumented():
227
+ """Test that instrumenting already instrumented function is skipped."""
228
+
229
+ @instrument
230
+ def test_func():
231
+ return "original"
232
+
233
+ # Try to instrument again
234
+ test_func2 = instrument(test_func)
235
+
236
+ # Should be the same function
237
+ assert test_func2 is test_func
238
+
239
+
240
+ def test_instrument_marks_as_instrumented():
241
+ """Test that instrument marks functions correctly."""
242
+
243
+ @instrument
244
+ def test_func():
245
+ return True
246
+
247
+ assert hasattr(test_func, "_hud_instrumented")
248
+ assert test_func._hud_instrumented is True
249
+ assert hasattr(test_func, "_hud_original")
250
+
251
+
252
+ @pytest.mark.asyncio
253
+ async def test_instrument_async_complex_result():
254
+ """Test instrument with complex result object."""
255
+
256
+ @instrument
257
+ async def test_func() -> dict:
258
+ return {"nested": {"data": [1, 2, 3]}, "count": 3}
259
+
260
+ result = await test_func()
261
+ assert result["count"] == 3
262
+
263
+
264
+ def test_instrument_sync_complex_result():
265
+ """Test instrument sync with complex result."""
266
+
267
+ @dataclass
268
+ class Result:
269
+ value: int
270
+ name: str
271
+
272
+ @instrument
273
+ def test_func() -> Result:
274
+ return Result(value=42, name="test")
275
+
276
+ result = test_func()
277
+ assert result.value == 42
278
+
279
+
280
+ @pytest.mark.asyncio
281
+ async def test_instrument_async_with_self_param():
282
+ """Test instrument properly handles 'self' parameter."""
283
+
284
+ class TestClass:
285
+ @instrument
286
+ async def method(self, x: int) -> int:
287
+ return x * 2
288
+
289
+ obj = TestClass()
290
+ result = await obj.method(5)
291
+ assert result == 10
292
+
293
+
294
+ def test_instrument_sync_with_cls_param():
295
+ """Test instrument properly handles 'cls' parameter."""
296
+
297
+ class TestClass:
298
+ @classmethod
299
+ @instrument
300
+ def method(cls, x: int) -> int:
301
+ return x * 3
302
+
303
+ result = TestClass.method(4)
304
+ assert result == 12
305
+
306
+
307
+ @pytest.mark.asyncio
308
+ async def test_instrument_async_serialization_error():
309
+ """Test instrument handles serialization errors gracefully."""
310
+
311
+ class UnserializableArg:
312
+ def __getattribute__(self, name):
313
+ raise Exception("Can't serialize")
314
+
315
+ @instrument
316
+ async def test_func(arg):
317
+ return "success"
318
+
319
+ # Should not raise, just skip serialization
320
+ result = await test_func(UnserializableArg())
321
+ assert result == "success"
322
+
323
+
324
+ def test_instrument_function_without_signature():
325
+ """Test instrument on functions without inspectable signature."""
326
+ # Built-in functions don't have signatures
327
+ instrumented_len = instrument(len)
328
+ result = instrumented_len([1, 2, 3])
329
+ assert result == 3
330
+
331
+
332
+ @pytest.mark.asyncio
333
+ async def test_instrument_async_result_serialization_error():
334
+ """Test instrument handles result serialization errors."""
335
+
336
+ class UnserializableResult:
337
+ def __iter__(self):
338
+ raise Exception("Can't iterate")
339
+
340
+ @instrument
341
+ async def test_func():
342
+ return UnserializableResult()
343
+
344
+ # Should not raise, just skip result recording
345
+ result = await test_func()
346
+ assert isinstance(result, UnserializableResult)
347
+
348
+
349
+ def test_instrument_without_parentheses():
350
+ """Test using @instrument without parentheses."""
351
+
352
+ @instrument
353
+ def test_func(x: int) -> int:
354
+ return x + 1
355
+
356
+ assert test_func(5) == 6
357
+
358
+
359
+ def test_instrument_with_parentheses():
360
+ """Test using @instrument() with parentheses."""
361
+
362
+ @instrument()
363
+ def test_func(x: int) -> int:
364
+ return x + 1
365
+
366
+ assert test_func(5) == 6
367
+
368
+
369
+ @pytest.mark.asyncio
370
+ async def test_instrument_async_with_defaults():
371
+ """Test instrument with function that has default arguments."""
372
+
373
+ @instrument
374
+ async def test_func(x: int, y: int = 10) -> int:
375
+ return x + y
376
+
377
+ assert await test_func(5) == 15
378
+ assert await test_func(5, 20) == 25
379
+
380
+
381
+ def test_instrument_sync_with_kwargs():
382
+ """Test instrument with keyword arguments."""
383
+
384
+ @instrument
385
+ def test_func(x: int, **kwargs) -> dict:
386
+ return {"x": x, **kwargs}
387
+
388
+ result = test_func(1, a=2, b=3)
389
+ assert result == {"x": 1, "a": 2, "b": 3}
390
+
391
+
392
+ @pytest.mark.asyncio
393
+ async def test_instrument_async_with_varargs():
394
+ """Test instrument with *args."""
395
+
396
+ @instrument
397
+ async def test_func(*args) -> int:
398
+ return sum(args)
399
+
400
+ result = await test_func(1, 2, 3, 4)
401
+ assert result == 10
hud/tools/__init__.py CHANGED
@@ -12,7 +12,13 @@ from .response import ResponseTool
12
12
  from .submit import SubmitTool
13
13
 
14
14
  if TYPE_CHECKING:
15
- from .computer import AnthropicComputerTool, HudComputerTool, OpenAIComputerTool
15
+ from .computer import (
16
+ AnthropicComputerTool,
17
+ GeminiComputerTool,
18
+ HudComputerTool,
19
+ OpenAIComputerTool,
20
+ QwenComputerTool,
21
+ )
16
22
 
17
23
  __all__ = [
18
24
  "AnthropicComputerTool",
@@ -20,9 +26,11 @@ __all__ = [
20
26
  "BaseTool",
21
27
  "BashTool",
22
28
  "EditTool",
29
+ "GeminiComputerTool",
23
30
  "HudComputerTool",
24
31
  "OpenAIComputerTool",
25
32
  "PlaywrightTool",
33
+ "QwenComputerTool",
26
34
  "ResponseTool",
27
35
  "SubmitTool",
28
36
  ]
@@ -30,7 +38,13 @@ __all__ = [
30
38
 
31
39
  def __getattr__(name: str) -> Any:
32
40
  """Lazy import computer tools to avoid importing pyautogui unless needed."""
33
- if name in ("AnthropicComputerTool", "HudComputerTool", "OpenAIComputerTool"):
41
+ if name in (
42
+ "AnthropicComputerTool",
43
+ "HudComputerTool",
44
+ "OpenAIComputerTool",
45
+ "GeminiComputerTool",
46
+ "QwenComputerTool",
47
+ ):
34
48
  from . import computer
35
49
 
36
50
  return getattr(computer, name)