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,68 @@
1
+ """Base connector mixin with shared helper."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ if TYPE_CHECKING:
8
+ from collections.abc import Callable
9
+
10
+ from fastmcp.tools.tool import Tool
11
+
12
+ from hud.environment.connection import ConnectionType, Connector
13
+
14
+ __all__ = ["BaseConnectorMixin"]
15
+
16
+
17
+ class BaseConnectorMixin:
18
+ """Base mixin providing connection helper.
19
+
20
+ Requires:
21
+ _connections: dict[str, Connector]
22
+ """
23
+
24
+ _connections: dict[str, Connector]
25
+
26
+ def _add_connection(
27
+ self,
28
+ name: str,
29
+ transport: Any,
30
+ *,
31
+ connection_type: ConnectionType,
32
+ auth: str | None = None,
33
+ prefix: str | None = None,
34
+ include: list[str] | None = None,
35
+ exclude: list[str] | None = None,
36
+ transform: Callable[[Tool], Tool | None] | None = None,
37
+ ) -> Any:
38
+ """Add a connection to the environment.
39
+
40
+ Args:
41
+ name: Connection name/alias.
42
+ transport: FastMCP transport (URL, config dict, etc.).
43
+ connection_type: LOCAL or REMOTE - determines parallelization.
44
+ auth: Authorization header value.
45
+ prefix: Prefix for tool names.
46
+ include: Only include these tools.
47
+ exclude: Exclude these tools.
48
+ transform: Transform function for tools.
49
+
50
+ Returns:
51
+ self for chaining.
52
+ """
53
+ from hud.environment.connection import ConnectionConfig, Connector
54
+
55
+ config = ConnectionConfig(
56
+ prefix=prefix,
57
+ include=include,
58
+ exclude=exclude,
59
+ transform=transform,
60
+ )
61
+ self._connections[name] = Connector(
62
+ transport,
63
+ config,
64
+ name,
65
+ connection_type=connection_type,
66
+ auth=auth,
67
+ )
68
+ return self
@@ -0,0 +1,177 @@
1
+ """Local connection connectors - Docker image, FastAPI, MCPServer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from hud.environment.connectors.mcp_config import MCPConfigConnectorMixin
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Callable
11
+
12
+ from fastmcp.tools.tool import Tool
13
+
14
+ __all__ = ["LocalConnectorMixin"]
15
+
16
+
17
+ class LocalConnectorMixin(MCPConfigConnectorMixin):
18
+ """Mixin providing local connection methods.
19
+
20
+ Methods:
21
+ connect_image(image) - Run Docker image via stdio
22
+ connect_fastapi(app) - Mount FastAPI app as MCP server
23
+ connect_server(server) - Mount any MCPServer/FastMCP directly
24
+
25
+ Inherits connect_mcp() from MCPConfigConnectorMixin.
26
+
27
+ Note: include_router() is inherited from MCPServer (via FastMCP).
28
+ """
29
+
30
+ def connect_image(
31
+ self,
32
+ image: str,
33
+ *,
34
+ alias: str | None = None,
35
+ docker_args: list[str] | None = None,
36
+ env_vars: dict[str, str] | None = None,
37
+ prefix: str | None = None,
38
+ include: list[str] | None = None,
39
+ exclude: list[str] | None = None,
40
+ transform: Callable[[Tool], Tool | None] | None = None,
41
+ ) -> Any:
42
+ """Connect to a Docker image via stdio.
43
+
44
+ Creates an MCP config that runs: docker run -i --rm {image}
45
+ Environment variables from `.env` files are auto-injected.
46
+
47
+ Example:
48
+ ```python
49
+ env = Environment("my-env")
50
+ env.connect_image("mcp/fetch")
51
+
52
+ async with env:
53
+ result = await env.call_tool("fetch", url="https://example.com")
54
+ ```
55
+ """
56
+ from hud.cli.utils.docker import create_docker_run_command
57
+
58
+ cmd = create_docker_run_command(
59
+ image=image,
60
+ docker_args=docker_args,
61
+ extra_env=env_vars,
62
+ interactive=True,
63
+ remove=True,
64
+ )
65
+
66
+ name = alias or image
67
+ mcp_config = {
68
+ name: {
69
+ "command": cmd[0],
70
+ "args": cmd[1:],
71
+ }
72
+ }
73
+ return self.connect_mcp(
74
+ mcp_config,
75
+ alias=name,
76
+ prefix=prefix,
77
+ include=include,
78
+ exclude=exclude,
79
+ transform=transform,
80
+ )
81
+
82
+ def connect_fastapi(
83
+ self,
84
+ app: Any,
85
+ *,
86
+ name: str | None = None,
87
+ prefix: str | None = None,
88
+ include_hidden: bool = True,
89
+ ) -> Any:
90
+ """Import a FastAPI application's routes as MCP tools.
91
+
92
+ Uses FastMCP's from_fastapi() to convert FastAPI endpoints to MCP tools,
93
+ then imports them synchronously so they're available immediately.
94
+
95
+ Args:
96
+ app: FastAPI application instance
97
+ name: Custom name for the server (defaults to app.title)
98
+ prefix: Optional prefix for tool names
99
+ include_hidden: If True (default), includes routes with include_in_schema=False
100
+
101
+ Example:
102
+ ```python
103
+ from fastapi import FastAPI
104
+
105
+ api = FastAPI()
106
+
107
+
108
+ @api.get("/users/{user_id}", operation_id="get_user")
109
+ def get_user(user_id: int):
110
+ return {"id": user_id, "name": "Alice"}
111
+
112
+
113
+ env = Environment("my-env")
114
+ env.connect_fastapi(api)
115
+
116
+ async with env:
117
+ result = await env.call_tool("get_user", user_id=1)
118
+ ```
119
+
120
+ Tip: Use operation_id in FastAPI decorators for cleaner tool names.
121
+ """
122
+ from fastmcp import FastMCP
123
+
124
+ # Temporarily enable hidden routes for OpenAPI generation
125
+ hidden_routes: list[Any] = []
126
+ if include_hidden:
127
+ for route in getattr(app, "routes", []):
128
+ if hasattr(route, "include_in_schema") and not route.include_in_schema:
129
+ hidden_routes.append(route)
130
+ route.include_in_schema = True
131
+ # Clear cached openapi schema so it regenerates
132
+ if hasattr(app, "openapi_schema"):
133
+ app.openapi_schema = None
134
+
135
+ try:
136
+ server_name = name or getattr(app, "title", None) or "fastapi"
137
+ mcp_server = FastMCP.from_fastapi(app=app, name=server_name)
138
+ # Use include_router for synchronous import (tools available immediately)
139
+ self.include_router(mcp_server, prefix=prefix) # type: ignore
140
+ finally:
141
+ # Restore original states
142
+ for route in hidden_routes:
143
+ route.include_in_schema = False
144
+ if hidden_routes and hasattr(app, "openapi_schema"):
145
+ app.openapi_schema = None # Clear cache again
146
+
147
+ return self
148
+
149
+ def connect_server(
150
+ self,
151
+ server: Any,
152
+ *,
153
+ prefix: str | None = None,
154
+ ) -> Any:
155
+ """Import an MCPServer or FastMCP instance's tools directly.
156
+
157
+ Example:
158
+ ```python
159
+ from fastmcp import FastMCP
160
+
161
+ tools = FastMCP("tools")
162
+
163
+
164
+ @tools.tool
165
+ def greet(name: str) -> str:
166
+ return f"Hello, {name}!"
167
+
168
+
169
+ env = Environment("my-env")
170
+ env.connect_server(tools)
171
+
172
+ async with env:
173
+ result = await env.call_tool("greet", name="World")
174
+ ```
175
+ """
176
+ self.include_router(server, prefix=prefix) # type: ignore
177
+ return self
@@ -0,0 +1,109 @@
1
+ """MCP config connection connectors."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from hud.environment.connectors.base import BaseConnectorMixin
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Callable
11
+
12
+ from fastmcp.tools.tool import Tool
13
+
14
+ __all__ = ["MCPConfigConnectorMixin"]
15
+
16
+
17
+ class MCPConfigConnectorMixin(BaseConnectorMixin):
18
+ """Mixin providing mcp_config connection methods."""
19
+
20
+ def connect_mcp(
21
+ self,
22
+ config: dict[str, dict[str, Any]],
23
+ *,
24
+ alias: str | None = None,
25
+ prefix: str | None = None,
26
+ include: list[str] | None = None,
27
+ exclude: list[str] | None = None,
28
+ transform: Callable[[Tool], Tool | None] | None = None,
29
+ ) -> Any:
30
+ """Connect using an mcp_config dictionary (single server).
31
+
32
+ Auto-detects LOCAL (stdio) vs REMOTE (URL) based on config.
33
+
34
+ Example:
35
+ ```python
36
+ env = Environment("my-env")
37
+
38
+ # Stdio server
39
+ env.connect_mcp(
40
+ {
41
+ "filesystem": {
42
+ "command": "npx",
43
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
44
+ }
45
+ }
46
+ )
47
+
48
+ async with env:
49
+ await env.call_tool("read_file", path="/tmp/test.txt")
50
+ ```
51
+ """
52
+ from hud.environment.connection import ConnectionType
53
+
54
+ name = alias or next(iter(config.keys()), "mcp")
55
+ server_config = next(iter(config.values()), {})
56
+
57
+ is_local = "command" in server_config or "args" in server_config
58
+ conn_type = ConnectionType.LOCAL if is_local else ConnectionType.REMOTE
59
+
60
+ return self._add_connection(
61
+ name,
62
+ config,
63
+ connection_type=conn_type,
64
+ prefix=prefix,
65
+ include=include,
66
+ exclude=exclude,
67
+ transform=transform,
68
+ )
69
+
70
+ def connect_mcp_config(
71
+ self,
72
+ mcp_config: dict[str, dict[str, Any]],
73
+ **kwargs: Any,
74
+ ) -> Any:
75
+ """Connect multiple servers from an mcp_config dictionary.
76
+
77
+ Example:
78
+ ```python
79
+ env = Environment("my-env")
80
+
81
+ # Claude Desktop style config
82
+ env.connect_mcp_config(
83
+ {
84
+ "filesystem": {
85
+ "command": "npx",
86
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
87
+ },
88
+ "github": {
89
+ "command": "npx",
90
+ "args": ["-y", "@modelcontextprotocol/server-github"],
91
+ "env": {"GITHUB_TOKEN": "..."},
92
+ },
93
+ }
94
+ )
95
+
96
+ async with env:
97
+ await env.call_tool("read_file", path="/tmp/test.txt")
98
+ await env.call_tool("search_repositories", query="mcp")
99
+ ```
100
+ """
101
+ # Store mcp_config for serialization (v4 format)
102
+ # Merge with existing if called multiple times
103
+ if not hasattr(self, "_mcp_config") or self._mcp_config is None:
104
+ self._mcp_config = {}
105
+ self._mcp_config.update(mcp_config)
106
+
107
+ for server_name, server_config in mcp_config.items():
108
+ self.connect_mcp({server_name: server_config}, alias=server_name, **kwargs)
109
+ return self
@@ -0,0 +1,101 @@
1
+ """OpenAI Agents SDK connectors - import tools from OpenAI agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any
7
+
8
+ __all__ = ["OpenAIConnectorMixin"]
9
+
10
+
11
+ class OpenAIConnectorMixin:
12
+ """Mixin providing OpenAI Agents SDK connector methods."""
13
+
14
+ # These are defined on Environment/MCPServer
15
+ _tool_manager: Any
16
+
17
+ def connect_function_tools(
18
+ self,
19
+ tools: list[Any],
20
+ *,
21
+ prefix: str | None = None,
22
+ ) -> Any:
23
+ """Import FunctionTools from the OpenAI Agents SDK.
24
+
25
+ Wraps each tool so calls go through HUD with telemetry.
26
+
27
+ Example:
28
+ ```python
29
+ from agents import function_tool
30
+
31
+
32
+ @function_tool
33
+ def search(query: str) -> str:
34
+ '''Search for information.'''
35
+ return f"Results for {query}"
36
+
37
+
38
+ @function_tool
39
+ def calculate(expression: str) -> float:
40
+ '''Evaluate a math expression.'''
41
+ return eval(expression)
42
+
43
+
44
+ env = Environment("my-env")
45
+ env.connect_function_tools([search, calculate])
46
+
47
+ async with env:
48
+ result = await env.call_tool("search", query="MCP protocol")
49
+ ```
50
+
51
+ Note:
52
+ Requires `openai-agents`: pip install openai-agents
53
+ """
54
+ try:
55
+ from agents import FunctionTool
56
+ except ImportError as e:
57
+ raise ImportError(
58
+ "openai-agents is required for connect_function_tools. "
59
+ "Install with: pip install openai-agents"
60
+ ) from e
61
+
62
+ for tool in tools:
63
+ if isinstance(tool, FunctionTool):
64
+ self._add_openai_function_tool(tool, prefix)
65
+
66
+ return self
67
+
68
+ def _add_openai_function_tool(self, tool: Any, prefix: str | None) -> None:
69
+ """Convert OpenAI FunctionTool to local MCP tool."""
70
+ name = f"{prefix}_{tool.name}" if prefix else tool.name
71
+
72
+ # Get the original invoke function
73
+ original_invoke = tool.on_invoke_tool
74
+
75
+ # Create wrapper that calls the original
76
+ async def invoke(**arguments: Any) -> Any:
77
+ # OpenAI's on_invoke_tool expects (ToolContext, str_json_args)
78
+ # We need to create a minimal context
79
+ from agents.tool_context import ToolContext
80
+
81
+ ctx = ToolContext(context=None)
82
+ result = await original_invoke(ctx, json.dumps(arguments))
83
+ return result
84
+
85
+ # Set function metadata for FastMCP
86
+ invoke.__name__ = name
87
+ invoke.__doc__ = tool.description
88
+
89
+ # Register using FastMCP's tool decorator mechanism
90
+ # We access the internal _tool_manager from MCPServer
91
+ from fastmcp.tools import Tool as FastMCPTool
92
+
93
+ fastmcp_tool = FastMCPTool.from_function(
94
+ fn=invoke,
95
+ name=name,
96
+ description=tool.description,
97
+ )
98
+ # Override the schema with OpenAI's (more accurate)
99
+ fastmcp_tool.parameters = tool.params_json_schema
100
+
101
+ self._tool_manager.add_tool(fastmcp_tool)
@@ -0,0 +1,172 @@
1
+ """Remote connection connectors - HUD Hub, URL, OpenAPI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import TYPE_CHECKING, Any, cast
7
+
8
+ from hud.environment.connectors.mcp_config import MCPConfigConnectorMixin
9
+
10
+ if TYPE_CHECKING:
11
+ from collections.abc import Callable
12
+
13
+ from fastmcp.tools.tool import Tool
14
+
15
+ __all__ = ["RemoteConnectorMixin"]
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class RemoteConnectorMixin(MCPConfigConnectorMixin):
21
+ """Mixin providing remote connection methods.
22
+
23
+ Note: include_router() is inherited from MCPServer (via FastMCP).
24
+ """
25
+
26
+ def connect_hub(
27
+ self,
28
+ slug: str,
29
+ *,
30
+ alias: str | None = None,
31
+ prefix: str | None = None,
32
+ include: list[str] | None = None,
33
+ exclude: list[str] | None = None,
34
+ transform: Callable[[Tool], Tool | None] | None = None,
35
+ ) -> Any:
36
+ """Connect to a HUD Hub environment.
37
+
38
+ Creates an MCP connection to the HUD API with the hub slug in headers.
39
+
40
+ Example:
41
+ ```python
42
+ env = Environment("my-env")
43
+ env.connect_hub("browser")
44
+
45
+ async with env:
46
+ await env.call_tool("navigate", url="https://google.com")
47
+ ```
48
+ """
49
+ from hud.settings import settings
50
+
51
+ logger.info("Connecting to hub environment: %s", slug)
52
+
53
+ # Store hub config for serialization (v5 format)
54
+ # Note: Only first hub is stored for serialization (task configs use single hub)
55
+ if not hasattr(self, "_hub_config") or self._hub_config is None:
56
+ hub_config: dict[str, Any] = {"name": slug}
57
+ if include:
58
+ hub_config["include"] = include
59
+ if exclude:
60
+ hub_config["exclude"] = exclude
61
+ self._hub_config = hub_config
62
+
63
+ # Create mcp_config with standard MCP URL and hub slug in headers
64
+ # Note: Authorization is injected at request time by httpx/aiohttp hooks
65
+ # in hud.eval.instrument (uses contextvar for api_key).
66
+ mcp_config = {
67
+ "hud": {
68
+ "url": settings.hud_mcp_url,
69
+ "headers": {"Environment-Name": slug},
70
+ }
71
+ }
72
+
73
+ self.connect_mcp_config(
74
+ mcp_config, prefix=prefix, include=include, exclude=exclude, transform=transform
75
+ )
76
+ logger.info("Hub connected: %s", slug)
77
+ return self
78
+
79
+ def connect_url(
80
+ self,
81
+ url: str,
82
+ *,
83
+ headers: dict[str, str] | None = None,
84
+ alias: str | None = None,
85
+ prefix: str | None = None,
86
+ include: list[str] | None = None,
87
+ exclude: list[str] | None = None,
88
+ transform: Callable[[Tool], Tool | None] | None = None,
89
+ ) -> Any:
90
+ """Connect to an MCP server via URL.
91
+
92
+ Example:
93
+ ```python
94
+ env = Environment("my-env")
95
+ env.connect_url(
96
+ "https://mcp.example.com",
97
+ headers={"Authorization": "Bearer token"},
98
+ )
99
+
100
+ async with env:
101
+ await env.call_tool("search", query="hello")
102
+ ```
103
+ """
104
+ from hud.environment.connection import ConnectionType
105
+
106
+ auth = headers.get("Authorization") if headers else None
107
+ return self._add_connection(
108
+ alias or url,
109
+ url,
110
+ connection_type=ConnectionType.REMOTE,
111
+ auth=auth,
112
+ prefix=prefix,
113
+ include=include,
114
+ exclude=exclude,
115
+ transform=transform,
116
+ )
117
+
118
+ def connect_openapi(
119
+ self,
120
+ openapi_spec: dict[str, Any] | str,
121
+ *,
122
+ base_url: str | None = None,
123
+ headers: dict[str, str] | None = None,
124
+ name: str | None = None,
125
+ prefix: str | None = None,
126
+ timeout: float = 30.0,
127
+ ) -> Any:
128
+ """Mount an OpenAPI specification as an MCP server.
129
+
130
+ Converts REST API endpoints to MCP tools. Base URL is auto-inferred
131
+ from the spec URL when possible.
132
+
133
+ Example:
134
+ ```python
135
+ env = Environment("my-env")
136
+ env.connect_openapi("https://petstore.swagger.io/v2/swagger.json")
137
+
138
+ async with env:
139
+ result = await env.call_tool("getPetById", petId=1)
140
+ ```
141
+ """
142
+ from urllib.parse import urlparse
143
+
144
+ import httpx
145
+ from fastmcp import FastMCP
146
+
147
+ if isinstance(openapi_spec, str):
148
+ if openapi_spec.startswith(("http://", "https://")):
149
+ if base_url is None:
150
+ parsed = urlparse(openapi_spec)
151
+ base_url = f"{parsed.scheme}://{parsed.netloc}"
152
+
153
+ resp = httpx.get(openapi_spec, headers=headers)
154
+ resp.raise_for_status()
155
+ openapi_spec = resp.json()
156
+ else:
157
+ import json
158
+
159
+ with open(openapi_spec) as f:
160
+ openapi_spec = json.load(f)
161
+
162
+ if base_url is None:
163
+ raise ValueError("base_url is required when openapi_spec is a dict or file")
164
+
165
+ client = httpx.AsyncClient(base_url=base_url, headers=headers or {}, timeout=timeout)
166
+ mcp_server = FastMCP.from_openapi(
167
+ openapi_spec=cast("dict[str, Any]", openapi_spec),
168
+ client=client,
169
+ name=name or "openapi",
170
+ )
171
+ self.include_router(mcp_server, prefix=prefix) # type: ignore
172
+ return self