hud 0.6.16.dev2__tar.gz → 0.6.16.dev4__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 (313) hide show
  1. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/PKG-INFO +2 -1
  2. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/claude/agent.py +114 -9
  3. hud-0.6.16.dev4/hud/agents/claude/tools/base.py +26 -0
  4. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/claude/tools/coding.py +89 -9
  5. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/claude/tools/computer.py +102 -4
  6. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/claude/tools/tests/test_computer.py +18 -4
  7. hud-0.6.16.dev4/hud/agents/tests/test_claude_agent.py +303 -0
  8. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tests/test_provider_native_tools.py +19 -0
  9. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/eval.py +2 -1
  10. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/jobs.py +2 -1
  11. hud-0.6.16.dev4/hud/cli/qa.py +419 -0
  12. hud-0.6.16.dev4/hud/cli/qa_analysis.py +207 -0
  13. hud-0.6.16.dev4/hud/cli/tests/test_jobs.py +38 -0
  14. hud-0.6.16.dev4/hud/cli/tests/test_qa.py +474 -0
  15. hud-0.6.16.dev4/hud/cli/tests/test_qa_analysis.py +87 -0
  16. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/trace.py +2 -2
  17. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/egress.py +10 -8
  18. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/namespace.py +3 -1
  19. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/robot/gym.py +1 -1
  20. hud-0.6.16.dev4/hud/environment/tests/test_platform.py +52 -0
  21. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/tests/test_robot_regressions.py +52 -0
  22. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/tests/test_workspace.py +3 -1
  23. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/job.py +2 -2
  24. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/run.py +8 -7
  25. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/runtime/__init__.py +0 -2
  26. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/runtime/core.py +12 -26
  27. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/runtime/daytona.py +22 -0
  28. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/runtime/docker.py +50 -19
  29. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/runtime/local.py +60 -25
  30. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/runtime/modal.py +52 -21
  31. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/tests/test_docker_provider.py +185 -7
  32. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/tests/test_job.py +19 -0
  33. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/tests/test_local_runtime.py +71 -1
  34. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/tests/test_rollout.py +17 -16
  35. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/telemetry/robot/recorder.py +11 -3
  36. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/platform.py +12 -0
  37. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/process.py +77 -1
  38. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/requests.py +9 -2
  39. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/tests/test_platform.py +18 -2
  40. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/tests/test_process.py +39 -1
  41. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/tests/test_requests.py +16 -0
  42. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/version.py +1 -1
  43. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/pyproject.toml +1 -0
  44. hud-0.6.16.dev2/hud/agents/claude/tools/base.py +0 -17
  45. hud-0.6.16.dev2/hud/agents/tests/test_claude_agent.py +0 -144
  46. hud-0.6.16.dev2/hud/cli/qa.py +0 -193
  47. hud-0.6.16.dev2/hud/cli/tests/test_qa.py +0 -171
  48. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/.gitignore +0 -0
  49. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/LICENSE +0 -0
  50. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/README.md +0 -0
  51. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/__init__.py +0 -0
  52. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/__main__.py +0 -0
  53. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/__init__.py +0 -0
  54. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/base.py +0 -0
  55. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/browser_use/__init__.py +0 -0
  56. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/browser_use/agent.py +0 -0
  57. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/claude/__init__.py +0 -0
  58. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/claude/sdk/__init__.py +0 -0
  59. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/claude/sdk/agent.py +0 -0
  60. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/claude/sdk/computer_mcp.py +0 -0
  61. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/claude/tools/__init__.py +0 -0
  62. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/claude/tools/hosted.py +0 -0
  63. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/claude/tools/mcp_proxy.py +0 -0
  64. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/claude/tools/settings.py +0 -0
  65. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/claude/tools/tests/__init__.py +0 -0
  66. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/gemini/__init__.py +0 -0
  67. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/gemini/agent.py +0 -0
  68. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/gemini/settings.py +0 -0
  69. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/gemini/tools/__init__.py +0 -0
  70. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/gemini/tools/base.py +0 -0
  71. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/gemini/tools/coding.py +0 -0
  72. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/gemini/tools/computer.py +0 -0
  73. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/gemini/tools/filesystem.py +0 -0
  74. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/gemini/tools/hosted.py +0 -0
  75. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/gemini/tools/mcp_proxy.py +0 -0
  76. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/gemini/tools/tests/__init__.py +0 -0
  77. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/gemini/tools/tests/test_computer.py +0 -0
  78. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/misc/__init__.py +0 -0
  79. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/misc/response_automation.py +0 -0
  80. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai/__init__.py +0 -0
  81. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai/agent.py +0 -0
  82. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai/tools/__init__.py +0 -0
  83. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai/tools/apply_patch.py +0 -0
  84. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai/tools/base.py +0 -0
  85. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai/tools/coding.py +0 -0
  86. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai/tools/computer.py +0 -0
  87. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai/tools/hosted.py +0 -0
  88. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai/tools/mcp_proxy.py +0 -0
  89. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai/tools/strict_schema.py +0 -0
  90. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai/tools/tests/__init__.py +0 -0
  91. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai/tools/tests/test_computer.py +0 -0
  92. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai/tools/tests/test_strict_schema.py +0 -0
  93. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai_compatible/__init__.py +0 -0
  94. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai_compatible/agent.py +0 -0
  95. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai_compatible/tools/__init__.py +0 -0
  96. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai_compatible/tools/base.py +0 -0
  97. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai_compatible/tools/filesystem.py +0 -0
  98. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/openai_compatible/tools/mcp_proxy.py +0 -0
  99. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/robot/__init__.py +0 -0
  100. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/robot/adapter.py +0 -0
  101. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/robot/agent.py +0 -0
  102. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/robot/batching.py +0 -0
  103. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/robot/dataset.py +0 -0
  104. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/robot/model.py +0 -0
  105. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tests/__init__.py +0 -0
  106. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tests/test_apply_patch.py +0 -0
  107. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tests/test_base.py +0 -0
  108. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tests/test_claude_sdk_agent.py +0 -0
  109. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tests/test_gemini_agent.py +0 -0
  110. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tests/test_openai_agent.py +0 -0
  111. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tests/test_openai_compatible_agent.py +0 -0
  112. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tests/test_robot_dataset_writer.py +0 -0
  113. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tests/test_tool_agent.py +0 -0
  114. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tests/test_trace.py +0 -0
  115. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tool_agent.py +0 -0
  116. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tools/__init__.py +0 -0
  117. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tools/base.py +0 -0
  118. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tools/hosted.py +0 -0
  119. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tools/mcp.py +0 -0
  120. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tools/rfb.py +0 -0
  121. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/tools/ssh.py +0 -0
  122. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/agents/types.py +0 -0
  123. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/capabilities/__init__.py +0 -0
  124. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/capabilities/base.py +0 -0
  125. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/capabilities/cdp.py +0 -0
  126. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/capabilities/mcp.py +0 -0
  127. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/capabilities/rfb.py +0 -0
  128. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/capabilities/robot.py +0 -0
  129. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/capabilities/ssh.py +0 -0
  130. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/capabilities/tests/__init__.py +0 -0
  131. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/capabilities/tests/test_rfb.py +0 -0
  132. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/capabilities/tests/test_ssh.py +0 -0
  133. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/__init__.py +0 -0
  134. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/__main__.py +0 -0
  135. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/cancel.py +0 -0
  136. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/client.py +0 -0
  137. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/deploy.py +0 -0
  138. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/init.py +0 -0
  139. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/login.py +0 -0
  140. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/models.py +0 -0
  141. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/presets.py +0 -0
  142. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/serve.py +0 -0
  143. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/sync.py +0 -0
  144. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/task.py +0 -0
  145. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/templates.py +0 -0
  146. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/tests/__init__.py +0 -0
  147. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/tests/test_cli_init.py +0 -0
  148. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/tests/test_cli_main.py +0 -0
  149. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/tests/test_cli_more_wrappers.py +0 -0
  150. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/tests/test_deploy.py +0 -0
  151. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/tests/test_eval_bedrock.py +0 -0
  152. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/tests/test_eval_config.py +0 -0
  153. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/tests/test_init.py +0 -0
  154. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/tests/test_main_module.py +0 -0
  155. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/tests/test_sync_export.py +0 -0
  156. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/tests/test_trace.py +0 -0
  157. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/tests/test_usage.py +0 -0
  158. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/__init__.py +0 -0
  159. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/api.py +0 -0
  160. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/build_display.py +0 -0
  161. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/build_logs.py +0 -0
  162. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/config.py +0 -0
  163. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/context.py +0 -0
  164. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/display.py +0 -0
  165. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/jobs.py +0 -0
  166. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/registry.py +0 -0
  167. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/source.py +0 -0
  168. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/tasks.py +0 -0
  169. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/tests/__init__.py +0 -0
  170. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/tests/test_build_display.py +0 -0
  171. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/tests/test_config.py +0 -0
  172. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/tests/test_context.py +0 -0
  173. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/tests/test_registry.py +0 -0
  174. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/tests/test_source.py +0 -0
  175. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/tests/test_tasks.py +0 -0
  176. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/tests/test_version_check.py +0 -0
  177. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/usage.py +0 -0
  178. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/cli/utils/version_check.py +0 -0
  179. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/clients/__init__.py +0 -0
  180. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/clients/client.py +0 -0
  181. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/clients/tests/__init__.py +0 -0
  182. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/clients/tests/test_connect.py +0 -0
  183. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/conftest.py +0 -0
  184. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/__init__.py +0 -0
  185. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/arguments.py +0 -0
  186. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/env.py +0 -0
  187. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/file_tracker.py +0 -0
  188. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/robot/__init__.py +0 -0
  189. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/robot/bridge.py +0 -0
  190. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/robot/endpoint.py +0 -0
  191. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/server.py +0 -0
  192. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/tests/__init__.py +0 -0
  193. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/tests/conftest.py +0 -0
  194. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/tests/test_arguments.py +0 -0
  195. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/tests/test_capability_backing.py +0 -0
  196. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/tests/test_file_tracker.py +0 -0
  197. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/tests/test_file_tracking.py +0 -0
  198. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/tests/test_loader.py +0 -0
  199. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/tests/test_manifest.py +0 -0
  200. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/tests/test_server.py +0 -0
  201. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/tests/test_sessions.py +0 -0
  202. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/tests/test_tunnel.py +0 -0
  203. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/utils.py +0 -0
  204. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/environment/workspace.py +0 -0
  205. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/__init__.py +0 -0
  206. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/chat.py +0 -0
  207. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/docker-seccomp.json +0 -0
  208. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/file_tracking.py +0 -0
  209. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/runtime/compose.py +0 -0
  210. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/runtime/hosted.py +0 -0
  211. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/runtime/hud.py +0 -0
  212. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/sync.py +0 -0
  213. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/task.py +0 -0
  214. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/taskset.py +0 -0
  215. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/tests/__init__.py +0 -0
  216. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/tests/test_chat.py +0 -0
  217. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/tests/test_file_tracking_observer.py +0 -0
  218. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/tests/test_hosted.py +0 -0
  219. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/tests/test_shared.py +0 -0
  220. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/tests/test_sync.py +0 -0
  221. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/eval/tests/test_task.py +0 -0
  222. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/graders/__init__.py +0 -0
  223. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/graders/base.py +0 -0
  224. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/graders/bash.py +0 -0
  225. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/graders/combine.py +0 -0
  226. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/graders/judge.py +0 -0
  227. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/graders/results.py +0 -0
  228. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/graders/text.py +0 -0
  229. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/__init__.py +0 -0
  230. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/Dockerfile +0 -0
  231. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/__init__.py +0 -0
  232. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/adapt.py +0 -0
  233. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/env.py +0 -0
  234. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/export.py +0 -0
  235. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/install.sh +0 -0
  236. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/__init__.py +0 -0
  237. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/conftest.py +0 -0
  238. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/agent-lifecycle/environment/Dockerfile +0 -0
  239. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/agent-lifecycle/instruction.md +0 -0
  240. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/agent-lifecycle/solution/solve.sh +0 -0
  241. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/agent-lifecycle/task.toml +0 -0
  242. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/agent-lifecycle/tests/test.sh +0 -0
  243. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/hello-mcp/environment/Dockerfile +0 -0
  244. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/hello-mcp/environment/docker-compose.yaml +0 -0
  245. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/hello-mcp/environment/mcp-server/Dockerfile +0 -0
  246. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/hello-mcp/environment/mcp-server/__init__.py +0 -0
  247. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/hello-mcp/environment/mcp-server/server.py +0 -0
  248. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/hello-mcp/instruction.md +0 -0
  249. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/hello-mcp/solution/solve.sh +0 -0
  250. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/hello-mcp/task.toml +0 -0
  251. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/hello-mcp/tests/test.sh +0 -0
  252. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/phase-boundary/environment/Dockerfile +0 -0
  253. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/phase-boundary/environment/entrypoint.sh +0 -0
  254. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/phase-boundary/instruction.md +0 -0
  255. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/phase-boundary/solution/solve.sh +0 -0
  256. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/phase-boundary/task.toml +0 -0
  257. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/phase-boundary/tests/test.sh +0 -0
  258. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/sidecar-reachability/environment/Dockerfile +0 -0
  259. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/sidecar-reachability/environment/docker-compose.yaml +0 -0
  260. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/sidecar-reachability/instruction.md +0 -0
  261. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/sidecar-reachability/solution/solve.sh +0 -0
  262. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/sidecar-reachability/task.toml +0 -0
  263. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/sidecar-reachability/tests/Dockerfile +0 -0
  264. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/sidecar-reachability/tests/test.sh +0 -0
  265. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/environment/Dockerfile +0 -0
  266. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/environment/entrypoint.sh +0 -0
  267. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/instruction.md +0 -0
  268. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/solution/solve.sh +0 -0
  269. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/task.toml +0 -0
  270. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/tests/test.sh +0 -0
  271. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/test_contract.py +0 -0
  272. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/test_harbor.py +0 -0
  273. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/integrations/harbor/tests/test_integration.py +0 -0
  274. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/patches/__init__.py +0 -0
  275. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/patches/mcp_patches.py +0 -0
  276. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/patches/tests/__init__.py +0 -0
  277. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/patches/tests/test_warnings.py +0 -0
  278. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/patches/warnings.py +0 -0
  279. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/py.typed +0 -0
  280. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/settings.py +0 -0
  281. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/telemetry/__init__.py +0 -0
  282. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/telemetry/context.py +0 -0
  283. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/telemetry/exporter.py +0 -0
  284. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/telemetry/instrument.py +0 -0
  285. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/telemetry/robot/__init__.py +0 -0
  286. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/telemetry/robot/video.py +0 -0
  287. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/telemetry/span.py +0 -0
  288. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/telemetry/tests/__init__.py +0 -0
  289. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/telemetry/tests/test_exporter.py +0 -0
  290. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/telemetry/tests/test_instrument.py +0 -0
  291. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/train/__init__.py +0 -0
  292. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/train/base.py +0 -0
  293. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/train/client.py +0 -0
  294. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/train/tests/__init__.py +0 -0
  295. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/train/tests/test_client.py +0 -0
  296. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/train/types.py +0 -0
  297. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/types.py +0 -0
  298. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/__init__.py +0 -0
  299. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/docker.py +0 -0
  300. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/exceptions.py +0 -0
  301. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/gateway.py +0 -0
  302. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/hints.py +0 -0
  303. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/hud_console.py +0 -0
  304. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/modules.py +0 -0
  305. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/naming.py +0 -0
  306. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/serialization.py +0 -0
  307. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/tests/__init__.py +0 -0
  308. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/tests/test_exceptions.py +0 -0
  309. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/tests/test_gateway.py +0 -0
  310. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/tests/test_hints.py +0 -0
  311. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/tests/test_hud_console.py +0 -0
  312. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/tests/test_serialization.py +0 -0
  313. {hud-0.6.16.dev2 → hud-0.6.16.dev4}/hud/utils/time.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hud
3
- Version: 0.6.16.dev2
3
+ Version: 0.6.16.dev4
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
@@ -39,6 +39,7 @@ Requires-Dist: asyncssh>=2.23.0
39
39
  Requires-Dist: asyncvnc>=1.3.0
40
40
  Requires-Dist: fastmcp==3.2.0
41
41
  Requires-Dist: google-genai
42
+ Requires-Dist: httpx2<3,>=2.0.0
42
43
  Requires-Dist: httpx<1,>=0.23.0
43
44
  Requires-Dist: mcp<2.0,>=1.24.0
44
45
  Requires-Dist: openai>=2.45.0
@@ -2,13 +2,25 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import asyncio
5
6
  import copy
6
7
  import json
7
8
  import logging
9
+ import math
10
+ from random import SystemRandom
8
11
  from typing import TYPE_CHECKING, Literal, cast
9
12
 
13
+ import httpx
14
+ import httpx2
10
15
  import mcp.types as mcp_types
11
- from anthropic import AsyncAnthropic, AsyncAnthropicBedrock, Omit
16
+ from anthropic import (
17
+ APIConnectionError,
18
+ APIStatusError,
19
+ APITimeoutError,
20
+ AsyncAnthropic,
21
+ AsyncAnthropicBedrock,
22
+ Omit,
23
+ )
12
24
  from anthropic.types import CacheControlEphemeralParam
13
25
  from anthropic.types.beta import (
14
26
  BetaBase64ImageSourceParam,
@@ -42,6 +54,42 @@ logger = logging.getLogger(__name__)
42
54
  ClaudeImageMediaType = Literal["image/jpeg", "image/png", "image/gif", "image/webp"]
43
55
  ClaudeToolResultContent = BetaTextBlockParam | BetaImageBlockParam | BetaRequestDocumentBlockParam
44
56
 
57
+ _RETRYABLE_STREAM_ERROR_TYPES = frozenset(
58
+ {
59
+ "api_error",
60
+ "gateway_error",
61
+ "gateway_timeout",
62
+ "overloaded_error",
63
+ "rate_limit_error",
64
+ "timeout_error",
65
+ "upstream_error",
66
+ }
67
+ )
68
+ _STREAM_MAX_ATTEMPTS = 3
69
+ _STREAM_RETRY_BASE_DELAY_SECONDS = 1.0
70
+ _STREAM_RETRY_MAX_DELAY_SECONDS = 5.0
71
+ _STREAM_JITTER = SystemRandom()
72
+
73
+
74
+ def _stream_retry_delay(attempt: int, error: Exception) -> float:
75
+ if isinstance(error, APIStatusError):
76
+ for header, divisor in (("retry-after-ms", 1000.0), ("retry-after", 1.0)):
77
+ value = error.response.headers.get(header)
78
+ if value is None:
79
+ continue
80
+ try:
81
+ retry_after = float(value) / divisor
82
+ except ValueError:
83
+ continue
84
+ if math.isfinite(retry_after):
85
+ return min(max(retry_after, 0.0), _STREAM_RETRY_MAX_DELAY_SECONDS)
86
+
87
+ backoff_limit = min(
88
+ _STREAM_RETRY_BASE_DELAY_SECONDS * (2 ** (attempt - 1)),
89
+ _STREAM_RETRY_MAX_DELAY_SECONDS,
90
+ )
91
+ return _STREAM_JITTER.uniform(0.0, backoff_limit)
92
+
45
93
 
46
94
  class ClaudeAgent(ToolAgent[BetaMessageParam, ClaudeConfig]):
47
95
  """Anthropic Claude agent. Drives SSH (coding), RFB (computer), and MCP capabilities."""
@@ -168,6 +216,67 @@ class ClaudeAgent(ToolAgent[BetaMessageParam, ClaudeConfig]):
168
216
 
169
217
  # ─── Anthropic call ───────────────────────────────────────────────
170
218
 
219
+ async def _stream_response(
220
+ self,
221
+ client: AsyncAnthropic,
222
+ *,
223
+ messages: list[BetaMessageParam],
224
+ system: str | Omit,
225
+ tools: list[BetaToolUnionParam],
226
+ tool_choice: BetaToolChoiceAutoParam,
227
+ betas: list[str] | Omit,
228
+ ) -> BetaMessage:
229
+ attempt = 1
230
+ while True:
231
+ stream_opened = False
232
+ try:
233
+ async with client.beta.messages.stream(
234
+ model=self.config.model,
235
+ system=system,
236
+ max_tokens=self.config.max_tokens,
237
+ messages=messages,
238
+ tools=tools,
239
+ tool_choice=tool_choice,
240
+ betas=betas,
241
+ ) as stream:
242
+ stream_opened = True
243
+ async for _ in stream:
244
+ pass
245
+ return await stream.get_final_message()
246
+ except (
247
+ APIConnectionError,
248
+ APIStatusError,
249
+ APITimeoutError,
250
+ httpx.TransportError,
251
+ httpx2.TransportError,
252
+ ) as exc:
253
+ inline_error = (
254
+ isinstance(exc, APIStatusError)
255
+ and exc.status_code == 200
256
+ and exc.type in _RETRYABLE_STREAM_ERROR_TYPES
257
+ )
258
+ interrupted_stream = stream_opened and isinstance(
259
+ exc,
260
+ APIConnectionError
261
+ | APITimeoutError
262
+ | httpx.TransportError
263
+ | httpx2.TransportError,
264
+ )
265
+ if attempt >= _STREAM_MAX_ATTEMPTS or not (inline_error or interrupted_stream):
266
+ raise
267
+
268
+ retry_delay = _stream_retry_delay(attempt, exc)
269
+ error_type = exc.type if isinstance(exc, APIStatusError) else type(exc).__name__
270
+ logger.warning(
271
+ "Claude stream failed with %s; retrying in %.2fs (attempt %d/%d)",
272
+ error_type,
273
+ retry_delay,
274
+ attempt + 1,
275
+ _STREAM_MAX_ATTEMPTS,
276
+ )
277
+ attempt += 1
278
+ await asyncio.sleep(retry_delay)
279
+
171
280
  async def get_response(
172
281
  self,
173
282
  state: RunState[BetaMessageParam],
@@ -202,18 +311,14 @@ class ClaudeAgent(ToolAgent[BetaMessageParam, ClaudeConfig]):
202
311
  )
203
312
  else:
204
313
  client = cast("AsyncAnthropic", self.anthropic_client)
205
- async with client.beta.messages.stream(
206
- model=self.config.model,
207
- system=system,
208
- max_tokens=self.config.max_tokens,
314
+ response = await self._stream_response(
315
+ client,
209
316
  messages=messages_cached,
317
+ system=system,
210
318
  tools=tools,
211
319
  tool_choice=tool_choice,
212
320
  betas=betas,
213
- ) as stream:
214
- async for _ in stream:
215
- pass
216
- response = await stream.get_final_message()
321
+ )
217
322
 
218
323
  state.messages.append(
219
324
  BetaMessageParam(role="assistant", content=response.content),
@@ -0,0 +1,26 @@
1
+ """Claude-specific tool spec."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from hud.agents.tools.base import AgentToolSpec
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class ClaudeToolSpec(AgentToolSpec):
12
+ """Claude tool spec — adds the optional Anthropic beta flag."""
13
+
14
+ beta: str | None = None
15
+
16
+
17
+ def is_anthropic_model(model: str | None) -> bool:
18
+ """Whether the model accepts Anthropic server-tool shorthands (``bash_20250124`` etc.).
19
+
20
+ Non-Anthropic models served over Anthropic-compatible endpoints (e.g. OpenRouter)
21
+ reject those shorthands, so tools fall back to plain ``input_schema`` definitions.
22
+ """
23
+ return bool(model) and "claude" in model.lower()
24
+
25
+
26
+ __all__ = ["ClaudeToolSpec", "is_anthropic_model"]
@@ -10,12 +10,13 @@ from hud.agents.tools import SSHTool
10
10
  from hud.agents.tools.base import result_text, tool_err
11
11
  from hud.types import MCPToolResult
12
12
 
13
- from .base import ClaudeToolSpec
13
+ from .base import ClaudeToolSpec, is_anthropic_model
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from anthropic.types.beta import (
17
17
  BetaToolBash20250124Param,
18
18
  BetaToolTextEditor20250728Param,
19
+ BetaToolUnionParam,
19
20
  )
20
21
 
21
22
 
@@ -24,11 +25,64 @@ CLAUDE_BASH_SPEC = ClaudeToolSpec(
24
25
  api_name="bash",
25
26
  )
26
27
 
28
+ GENERIC_BASH_SPEC = ClaudeToolSpec(
29
+ api_type="function",
30
+ api_name="bash",
31
+ )
32
+
27
33
  CLAUDE_TEXT_EDITOR_SPEC = ClaudeToolSpec(
28
34
  api_type="text_editor_20250728",
29
35
  api_name="str_replace_based_edit_tool",
30
36
  )
31
37
 
38
+ GENERIC_TEXT_EDITOR_SPEC = ClaudeToolSpec(
39
+ api_type="function",
40
+ api_name="str_replace_based_edit_tool",
41
+ )
42
+
43
+ _BASH_INPUT_SCHEMA: dict[str, Any] = {
44
+ "type": "object",
45
+ "properties": {
46
+ "command": {
47
+ "type": "string",
48
+ "description": "The bash command to run.",
49
+ },
50
+ },
51
+ "required": ["command"],
52
+ }
53
+
54
+ _TEXT_EDITOR_INPUT_SCHEMA: dict[str, Any] = {
55
+ "type": "object",
56
+ "properties": {
57
+ "command": {
58
+ "type": "string",
59
+ "enum": ["view", "create", "str_replace", "insert"],
60
+ "description": "The editor operation to run.",
61
+ },
62
+ "path": {
63
+ "type": "string",
64
+ "description": "Absolute path to the file.",
65
+ },
66
+ "file_text": {
67
+ "type": "string",
68
+ "description": "Full file contents for `create`.",
69
+ },
70
+ "old_str": {
71
+ "type": "string",
72
+ "description": "Exact existing text to replace for `str_replace`; must be unique.",
73
+ },
74
+ "new_str": {
75
+ "type": "string",
76
+ "description": "Replacement text for `str_replace`, or text to insert for `insert`.",
77
+ },
78
+ "insert_line": {
79
+ "type": "integer",
80
+ "description": "Line number after which to insert for `insert` (0 = top of file).",
81
+ },
82
+ },
83
+ "required": ["command", "path"],
84
+ }
85
+
32
86
 
33
87
  class ClaudeBashTool(SSHTool):
34
88
  """Claude's native ``bash_20250124`` schema, executed over SSH."""
@@ -37,10 +91,18 @@ class ClaudeBashTool(SSHTool):
37
91
 
38
92
  @classmethod
39
93
  def default_spec(cls, model: str) -> ClaudeToolSpec:
40
- del model
41
- return CLAUDE_BASH_SPEC
42
-
43
- def to_params(self) -> BetaToolBash20250124Param:
94
+ return CLAUDE_BASH_SPEC if is_anthropic_model(model) else GENERIC_BASH_SPEC
95
+
96
+ def to_params(self) -> BetaToolUnionParam:
97
+ if self.spec.api_type == "function":
98
+ return cast(
99
+ "BetaToolUnionParam",
100
+ {
101
+ "name": self.name,
102
+ "description": "Run a command in a bash shell on the remote machine.",
103
+ "input_schema": _BASH_INPUT_SCHEMA,
104
+ },
105
+ )
44
106
  return cast(
45
107
  "BetaToolBash20250124Param",
46
108
  {"type": self.spec.api_type, "name": self.name},
@@ -77,14 +139,25 @@ class ClaudeTextEditorTool(SSHTool):
77
139
 
78
140
  @classmethod
79
141
  def default_spec(cls, model: str) -> ClaudeToolSpec:
80
- del model
81
- return CLAUDE_TEXT_EDITOR_SPEC
142
+ return CLAUDE_TEXT_EDITOR_SPEC if is_anthropic_model(model) else GENERIC_TEXT_EDITOR_SPEC
82
143
 
83
144
  @property
84
145
  def provider_name(self) -> str:
85
146
  return self.spec.api_name
86
147
 
87
- def to_params(self) -> BetaToolTextEditor20250728Param:
148
+ def to_params(self) -> BetaToolUnionParam:
149
+ if self.spec.api_type == "function":
150
+ return cast(
151
+ "BetaToolUnionParam",
152
+ {
153
+ "name": self.provider_name,
154
+ "description": (
155
+ "View, create, and edit files on the remote machine "
156
+ "(view/create/str_replace/insert)."
157
+ ),
158
+ "input_schema": _TEXT_EDITOR_INPUT_SCHEMA,
159
+ },
160
+ )
88
161
  return cast(
89
162
  "BetaToolTextEditor20250728Param",
90
163
  {"type": self.spec.api_type, "name": self.provider_name},
@@ -142,4 +215,11 @@ class ClaudeTextEditorTool(SSHTool):
142
215
  return await self.file_write(path, "".join(lines))
143
216
 
144
217
 
145
- __all__ = ["CLAUDE_BASH_SPEC", "CLAUDE_TEXT_EDITOR_SPEC", "ClaudeBashTool", "ClaudeTextEditorTool"]
218
+ __all__ = [
219
+ "CLAUDE_BASH_SPEC",
220
+ "CLAUDE_TEXT_EDITOR_SPEC",
221
+ "GENERIC_BASH_SPEC",
222
+ "GENERIC_TEXT_EDITOR_SPEC",
223
+ "ClaudeBashTool",
224
+ "ClaudeTextEditorTool",
225
+ ]
@@ -19,12 +19,13 @@ from hud.agents.tools.base import tool_err, tool_ok
19
19
  from hud.capabilities.rfb import ScreenshotEncoding, WebPScreenshotEncoding
20
20
  from hud.types import MCPToolResult
21
21
 
22
- from .base import ClaudeToolSpec
22
+ from .base import ClaudeToolSpec, is_anthropic_model
23
23
 
24
24
  if TYPE_CHECKING:
25
25
  from anthropic.types.beta import (
26
26
  BetaToolComputerUse20250124Param,
27
27
  BetaToolComputerUse20251124Param,
28
+ BetaToolUnionParam,
28
29
  )
29
30
 
30
31
  from hud.agents.tools.rfb import Button
@@ -110,9 +111,89 @@ CLAUDE_COMPUTER_SPECS: tuple[ClaudeToolSpec, ...] = (
110
111
  ),
111
112
  )
112
113
 
113
- # Fallback for unknown models — use the latest version.
114
+ # Fallback for unknown Claude models — use the latest version.
114
115
  _DEFAULT_COMPUTER_SPEC = CLAUDE_COMPUTER_SPECS[0]
115
116
 
117
+ GENERIC_COMPUTER_SPEC = ClaudeToolSpec(
118
+ api_type="function",
119
+ api_name="computer",
120
+ )
121
+
122
+ _COMPUTER_INPUT_SCHEMA: dict[str, Any] = {
123
+ "type": "object",
124
+ "properties": {
125
+ "action": {
126
+ "type": "string",
127
+ "enum": [
128
+ "screenshot",
129
+ "zoom",
130
+ "left_click",
131
+ "right_click",
132
+ "middle_click",
133
+ "double_click",
134
+ "triple_click",
135
+ "mouse_move",
136
+ "left_mouse_down",
137
+ "left_mouse_up",
138
+ "type",
139
+ "key",
140
+ "hold_key",
141
+ "scroll",
142
+ "left_click_drag",
143
+ "wait",
144
+ "cursor_position",
145
+ ],
146
+ "description": "The action to perform on the screen.",
147
+ },
148
+ "coordinate": {
149
+ "type": "array",
150
+ "items": {"type": "integer"},
151
+ "minItems": 2,
152
+ "maxItems": 2,
153
+ "description": "[x, y] pixel coordinate for click/move/scroll, or drag end point.",
154
+ },
155
+ "start_coordinate": {
156
+ "type": "array",
157
+ "items": {"type": "integer"},
158
+ "minItems": 2,
159
+ "maxItems": 2,
160
+ "description": "[x, y] drag start point for `left_click_drag`.",
161
+ },
162
+ "text": {
163
+ "type": "string",
164
+ "description": (
165
+ "Text to type for `type`; key chord (xdotool syntax, e.g. `ctrl+s`) for "
166
+ "`key`/`hold_key`; optional modifier keys held during clicks and scrolls."
167
+ ),
168
+ },
169
+ "duration": {
170
+ "type": "number",
171
+ "description": "Seconds to hold for `hold_key` or pause for `wait`.",
172
+ },
173
+ "scroll_direction": {
174
+ "type": "string",
175
+ "enum": ["up", "down", "left", "right"],
176
+ "description": "Direction for `scroll`.",
177
+ },
178
+ "scroll_amount": {
179
+ "type": "integer",
180
+ "description": "Number of scroll clicks for `scroll`.",
181
+ },
182
+ "repeat": {
183
+ "type": "integer",
184
+ "description": "Times to repeat the key chord for `key`.",
185
+ },
186
+ "region": {
187
+ "type": "array",
188
+ "items": {"type": "integer"},
189
+ "minItems": 4,
190
+ "maxItems": 4,
191
+ "description": "[x0, y0, x1, y1] region to magnify for `zoom`.",
192
+ },
193
+ },
194
+ "required": ["action"],
195
+ }
196
+
116
197
 
117
198
  class ClaudeComputerTool(RFBTool):
118
199
  """Claude's native ``computer_use`` schema, executed over an RFB capability."""
@@ -121,12 +202,29 @@ class ClaudeComputerTool(RFBTool):
121
202
 
122
203
  @classmethod
123
204
  def default_spec(cls, model: str) -> ClaudeToolSpec | None:
205
+ if not is_anthropic_model(model):
206
+ return GENERIC_COMPUTER_SPEC
124
207
  for candidate in CLAUDE_COMPUTER_SPECS:
125
208
  if candidate.supports_model(model):
126
209
  return candidate
127
210
  return _DEFAULT_COMPUTER_SPEC
128
211
 
129
- def to_params(self) -> BetaToolComputerUse20250124Param | BetaToolComputerUse20251124Param:
212
+ def to_params(
213
+ self,
214
+ ) -> BetaToolComputerUse20250124Param | BetaToolComputerUse20251124Param | BetaToolUnionParam:
215
+ if self.spec.api_type == "function":
216
+ return cast(
217
+ "BetaToolUnionParam",
218
+ {
219
+ "name": self.name,
220
+ "description": (
221
+ "Control the remote computer's screen, mouse, and keyboard. "
222
+ f"The display is {self.display_width}x{self.display_height} pixels. "
223
+ "Most actions return a screenshot of the result."
224
+ ),
225
+ "input_schema": _COMPUTER_INPUT_SCHEMA,
226
+ },
227
+ )
130
228
  if self.spec.api_type == "computer_20251124":
131
229
  return cast(
132
230
  "BetaToolComputerUse20251124Param",
@@ -375,4 +473,4 @@ def _crop_png(
375
473
  return buf.getvalue(), encoding.mime_type
376
474
 
377
475
 
378
- __all__ = ["CLAUDE_COMPUTER_SPECS", "ClaudeComputerTool"]
476
+ __all__ = ["CLAUDE_COMPUTER_SPECS", "GENERIC_COMPUTER_SPEC", "ClaudeComputerTool"]
@@ -14,6 +14,7 @@ from PIL import Image
14
14
 
15
15
  from hud.agents.claude.tools.computer import (
16
16
  CLAUDE_COMPUTER_SPECS,
17
+ GENERIC_COMPUTER_SPEC,
17
18
  ClaudeComputerTool,
18
19
  _crop_png,
19
20
  _hold_keys,
@@ -120,18 +121,31 @@ def test_default_spec_per_model() -> None:
120
121
  spec_45 = ClaudeComputerTool.default_spec("claude-sonnet-4-5-20250101")
121
122
  assert spec_45 is not None
122
123
  assert spec_45.api_type == "computer_20250124"
123
- # Unknown model falls back to the latest spec.
124
- spec_unknown = ClaudeComputerTool.default_spec("totally-unknown")
124
+ # Unknown Claude model falls back to the latest spec.
125
+ spec_unknown = ClaudeComputerTool.default_spec("claude-totally-unknown")
125
126
  assert spec_unknown is not None
126
127
  assert spec_unknown.api_type == "computer_20251124"
128
+ # Non-Anthropic models get the generic input_schema spec.
129
+ assert ClaudeComputerTool.default_spec("qwen/qwen3.8-max") is GENERIC_COMPUTER_SPEC
127
130
 
128
131
 
129
132
  def test_to_params_reflects_spec_version() -> None:
130
133
  tool = RecordingComputer()
131
134
  tool.spec = CLAUDE_COMPUTER_SPECS[0]
132
- assert tool.to_params()["type"] == "computer_20251124"
135
+ assert tool.to_params().get("type") == "computer_20251124"
133
136
  tool.spec = CLAUDE_COMPUTER_SPECS[1]
134
- assert tool.to_params()["type"] == "computer_20250124"
137
+ assert tool.to_params().get("type") == "computer_20250124"
138
+
139
+
140
+ def test_to_params_generic_spec_uses_input_schema() -> None:
141
+ tool = RecordingComputer()
142
+ tool.spec = GENERIC_COMPUTER_SPEC
143
+ params: Any = tool.to_params()
144
+ assert "type" not in params
145
+ assert params["name"] == "computer"
146
+ assert "200x100" in params["description"]
147
+ assert params["input_schema"]["required"] == ["action"]
148
+ assert "left_click" in params["input_schema"]["properties"]["action"]["enum"]
135
149
 
136
150
 
137
151
  # ─── action dispatch ──────────────────────────────────────────────────