hud 0.6.13.dev1__tar.gz → 0.6.13.dev2__tar.gz

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 (297) hide show
  1. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/.gitignore +1 -0
  2. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/PKG-INFO +19 -4
  3. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/README.md +16 -1
  4. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/__init__.py +0 -3
  5. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/browser_use/agent.py +49 -11
  6. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/claude/agent.py +2 -2
  7. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/claude/sdk/agent.py +2 -2
  8. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/claude/sdk/computer_mcp.py +15 -4
  9. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/claude/tools/computer.py +6 -9
  10. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/claude/tools/tests/test_computer.py +43 -12
  11. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/gemini/tools/tests/test_computer.py +28 -9
  12. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai/tools/apply_patch.py +0 -1
  13. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai/tools/computer.py +2 -2
  14. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai/tools/tests/test_computer.py +33 -9
  15. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai_compatible/tools/base.py +1 -12
  16. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai_compatible/tools/mcp_proxy.py +1 -1
  17. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/robot/adapter.py +18 -4
  18. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tests/test_apply_patch.py +0 -1
  19. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tests/test_base.py +1 -1
  20. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tests/test_claude_agent.py +0 -1
  21. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tests/test_claude_sdk_agent.py +92 -3
  22. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tests/test_gemini_agent.py +0 -1
  23. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tests/test_openai_agent.py +0 -1
  24. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tests/test_openai_compatible_agent.py +0 -1
  25. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tests/test_robot_dataset_writer.py +5 -5
  26. hud-0.6.13.dev2/hud/agents/tests/test_tool_agent.py +513 -0
  27. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tool_agent.py +42 -12
  28. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tools/base.py +21 -1
  29. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tools/mcp.py +5 -3
  30. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tools/rfb.py +5 -5
  31. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/types.py +11 -3
  32. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/capabilities/base.py +36 -11
  33. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/capabilities/mcp.py +9 -4
  34. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/capabilities/rfb.py +36 -6
  35. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/capabilities/tests/test_rfb.py +9 -9
  36. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/__init__.py +1 -2
  37. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/cancel.py +4 -2
  38. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/deploy.py +16 -19
  39. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/eval.py +1 -1
  40. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/serve.py +1 -2
  41. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/tests/test_deploy.py +24 -10
  42. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/tests/test_eval_config.py +20 -1
  43. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/source.py +36 -38
  44. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/tests/test_registry.py +3 -3
  45. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/tests/test_source.py +29 -12
  46. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/tests/test_tasks.py +5 -5
  47. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/clients/client.py +150 -94
  48. hud-0.6.13.dev2/hud/clients/tests/test_connect.py +213 -0
  49. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/__init__.py +0 -2
  50. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/egress.py +62 -70
  51. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/env.py +15 -26
  52. hud-0.6.13.dev2/hud/environment/namespace.py +605 -0
  53. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/robot/bridge.py +7 -5
  54. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/robot/gym.py +22 -14
  55. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/server.py +15 -4
  56. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/tests/test_capability_backing.py +12 -9
  57. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/tests/test_loader.py +14 -6
  58. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/tests/test_robot_regressions.py +40 -28
  59. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/tests/test_tunnel.py +23 -11
  60. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/tests/test_workspace.py +385 -134
  61. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/workspace.py +441 -278
  62. hud-0.6.13.dev2/hud/eval/_runtime_protocols.py +212 -0
  63. hud-0.6.13.dev2/hud/eval/compose.py +172 -0
  64. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/run.py +127 -40
  65. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/runtime.py +307 -69
  66. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/sync.py +5 -0
  67. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/task.py +13 -1
  68. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/taskset.py +4 -2
  69. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/tests/test_chat.py +0 -4
  70. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/tests/test_docker_provider.py +280 -21
  71. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/tests/test_file_tracking_observer.py +8 -6
  72. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/tests/test_hosted.py +23 -4
  73. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/tests/test_local_runtime.py +42 -0
  74. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/tests/test_rollout.py +249 -0
  75. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/tests/test_sync.py +28 -4
  76. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/tests/test_task.py +70 -2
  77. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/graders/bash.py +3 -1
  78. hud-0.6.13.dev2/hud/integrations/__init__.py +1 -0
  79. hud-0.6.13.dev2/hud/integrations/harbor/Dockerfile +25 -0
  80. hud-0.6.13.dev2/hud/integrations/harbor/__init__.py +12 -0
  81. hud-0.6.13.dev2/hud/integrations/harbor/adapt.py +734 -0
  82. hud-0.6.13.dev2/hud/integrations/harbor/env.py +678 -0
  83. hud-0.6.13.dev2/hud/integrations/harbor/export.py +233 -0
  84. hud-0.6.13.dev2/hud/integrations/harbor/install.sh +32 -0
  85. hud-0.6.13.dev2/hud/integrations/harbor/tests/conftest.py +135 -0
  86. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/agent-lifecycle/environment/Dockerfile +5 -0
  87. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/agent-lifecycle/instruction.md +1 -0
  88. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/agent-lifecycle/solution/solve.sh +6 -0
  89. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/agent-lifecycle/task.toml +10 -0
  90. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/agent-lifecycle/tests/test.sh +15 -0
  91. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/hello-mcp/environment/Dockerfile +9 -0
  92. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/hello-mcp/environment/compose.yaml +17 -0
  93. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/hello-mcp/environment/mcp-server/Dockerfile +6 -0
  94. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/hello-mcp/environment/mcp-server/server.py +13 -0
  95. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/hello-mcp/instruction.md +1 -0
  96. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/hello-mcp/solution/solve.sh +3 -0
  97. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/hello-mcp/task.toml +10 -0
  98. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/hello-mcp/tests/test.sh +9 -0
  99. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/phase-boundary/environment/Dockerfile +6 -0
  100. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/phase-boundary/environment/entrypoint.sh +7 -0
  101. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/phase-boundary/instruction.md +13 -0
  102. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/phase-boundary/solution/solve.sh +49 -0
  103. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/phase-boundary/task.toml +31 -0
  104. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/phase-boundary/tests/test.sh +33 -0
  105. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/sidecar-reachability/environment/Dockerfile +5 -0
  106. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/sidecar-reachability/environment/compose.yaml +11 -0
  107. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/sidecar-reachability/instruction.md +1 -0
  108. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/sidecar-reachability/solution/solve.sh +26 -0
  109. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/sidecar-reachability/task.toml +26 -0
  110. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/sidecar-reachability/tests/Dockerfile +14 -0
  111. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/sidecar-reachability/tests/test.sh +19 -0
  112. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/verifier-lifecycle/environment/Dockerfile +5 -0
  113. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/verifier-lifecycle/environment/entrypoint.sh +7 -0
  114. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/verifier-lifecycle/instruction.md +1 -0
  115. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/verifier-lifecycle/solution/solve.sh +4 -0
  116. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/verifier-lifecycle/task.toml +17 -0
  117. hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/verifier-lifecycle/tests/test.sh +20 -0
  118. hud-0.6.13.dev2/hud/integrations/harbor/tests/test_contract.py +896 -0
  119. hud-0.6.13.dev2/hud/integrations/harbor/tests/test_harbor.py +267 -0
  120. hud-0.6.13.dev2/hud/integrations/harbor/tests/test_integration.py +339 -0
  121. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/patches/mcp_patches.py +4 -4
  122. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/telemetry/instrument.py +11 -3
  123. hud-0.6.13.dev2/hud/telemetry/tests/__init__.py +0 -0
  124. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/telemetry/tests/test_instrument.py +2 -2
  125. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/types.py +2 -2
  126. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/hints.py +2 -1
  127. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/process.py +3 -1
  128. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/serialization.py +1 -1
  129. hud-0.6.13.dev2/hud/utils/tests/__init__.py +0 -0
  130. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/tests/test_platform.py +5 -1
  131. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/tests/test_process.py +26 -1
  132. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/version.py +1 -1
  133. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/pyproject.toml +19 -18
  134. hud-0.6.13.dev1/hud/_legacy.py +0 -300
  135. hud-0.6.13.dev1/hud/agents/tests/test_tool_agent.py +0 -242
  136. hud-0.6.13.dev1/hud/clients/tests/test_connect.py +0 -111
  137. hud-0.6.13.dev1/hud/environment/legacy.py +0 -364
  138. hud-0.6.13.dev1/hud/environment/tests/test_legacy.py +0 -273
  139. hud-0.6.13.dev1/hud/server.py +0 -32
  140. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/LICENSE +0 -0
  141. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/__main__.py +0 -0
  142. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/__init__.py +0 -0
  143. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/base.py +0 -0
  144. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/browser_use/__init__.py +0 -0
  145. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/claude/__init__.py +0 -0
  146. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/claude/sdk/__init__.py +0 -0
  147. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/claude/tools/__init__.py +0 -0
  148. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/claude/tools/base.py +0 -0
  149. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/claude/tools/coding.py +0 -0
  150. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/claude/tools/hosted.py +0 -0
  151. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/claude/tools/mcp_proxy.py +0 -0
  152. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/claude/tools/settings.py +0 -0
  153. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/claude/tools/tests/__init__.py +0 -0
  154. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/gemini/__init__.py +0 -0
  155. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/gemini/agent.py +0 -0
  156. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/gemini/settings.py +0 -0
  157. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/gemini/tools/__init__.py +0 -0
  158. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/gemini/tools/base.py +0 -0
  159. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/gemini/tools/coding.py +0 -0
  160. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/gemini/tools/computer.py +0 -0
  161. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/gemini/tools/filesystem.py +0 -0
  162. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/gemini/tools/hosted.py +0 -0
  163. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/gemini/tools/mcp_proxy.py +0 -0
  164. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/gemini/tools/tests/__init__.py +0 -0
  165. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/misc/__init__.py +0 -0
  166. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/misc/response_automation.py +0 -0
  167. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai/__init__.py +0 -0
  168. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai/agent.py +0 -0
  169. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai/tools/__init__.py +0 -0
  170. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai/tools/base.py +0 -0
  171. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai/tools/coding.py +0 -0
  172. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai/tools/hosted.py +0 -0
  173. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai/tools/mcp_proxy.py +0 -0
  174. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai/tools/strict_schema.py +0 -0
  175. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai/tools/tests/__init__.py +0 -0
  176. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai/tools/tests/test_strict_schema.py +0 -0
  177. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai_compatible/__init__.py +0 -0
  178. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai_compatible/agent.py +0 -0
  179. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai_compatible/tools/__init__.py +0 -0
  180. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/openai_compatible/tools/filesystem.py +0 -0
  181. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/robot/__init__.py +0 -0
  182. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/robot/agent.py +0 -0
  183. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/robot/batching.py +0 -0
  184. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/robot/dataset.py +0 -0
  185. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/robot/model.py +0 -0
  186. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tests/__init__.py +0 -0
  187. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tests/test_provider_native_tools.py +0 -0
  188. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tests/test_trace.py +0 -0
  189. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tools/__init__.py +0 -0
  190. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tools/hosted.py +0 -0
  191. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/agents/tools/ssh.py +0 -0
  192. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/capabilities/__init__.py +0 -0
  193. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/capabilities/cdp.py +0 -0
  194. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/capabilities/robot.py +0 -0
  195. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/capabilities/ssh.py +0 -0
  196. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/capabilities/tests/__init__.py +0 -0
  197. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/__main__.py +0 -0
  198. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/client.py +0 -0
  199. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/init.py +0 -0
  200. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/jobs.py +0 -0
  201. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/login.py +0 -0
  202. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/models.py +0 -0
  203. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/presets.py +0 -0
  204. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/sync.py +0 -0
  205. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/task.py +0 -0
  206. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/templates.py +0 -0
  207. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/tests/__init__.py +0 -0
  208. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/tests/test_cli_init.py +0 -0
  209. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/tests/test_cli_main.py +0 -0
  210. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/tests/test_cli_more_wrappers.py +0 -0
  211. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/tests/test_eval_bedrock.py +0 -0
  212. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/tests/test_init.py +0 -0
  213. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/tests/test_main_module.py +0 -0
  214. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/tests/test_sync_export.py +0 -0
  215. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/trace.py +0 -0
  216. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/__init__.py +0 -0
  217. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/api.py +0 -0
  218. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/build_display.py +0 -0
  219. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/build_logs.py +0 -0
  220. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/config.py +0 -0
  221. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/context.py +0 -0
  222. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/display.py +0 -0
  223. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/jobs.py +0 -0
  224. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/registry.py +0 -0
  225. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/tasks.py +0 -0
  226. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/tests/__init__.py +0 -0
  227. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/tests/test_build_display.py +0 -0
  228. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/tests/test_config.py +0 -0
  229. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/tests/test_context.py +0 -0
  230. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/tests/test_version_check.py +0 -0
  231. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/cli/utils/version_check.py +0 -0
  232. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/clients/__init__.py +0 -0
  233. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/clients/tests/__init__.py +0 -0
  234. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/conftest.py +0 -0
  235. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/file_tracker.py +0 -0
  236. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/robot/__init__.py +0 -0
  237. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/robot/endpoint.py +0 -0
  238. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/tests/__init__.py +0 -0
  239. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/tests/conftest.py +0 -0
  240. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/tests/test_file_tracker.py +0 -0
  241. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/tests/test_file_tracking.py +0 -0
  242. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/tests/test_manifest.py +0 -0
  243. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/tests/test_server.py +0 -0
  244. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/tests/test_sessions.py +0 -0
  245. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/environment/utils.py +0 -0
  246. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/__init__.py +0 -0
  247. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/chat.py +0 -0
  248. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/docker-seccomp.json +0 -0
  249. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/file_tracking.py +0 -0
  250. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/job.py +0 -0
  251. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/tests/__init__.py +0 -0
  252. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/tests/test_job.py +0 -0
  253. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/eval/tests/test_shared.py +0 -0
  254. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/graders/__init__.py +0 -0
  255. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/graders/base.py +0 -0
  256. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/graders/combine.py +0 -0
  257. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/graders/judge.py +0 -0
  258. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/graders/results.py +0 -0
  259. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/graders/text.py +0 -0
  260. {hud-0.6.13.dev1/hud/telemetry → hud-0.6.13.dev2/hud/integrations/harbor}/tests/__init__.py +0 -0
  261. {hud-0.6.13.dev1/hud/utils/tests → hud-0.6.13.dev2/hud/integrations/harbor/tests/tasks/hello-mcp/environment/mcp-server}/__init__.py +0 -0
  262. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/patches/__init__.py +0 -0
  263. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/patches/tests/__init__.py +0 -0
  264. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/patches/tests/test_warnings.py +0 -0
  265. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/patches/warnings.py +0 -0
  266. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/py.typed +0 -0
  267. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/settings.py +0 -0
  268. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/telemetry/__init__.py +0 -0
  269. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/telemetry/context.py +0 -0
  270. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/telemetry/exporter.py +0 -0
  271. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/telemetry/robot/__init__.py +0 -0
  272. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/telemetry/robot/recorder.py +0 -0
  273. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/telemetry/robot/video.py +0 -0
  274. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/telemetry/span.py +0 -0
  275. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/telemetry/tests/test_exporter.py +0 -0
  276. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/train/__init__.py +0 -0
  277. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/train/base.py +0 -0
  278. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/train/client.py +0 -0
  279. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/train/tests/__init__.py +0 -0
  280. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/train/tests/test_client.py +0 -0
  281. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/train/types.py +0 -0
  282. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/__init__.py +0 -0
  283. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/docker.py +0 -0
  284. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/exceptions.py +0 -0
  285. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/gateway.py +0 -0
  286. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/hud_console.py +0 -0
  287. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/modules.py +0 -0
  288. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/naming.py +0 -0
  289. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/platform.py +0 -0
  290. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/requests.py +0 -0
  291. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/tests/test_exceptions.py +0 -0
  292. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/tests/test_gateway.py +0 -0
  293. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/tests/test_hints.py +0 -0
  294. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/tests/test_hud_console.py +0 -0
  295. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/tests/test_requests.py +0 -0
  296. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/tests/test_serialization.py +0 -0
  297. {hud-0.6.13.dev1 → hud-0.6.13.dev2}/hud/utils/time.py +0 -0
@@ -56,6 +56,7 @@ hud/rl/checkpoints_test/
56
56
 
57
57
  .ck/
58
58
 
59
+ .hud/
59
60
  .hud_eval_config
60
61
  .hud_eval.toml
61
62
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hud
3
- Version: 0.6.13.dev1
3
+ Version: 0.6.13.dev2
4
4
  Summary: SDK for the HUD platform.
5
5
  Project-URL: Homepage, https://github.com/hud-evals/hud-python
6
6
  Project-URL: Bug Tracker, https://github.com/hud-evals/hud-python/issues
@@ -65,12 +65,12 @@ Requires-Dist: dotenv>=0.9.9; extra == 'dev'
65
65
  Requires-Dist: gymnasium>=1.0; extra == 'dev'
66
66
  Requires-Dist: numpy>=1.24; extra == 'dev'
67
67
  Requires-Dist: openpi-client>=0.1.2; extra == 'dev'
68
- Requires-Dist: pyright==1.1.407; extra == 'dev'
69
68
  Requires-Dist: pytest-asyncio; extra == 'dev'
70
69
  Requires-Dist: pytest-cov; extra == 'dev'
71
70
  Requires-Dist: pytest-mock; extra == 'dev'
72
71
  Requires-Dist: pytest>=8.1.1; extra == 'dev'
73
- Requires-Dist: ruff<0.15.0,>=0.11.8; extra == 'dev'
72
+ Requires-Dist: ruff==0.16.1; extra == 'dev'
73
+ Requires-Dist: ty==0.0.67; extra == 'dev'
74
74
  Provides-Extra: modal
75
75
  Requires-Dist: modal>=1.0; extra == 'modal'
76
76
  Provides-Extra: robot
@@ -185,11 +185,13 @@ from hud import Environment
185
185
 
186
186
  env = Environment(name="letter-count")
187
187
 
188
+
188
189
  @env.template()
189
190
  async def count_letter(word: str = "strawberry", letter: str = "r"):
190
191
  answer = yield f"How many '{letter}'s are in '{word}'? Reply with just the number."
191
192
  yield 1.0 if answer and str(word.count(letter)) in answer else 0.0
192
193
 
194
+
193
195
  tasks = [count_letter(word=w) for w in ("strawberry", "raspberry", "blueberry")]
194
196
  ```
195
197
 
@@ -203,6 +205,19 @@ Each graded evaluation is a **trace** (the SDK's live handle is a `Run`). With `
203
205
 
204
206
  → [Quickstart](https://docs.hud.ai/v6/start/quickstart) · [Tasks & tasksets](https://docs.hud.ai/v6/reference/tasks)
205
207
 
208
+ ## Existing benchmarks
209
+
210
+ The experimental `hud.integrations.harbor` adapter turns Harbor task directories into runnable HUD
211
+ tasksets and can export authored HUD tasks back to Harbor folders.
212
+
213
+ ```python
214
+ from hud.integrations import harbor
215
+
216
+ taskset = await harbor.adapt("./benchmark")
217
+ ```
218
+
219
+ → [Harbor interop](https://docs.hud.ai/v6/advanced/harbor-convert)
220
+
206
221
  ## Capabilities & harnesses
207
222
 
208
223
  A **capability** is a connection the environment exposes; a **harness** attaches its own tools to it. The same environment serves a one-shot Q&A or a full computer-use rollout, depending on which capabilities the harness opens.
@@ -269,7 +284,7 @@ Building agents at scale? We work with teams on custom environments, benchmarks,
269
284
 
270
285
  We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md).
271
286
 
272
- Key areas: [Agents](hud/agents/) · [Environments](hud/environment/) · [Capabilities](hud/capabilities/) · [Eval](hud/eval/)
287
+ Key areas: [Agents](hud/agents/) · [Environments](hud/environment/) · [Capabilities](hud/capabilities/) · [Eval](hud/eval/) · [Integrations](hud/integrations/)
273
288
 
274
289
  <a href="https://github.com/hud-evals/hud-python/graphs/contributors">
275
290
  <img src="https://contrib.rocks/image?repo=hud-evals/hud-python&max=50" />
@@ -101,11 +101,13 @@ from hud import Environment
101
101
 
102
102
  env = Environment(name="letter-count")
103
103
 
104
+
104
105
  @env.template()
105
106
  async def count_letter(word: str = "strawberry", letter: str = "r"):
106
107
  answer = yield f"How many '{letter}'s are in '{word}'? Reply with just the number."
107
108
  yield 1.0 if answer and str(word.count(letter)) in answer else 0.0
108
109
 
110
+
109
111
  tasks = [count_letter(word=w) for w in ("strawberry", "raspberry", "blueberry")]
110
112
  ```
111
113
 
@@ -119,6 +121,19 @@ Each graded evaluation is a **trace** (the SDK's live handle is a `Run`). With `
119
121
 
120
122
  → [Quickstart](https://docs.hud.ai/v6/start/quickstart) · [Tasks & tasksets](https://docs.hud.ai/v6/reference/tasks)
121
123
 
124
+ ## Existing benchmarks
125
+
126
+ The experimental `hud.integrations.harbor` adapter turns Harbor task directories into runnable HUD
127
+ tasksets and can export authored HUD tasks back to Harbor folders.
128
+
129
+ ```python
130
+ from hud.integrations import harbor
131
+
132
+ taskset = await harbor.adapt("./benchmark")
133
+ ```
134
+
135
+ → [Harbor interop](https://docs.hud.ai/v6/advanced/harbor-convert)
136
+
122
137
  ## Capabilities & harnesses
123
138
 
124
139
  A **capability** is a connection the environment exposes; a **harness** attaches its own tools to it. The same environment serves a one-shot Q&A or a full computer-use rollout, depending on which capabilities the harness opens.
@@ -185,7 +200,7 @@ Building agents at scale? We work with teams on custom environments, benchmarks,
185
200
 
186
201
  We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md).
187
202
 
188
- Key areas: [Agents](hud/agents/) · [Environments](hud/environment/) · [Capabilities](hud/capabilities/) · [Eval](hud/eval/)
203
+ Key areas: [Agents](hud/agents/) · [Environments](hud/environment/) · [Capabilities](hud/capabilities/) · [Eval](hud/eval/) · [Integrations](hud/integrations/)
189
204
 
190
205
  <a href="https://github.com/hud-evals/hud-python/graphs/contributors">
191
206
  <img src="https://contrib.rocks/image?repo=hud-evals/hud-python&max=50" />
@@ -7,7 +7,6 @@ from __future__ import annotations
7
7
 
8
8
  # Apply patches to third-party libraries early, before other imports
9
9
  from . import patches as _patches # noqa: F401
10
- from ._legacy import install as _install_v5_compat
11
10
  from .clients import connect
12
11
  from .environment import Environment
13
12
  from .eval import (
@@ -34,8 +33,6 @@ from .train import TrainingClient
34
33
  from .types import Trace
35
34
  from .version import __version__
36
35
 
37
- _install_v5_compat()
38
-
39
36
  __all__ = [
40
37
  "Chat",
41
38
  "DockerRuntime",
@@ -15,13 +15,10 @@ optional dependency
15
15
 
16
16
  from __future__ import annotations
17
17
 
18
- # browser-use is an optional, untyped dependency (lazy __getattr__ exports), so
19
- # its symbols and members resolve as Unknown under strict checking. This whole
20
- # module is the boundary around that SDK; contain the unknowns here.
21
- # pyright: reportAttributeAccessIssue=false, reportUnknownVariableType=false, reportUnknownMemberType=false
22
18
  import contextlib
19
+ import importlib
23
20
  import logging
24
- from typing import TYPE_CHECKING, Any, cast
21
+ from typing import TYPE_CHECKING, Protocol, cast
25
22
  from urllib.parse import urlsplit, urlunsplit
26
23
 
27
24
  from hud.agents.base import Agent
@@ -37,6 +34,44 @@ LOGGER = logging.getLogger("hud.agents.browser_use")
37
34
  CDP_PROTOCOL = "cdp/1.3"
38
35
 
39
36
 
37
+ class _Browser(Protocol):
38
+ async def stop(self) -> None: ...
39
+
40
+
41
+ class _History(Protocol):
42
+ def is_successful(self) -> bool | None: ...
43
+
44
+ def final_result(self) -> str | None: ...
45
+
46
+ def number_of_steps(self) -> int: ...
47
+
48
+ def urls(self) -> list[str]: ...
49
+
50
+ def is_done(self) -> bool: ...
51
+
52
+
53
+ class _BrowserAgent(Protocol):
54
+ async def run(self, *, max_steps: int) -> _History: ...
55
+
56
+
57
+ class _ChatAnthropicFactory(Protocol):
58
+ def __call__(self, *, model: str, api_key: str, base_url: str | None) -> object: ...
59
+
60
+
61
+ class _BrowserFactory(Protocol):
62
+ def __call__(self, *, cdp_url: str) -> _Browser: ...
63
+
64
+
65
+ class _AgentFactory(Protocol):
66
+ def __call__(self, *, task: str, llm: object, browser: _Browser) -> _BrowserAgent: ...
67
+
68
+
69
+ class _BrowserUseModule(Protocol):
70
+ ChatAnthropic: _ChatAnthropicFactory
71
+ Browser: _BrowserFactory
72
+ Agent: _AgentFactory
73
+
74
+
40
75
  class BrowserUseAgent(Agent):
41
76
  """Run the ``browser-use`` agent against an env's ``cdp/1.3`` capability."""
42
77
 
@@ -52,8 +87,7 @@ class BrowserUseAgent(Agent):
52
87
  loop, and writes the final answer + trajectory metadata onto ``run.trace``
53
88
  (graded on exit).
54
89
  """
55
- from browser_use import Agent as BrowserUseSdkAgent
56
- from browser_use import Browser, ChatAnthropic
90
+ browser_use = cast("_BrowserUseModule", importlib.import_module("browser_use"))
57
91
 
58
92
  trace = run.trace
59
93
  cdp_url = _ws_to_http(run.client.binding(CDP_PROTOCOL).url)
@@ -63,12 +97,16 @@ class BrowserUseAgent(Agent):
63
97
  if not api_key:
64
98
  raise ValueError("BrowserUseAgent needs an Anthropic API key (set ANTHROPIC_API_KEY)")
65
99
 
66
- llm = ChatAnthropic(model=self.config.model, api_key=api_key, base_url=self.config.base_url)
67
- browser: Any = Browser(cdp_url=cdp_url)
68
- sdk_agent = cast("Any", BrowserUseSdkAgent(task=run.prompt_text, llm=llm, browser=browser))
100
+ llm = browser_use.ChatAnthropic(
101
+ model=self.config.model,
102
+ api_key=api_key,
103
+ base_url=self.config.base_url,
104
+ )
105
+ browser = browser_use.Browser(cdp_url=cdp_url)
106
+ sdk_agent = browser_use.Agent(task=run.prompt_text, llm=llm, browser=browser)
69
107
 
70
108
  try:
71
- history: Any = await sdk_agent.run(max_steps=self.config.max_steps)
109
+ history = await sdk_agent.run(max_steps=self.config.max_steps)
72
110
  except Exception as exc:
73
111
  LOGGER.exception("browser-use run failed")
74
112
  trace.status = "error"
@@ -35,7 +35,7 @@ from .tools.computer import ClaudeComputerTool
35
35
  from .tools.mcp_proxy import ClaudeMCPProxyTool
36
36
 
37
37
  if TYPE_CHECKING:
38
- from anthropic.types.beta import BetaTextBlock, BetaTextCitation
38
+ from anthropic.types.beta import BetaTextCitation
39
39
 
40
40
  logger = logging.getLogger(__name__)
41
41
 
@@ -284,7 +284,7 @@ class ClaudeAgent(ToolAgent[BetaMessageParam, ClaudeConfig]):
284
284
  )
285
285
  result.done = False
286
286
  case "text":
287
- text_block = cast("BetaTextBlock", block)
287
+ text_block = block
288
288
  text_parts.append(text_block.text)
289
289
  citations.extend(self._citation(c) for c in (text_block.citations or []))
290
290
  case "thinking":
@@ -99,7 +99,7 @@ class ClaudeSDKAgent(Agent):
99
99
  family = cap.protocol.split("/", 1)[0]
100
100
  if family == "mcp":
101
101
  token = cap.params.get("auth_token")
102
- transport = "http" if cap.url.startswith("http") else "sse"
102
+ transport = "http" if cap.params["transport"] == "streamable-http" else "sse"
103
103
  server_config: dict[str, Any] = {"type": transport, "url": cap.url}
104
104
  if token:
105
105
  server_config["headers"] = {"Authorization": f"Bearer {token}"}
@@ -108,7 +108,7 @@ class ClaudeSDKAgent(Agent):
108
108
  from hud.agents.claude.sdk.computer_mcp import serve_computer_mcp
109
109
 
110
110
  rfb = cast("RFBClient", await run.client.open("rfb"))
111
- port = await serve_computer_mcp(rfb)
111
+ port = await serve_computer_mcp(rfb, self.config.screenshot_encoding)
112
112
  self._mcp_servers["computer-use"] = {
113
113
  "type": "http",
114
114
  "url": f"http://127.0.0.1:{port}/mcp",
@@ -12,6 +12,8 @@ from typing import TYPE_CHECKING, Any
12
12
 
13
13
  import fastmcp
14
14
 
15
+ from hud.capabilities.rfb import ScreenshotEncoding, WebPScreenshotEncoding
16
+
15
17
  if TYPE_CHECKING:
16
18
  from hud.capabilities.rfb import RFBClient
17
19
 
@@ -19,15 +21,19 @@ logger = logging.getLogger(__name__)
19
21
 
20
22
  #: Keep references to background server tasks so they aren't garbage-collected.
21
23
  _BACKGROUND_TASKS: set[asyncio.Task[None]] = set()
24
+ _DEFAULT_SCREENSHOT_ENCODING = WebPScreenshotEncoding()
22
25
 
23
26
 
24
- def create_computer_mcp(rfb: RFBClient) -> fastmcp.FastMCP:
27
+ def create_computer_mcp(
28
+ rfb: RFBClient,
29
+ screenshot_encoding: ScreenshotEncoding = _DEFAULT_SCREENSHOT_ENCODING,
30
+ ) -> fastmcp.FastMCP:
25
31
  """Build a FastMCP server with one ``computer`` tool backed by ``rfb``."""
26
32
 
27
33
  mcp = fastmcp.FastMCP("computer-use")
28
34
 
29
35
  @mcp.tool()
30
- async def computer( # pyright: ignore[reportUnusedFunction]
36
+ async def computer(
31
37
  action: str,
32
38
  coordinate: str | None = None,
33
39
  text: str | None = None,
@@ -79,7 +85,11 @@ def create_computer_mcp(rfb: RFBClient) -> fastmcp.FastMCP:
79
85
  arguments["region"] = region
80
86
 
81
87
  spec = AgentToolSpec(api_type="computer", api_name="computer")
82
- tool = ClaudeComputerTool(spec=spec, client=rfb)
88
+ tool = ClaudeComputerTool(
89
+ spec=spec,
90
+ client=rfb,
91
+ screenshot_encoding=screenshot_encoding,
92
+ )
83
93
  result = await tool.execute(arguments)
84
94
 
85
95
  # Return content blocks directly so the CLI/model sees real images.
@@ -106,6 +116,7 @@ def create_computer_mcp(rfb: RFBClient) -> fastmcp.FastMCP:
106
116
 
107
117
  async def serve_computer_mcp(
108
118
  rfb: RFBClient,
119
+ screenshot_encoding: ScreenshotEncoding = _DEFAULT_SCREENSHOT_ENCODING,
109
120
  host: str = "127.0.0.1",
110
121
  port: int = 0,
111
122
  ) -> int:
@@ -115,7 +126,7 @@ async def serve_computer_mcp(
115
126
  port = srv.sockets[0].getsockname()[1]
116
127
  srv.close()
117
128
 
118
- mcp = create_computer_mcp(rfb)
129
+ mcp = create_computer_mcp(rfb, screenshot_encoding)
119
130
  task = asyncio.create_task(_run(mcp, host, port))
120
131
  _BACKGROUND_TASKS.add(task)
121
132
  task.add_done_callback(_BACKGROUND_TASKS.discard)
@@ -16,6 +16,7 @@ import mcp.types as mcp_types
16
16
 
17
17
  from hud.agents.tools import RFBTool
18
18
  from hud.agents.tools.base import tool_err, tool_ok
19
+ from hud.capabilities.rfb import ScreenshotEncoding, WebPScreenshotEncoding
19
20
  from hud.types import MCPToolResult
20
21
 
21
22
  from .base import ClaudeToolSpec
@@ -279,11 +280,7 @@ class ClaudeComputerTool(RFBTool):
279
280
  except (TypeError, ValueError):
280
281
  return tool_err("region must contain 4 integers")
281
282
  screenshot, _ = await self.client.screenshot_png("image/png")
282
- cropped, mime_type = _crop_png(
283
- screenshot,
284
- (x0, y0, x1, y1),
285
- self.screenshot_mime_type,
286
- )
283
+ cropped, mime_type = _crop_png(screenshot, (x0, y0, x1, y1), self.screenshot_encoding)
287
284
  return MCPToolResult(
288
285
  content=[
289
286
  mcp_types.ImageContent(
@@ -356,18 +353,18 @@ def _drag_path(arguments: dict[str, Any]) -> list[tuple[int, int]]:
356
353
  def _crop_png(
357
354
  png: bytes,
358
355
  region: tuple[int, int, int, int],
359
- mime_type: str,
356
+ encoding: ScreenshotEncoding,
360
357
  ) -> tuple[bytes, str]:
361
358
  from PIL import Image
362
359
 
363
360
  image = Image.open(BytesIO(png))
364
361
  cropped = image.crop(region)
365
362
  buf = BytesIO()
366
- if mime_type == "image/webp":
367
- cropped.save(buf, format="WEBP", quality=85)
363
+ if isinstance(encoding, WebPScreenshotEncoding):
364
+ cropped.save(buf, format="WEBP", quality=encoding.quality)
368
365
  else:
369
366
  cropped.save(buf, format="PNG")
370
- return buf.getvalue(), mime_type
367
+ return buf.getvalue(), encoding.mime_type
371
368
 
372
369
 
373
370
  __all__ = ["CLAUDE_COMPUTER_SPECS", "ClaudeComputerTool"]
@@ -1,13 +1,12 @@
1
1
  """``ClaudeComputerTool`` — key translation, per-model spec gating, and the
2
2
  computer-use action dispatch (translation to RFB primitives), without a live VNC.
3
3
  """
4
- # pyright: reportPrivateUsage=false, reportIncompatibleMethodOverride=false
5
4
 
6
5
  from __future__ import annotations
7
6
 
8
7
  from io import BytesIO
9
8
  from types import SimpleNamespace
10
- from typing import Any
9
+ from typing import TYPE_CHECKING, Any
11
10
  from unittest.mock import AsyncMock, patch
12
11
 
13
12
  import mcp.types as mcp_types
@@ -21,7 +20,11 @@ from hud.agents.claude.tools.computer import (
21
20
  _split_keys,
22
21
  _translate_key,
23
22
  )
23
+
24
+ if TYPE_CHECKING:
25
+ from collections.abc import Iterable
24
26
  from hud.agents.tools.base import result_text, tool_ok
27
+ from hud.capabilities.rfb import WebPScreenshotEncoding
25
28
 
26
29
 
27
30
  class RecordingComputer(ClaudeComputerTool):
@@ -31,23 +34,41 @@ class RecordingComputer(ClaudeComputerTool):
31
34
 
32
35
  def __init__(self) -> None:
33
36
  self.calls: list[tuple[Any, ...]] = []
34
- self.screenshot_mime_type = "image/webp"
37
+ self.screenshot_encoding = WebPScreenshotEncoding(quality=42)
35
38
  self.client = SimpleNamespace(width=200, height=100)
36
39
 
37
40
  async def screenshot(self) -> Any:
38
41
  self.calls.append(("screenshot",))
39
42
  return tool_ok("shot")
40
43
 
41
- async def click(self, x: Any, y: Any, **kw: Any) -> None:
44
+ async def click(
45
+ self,
46
+ x: int | None = None,
47
+ y: int | None = None,
48
+ *,
49
+ button: Any = "left",
50
+ hold_keys: Iterable[str] | None = None,
51
+ count: int = 1,
52
+ interval_ms: int = 0,
53
+ ) -> None:
54
+ kw: dict[str, Any] = {}
55
+ if button != "left":
56
+ kw["button"] = button
57
+ if hold_keys is not None:
58
+ kw["hold_keys"] = hold_keys
59
+ if count != 1:
60
+ kw["count"] = count
61
+ if interval_ms:
62
+ kw["interval_ms"] = interval_ms
42
63
  self.calls.append(("click", x, y, kw))
43
64
 
44
65
  async def move(self, x: Any, y: Any) -> None:
45
66
  self.calls.append(("move", x, y))
46
67
 
47
- async def mouse_down(self, button: Any) -> None:
68
+ async def mouse_down(self, button: Any = "left") -> None:
48
69
  self.calls.append(("down", button))
49
70
 
50
- async def mouse_up(self, button: Any) -> None:
71
+ async def mouse_up(self, button: Any = "left") -> None:
51
72
  self.calls.append(("up", button))
52
73
 
53
74
  async def type_text(self, text: Any) -> None:
@@ -59,15 +80,21 @@ class RecordingComputer(ClaudeComputerTool):
59
80
  async def hold_key(self, key: Any, **kw: Any) -> None:
60
81
  self.calls.append(("hold", key, kw))
61
82
 
62
- async def scroll(self, x: Any, y: Any, **kw: Any) -> None:
83
+ async def scroll(
84
+ self,
85
+ x: int | None = None,
86
+ y: int | None = None,
87
+ *,
88
+ scroll_x: int = 0,
89
+ scroll_y: int = 0,
90
+ hold_keys: Iterable[str] | None = None,
91
+ ) -> None:
92
+ kw = {"scroll_x": scroll_x, "scroll_y": scroll_y, "hold_keys": hold_keys}
63
93
  self.calls.append(("scroll", x, y, kw))
64
94
 
65
95
  async def drag(self, path: Any, **kw: Any) -> None:
66
96
  self.calls.append(("drag", tuple(path), kw))
67
97
 
68
- async def wait(self, ms: Any) -> None:
69
- self.calls.append(("wait", ms))
70
-
71
98
 
72
99
  # ─── key translation helpers ──────────────────────────────────────────
73
100
 
@@ -160,7 +187,11 @@ def test_crop_reports_encoded_mime_type() -> None:
160
187
  source = BytesIO()
161
188
  Image.effect_noise((128, 128), 100).convert("RGB").save(source, format="PNG")
162
189
 
163
- cropped, mime_type = _crop_png(source.getvalue(), (0, 0, 128, 128), "image/webp")
190
+ cropped, mime_type = _crop_png(
191
+ source.getvalue(),
192
+ (0, 0, 128, 128),
193
+ WebPScreenshotEncoding(quality=42),
194
+ )
164
195
 
165
196
  assert mime_type == "image/webp"
166
197
  assert cropped.startswith(b"RIFF")
@@ -177,6 +208,6 @@ async def test_zoom_reports_encoded_mime_type() -> None:
177
208
  result = await tool._zoom({"region": [0, 0, 10, 10]})
178
209
 
179
210
  tool.client.screenshot_png.assert_awaited_once_with("image/png")
180
- crop.assert_called_once_with(b"png", (0, 0, 10, 10), "image/webp")
211
+ crop.assert_called_once_with(b"png", (0, 0, 10, 10), tool.screenshot_encoding)
181
212
  image = next(block for block in result.content if isinstance(block, mcp_types.ImageContent))
182
213
  assert image.mimeType == "image/webp"
@@ -2,16 +2,19 @@
2
2
 
3
3
  No live VNC: a recording subclass captures the primitive calls.
4
4
  """
5
- # pyright: reportPrivateUsage=false, reportIncompatibleMethodOverride=false
6
5
 
7
6
  from __future__ import annotations
8
7
 
9
8
  from types import SimpleNamespace
10
- from typing import Any
9
+ from typing import TYPE_CHECKING, Any
10
+ from unittest.mock import AsyncMock, patch
11
11
 
12
12
  from hud.agents.gemini.tools.computer import GEMINI_COMPUTER_SPEC, GeminiComputerTool
13
13
  from hud.agents.tools.base import tool_ok
14
14
 
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Iterable
17
+
15
18
 
16
19
  class RecordingGemini(GeminiComputerTool):
17
20
  client: Any
@@ -25,7 +28,16 @@ class RecordingGemini(GeminiComputerTool):
25
28
  self.calls.append(("screenshot",))
26
29
  return tool_ok("shot")
27
30
 
28
- async def click(self, x: Any, y: Any, **kw: Any) -> None:
31
+ async def click(
32
+ self,
33
+ x: int | None = None,
34
+ y: int | None = None,
35
+ *,
36
+ button: Any = "left",
37
+ hold_keys: Iterable[str] | None = None,
38
+ count: int = 1,
39
+ interval_ms: int = 0,
40
+ ) -> None:
29
41
  self.calls.append(("click", x, y))
30
42
 
31
43
  async def move(self, x: Any, y: Any) -> None:
@@ -37,15 +49,21 @@ class RecordingGemini(GeminiComputerTool):
37
49
  async def press_keys(self, keys: Any, **kw: Any) -> None:
38
50
  self.calls.append(("keys", tuple(keys)))
39
51
 
40
- async def scroll(self, x: Any, y: Any, **kw: Any) -> None:
52
+ async def scroll(
53
+ self,
54
+ x: int | None = None,
55
+ y: int | None = None,
56
+ *,
57
+ scroll_x: int = 0,
58
+ scroll_y: int = 0,
59
+ hold_keys: Iterable[str] | None = None,
60
+ ) -> None:
61
+ kw = {"scroll_x": scroll_x, "scroll_y": scroll_y, "hold_keys": hold_keys}
41
62
  self.calls.append(("scroll", x, y, kw))
42
63
 
43
64
  async def drag(self, path: Any, **kw: Any) -> None:
44
65
  self.calls.append(("drag", tuple(path)))
45
66
 
46
- async def wait(self, ms: Any) -> None:
47
- self.calls.append(("wait", ms))
48
-
49
67
 
50
68
  def test_default_spec() -> None:
51
69
  assert GeminiComputerTool.default_spec("any") is GEMINI_COMPUTER_SPEC
@@ -87,11 +105,12 @@ async def test_key_combination_requires_string() -> None:
87
105
 
88
106
  async def test_wait_and_drag() -> None:
89
107
  tool = RecordingGemini()
90
- await tool.execute({"action": "wait_5_seconds"})
108
+ with patch("hud.agents.tools.rfb.asyncio.sleep", new_callable=AsyncMock) as sleep:
109
+ await tool.execute({"action": "wait_5_seconds"})
91
110
  await tool.execute(
92
111
  {"action": "drag_and_drop", "x": 50, "y": 50, "destination_x": 100, "destination_y": 100}
93
112
  )
94
- assert ("wait", 5000) in tool.calls
113
+ sleep.assert_awaited_once_with(5)
95
114
  assert any(c[0] == "drag" for c in tool.calls)
96
115
 
97
116
 
@@ -1,4 +1,3 @@
1
- # pyright: reportUnusedFunction=false
2
1
  """OpenAI apply_patch parser helpers."""
3
2
 
4
3
  from __future__ import annotations
@@ -148,14 +148,14 @@ class OpenAIComputerTool(RFBTool):
148
148
  if button_raw == "wheel":
149
149
  button = "middle"
150
150
  elif isinstance(button_raw, str):
151
- button = button_raw # type: ignore[assignment]
151
+ button = button_raw
152
152
  else:
153
153
  button = "left"
154
154
  hold = _hold_keys(args.get("keys"))
155
155
  await self.click(
156
156
  args.get("x"),
157
157
  args.get("y"),
158
- button=button, # type: ignore[arg-type]
158
+ button=button,
159
159
  hold_keys=hold,
160
160
  )
161
161
 
@@ -2,12 +2,12 @@
2
2
 
3
3
  No live VNC: a recording subclass captures the primitive calls.
4
4
  """
5
- # pyright: reportPrivateUsage=false, reportIncompatibleMethodOverride=false
6
5
 
7
6
  from __future__ import annotations
8
7
 
9
8
  from types import SimpleNamespace
10
- from typing import Any
9
+ from typing import TYPE_CHECKING, Any
10
+ from unittest.mock import AsyncMock, patch
11
11
 
12
12
  from hud.agents.openai.tools.computer import (
13
13
  OpenAIComputerTool,
@@ -16,6 +16,9 @@ from hud.agents.openai.tools.computer import (
16
16
  )
17
17
  from hud.agents.tools.base import result_text, tool_ok
18
18
 
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Iterable
21
+
19
22
 
20
23
  class RecordingOpenAI(OpenAIComputerTool):
21
24
  client: Any
@@ -28,7 +31,21 @@ class RecordingOpenAI(OpenAIComputerTool):
28
31
  self.calls.append(("screenshot",))
29
32
  return tool_ok("shot")
30
33
 
31
- async def click(self, x: Any, y: Any, **kw: Any) -> None:
34
+ async def click(
35
+ self,
36
+ x: int | None = None,
37
+ y: int | None = None,
38
+ *,
39
+ button: Any = "left",
40
+ hold_keys: Iterable[str] | None = None,
41
+ count: int = 1,
42
+ interval_ms: int = 0,
43
+ ) -> None:
44
+ kw = {"button": button, "hold_keys": hold_keys}
45
+ if count != 1:
46
+ kw["count"] = count
47
+ if interval_ms:
48
+ kw["interval_ms"] = interval_ms
32
49
  self.calls.append(("click", x, y, kw))
33
50
 
34
51
  async def move(self, x: Any, y: Any) -> None:
@@ -40,15 +57,21 @@ class RecordingOpenAI(OpenAIComputerTool):
40
57
  async def press_keys(self, keys: Any, **kw: Any) -> None:
41
58
  self.calls.append(("keys", tuple(keys)))
42
59
 
43
- async def scroll(self, x: Any, y: Any, **kw: Any) -> None:
60
+ async def scroll(
61
+ self,
62
+ x: int | None = None,
63
+ y: int | None = None,
64
+ *,
65
+ scroll_x: int = 0,
66
+ scroll_y: int = 0,
67
+ hold_keys: Iterable[str] | None = None,
68
+ ) -> None:
69
+ kw = {"scroll_x": scroll_x, "scroll_y": scroll_y, "hold_keys": hold_keys}
44
70
  self.calls.append(("scroll", x, y, kw))
45
71
 
46
72
  async def drag(self, path: Any, **kw: Any) -> None:
47
73
  self.calls.append(("drag", tuple(path)))
48
74
 
49
- async def wait(self, ms: Any) -> None:
50
- self.calls.append(("wait", ms))
51
-
52
75
 
53
76
  def test_key_mapping() -> None:
54
77
  assert _map_key("ctrl") == "Control_L"
@@ -79,9 +102,10 @@ async def test_type_and_keypress() -> None:
79
102
  async def test_drag_and_wait() -> None:
80
103
  tool = RecordingOpenAI()
81
104
  await tool.execute({"type": "drag", "path": [{"x": 0, "y": 0}, {"x": 5, "y": 5}]})
82
- await tool.execute({"type": "wait", "ms": 500})
105
+ with patch("hud.agents.tools.rfb.asyncio.sleep", new_callable=AsyncMock) as sleep:
106
+ await tool.execute({"type": "wait", "ms": 500})
83
107
  assert ("drag", ((0, 0), (5, 5))) in tool.calls
84
- assert ("wait", 500) in tool.calls
108
+ sleep.assert_awaited_once_with(0.5)
85
109
 
86
110
 
87
111
  async def test_response_action_returns_text() -> None: