hud 0.6.17__tar.gz → 0.6.18.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.
- {hud-0.6.17 → hud-0.6.18.dev2}/.gitignore +2 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/PKG-INFO +2 -12
- {hud-0.6.17 → hud-0.6.18.dev2}/README.md +1 -11
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/__main__.py +1 -1
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/__init__.py +35 -69
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/claude/agent.py +5 -7
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/gemini/agent.py +8 -11
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai/agent.py +18 -15
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai/tools/computer.py +58 -41
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai/tools/tests/test_computer.py +50 -10
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai_compatible/agent.py +1 -1
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tests/test_base.py +27 -6
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tests/test_gemini_agent.py +16 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tests/test_openai_agent.py +120 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tests/test_tool_agent.py +17 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tool_agent.py +6 -1
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tools/ssh.py +1 -1
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/types.py +5 -1
- hud-0.6.18.dev2/hud/cli/__init__.py +425 -0
- hud-0.6.18.dev2/hud/cli/__main__.py +270 -0
- hud-0.6.18.dev2/hud/cli/deploy.py +541 -0
- hud-0.6.18.dev2/hud/cli/eval.py +573 -0
- hud-0.6.18.dev2/hud/cli/init.py +195 -0
- hud-0.6.18.dev2/hud/cli/jobs.py +280 -0
- hud-0.6.18.dev2/hud/cli/models.py +262 -0
- hud-0.6.18.dev2/hud/cli/project.py +247 -0
- hud-0.6.18.dev2/hud/cli/qa.py +574 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/cli/serve.py +23 -54
- hud-0.6.18.dev2/hud/cli/sync.py +434 -0
- hud-0.6.18.dev2/hud/cli/task.py +213 -0
- hud-0.6.18.dev2/hud/cli/tests/test_cli.py +853 -0
- hud-0.6.18.dev2/hud/cli/tests/test_deploy.py +850 -0
- hud-0.6.18.dev2/hud/cli/tests/test_eval.py +953 -0
- hud-0.6.18.dev2/hud/cli/tests/test_init.py +255 -0
- hud-0.6.18.dev2/hud/cli/tests/test_jobs.py +73 -0
- hud-0.6.18.dev2/hud/cli/tests/test_main.py +26 -0
- hud-0.6.18.dev2/hud/cli/tests/test_project.py +284 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/cli/tests/test_qa.py +117 -4
- hud-0.6.18.dev2/hud/cli/tests/test_sync.py +396 -0
- hud-0.6.18.dev2/hud/cli/tests/test_task.py +149 -0
- hud-0.6.18.dev2/hud/cli/tests/test_trace.py +76 -0
- hud-0.6.18.dev2/hud/cli/trace.py +158 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/conftest.py +4 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/__init__.py +21 -7
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/file_tracker.py +1 -1
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/namespace.py +77 -29
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/tests/test_loader.py +140 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/tests/test_workspace.py +96 -1
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/utils.py +16 -2
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/workspace.py +66 -58
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/job.py +2 -2
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/run.py +1 -5
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/runtime/hosted.py +2 -2
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/runtime/local.py +51 -31
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/sync.py +3 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/task.py +9 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/taskset.py +39 -19
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/tests/test_hosted.py +1 -1
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/tests/test_local_runtime.py +195 -4
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/tests/test_rollout.py +6 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/settings.py +10 -1
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/telemetry/exporter.py +4 -4
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/telemetry/robot/recorder.py +2 -2
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/telemetry/tests/test_exporter.py +10 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/train/base.py +15 -35
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/train/tests/test_client.py +26 -0
- hud-0.6.18.dev2/hud/utils/gateway.py +202 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/hud_console.py +6 -9
- hud-0.6.18.dev2/hud/utils/modules.py +122 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/platform.py +9 -9
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/process.py +57 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/requests.py +35 -90
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/tests/test_gateway.py +111 -0
- hud-0.6.18.dev2/hud/utils/tests/test_naming.py +13 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/tests/test_platform.py +13 -13
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/tests/test_requests.py +45 -20
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/version.py +1 -1
- {hud-0.6.17 → hud-0.6.18.dev2}/pyproject.toml +1 -1
- hud-0.6.17/hud/cli/__init__.py +0 -175
- hud-0.6.17/hud/cli/__main__.py +0 -8
- hud-0.6.17/hud/cli/cancel.py +0 -111
- hud-0.6.17/hud/cli/client.py +0 -82
- hud-0.6.17/hud/cli/deploy.py +0 -898
- hud-0.6.17/hud/cli/eval.py +0 -1003
- hud-0.6.17/hud/cli/init.py +0 -129
- hud-0.6.17/hud/cli/jobs.py +0 -147
- hud-0.6.17/hud/cli/models.py +0 -274
- hud-0.6.17/hud/cli/presets.py +0 -115
- hud-0.6.17/hud/cli/project.py +0 -198
- hud-0.6.17/hud/cli/qa.py +0 -419
- hud-0.6.17/hud/cli/qa_analysis.py +0 -207
- hud-0.6.17/hud/cli/sync.py +0 -525
- hud-0.6.17/hud/cli/task.py +0 -224
- hud-0.6.17/hud/cli/tests/test_cli_init.py +0 -87
- hud-0.6.17/hud/cli/tests/test_cli_main.py +0 -27
- hud-0.6.17/hud/cli/tests/test_cli_more_wrappers.py +0 -7
- hud-0.6.17/hud/cli/tests/test_deploy.py +0 -636
- hud-0.6.17/hud/cli/tests/test_eval_bedrock.py +0 -51
- hud-0.6.17/hud/cli/tests/test_eval_config.py +0 -354
- hud-0.6.17/hud/cli/tests/test_init.py +0 -276
- hud-0.6.17/hud/cli/tests/test_jobs.py +0 -38
- hud-0.6.17/hud/cli/tests/test_main_module.py +0 -36
- hud-0.6.17/hud/cli/tests/test_project_command.py +0 -133
- hud-0.6.17/hud/cli/tests/test_qa_analysis.py +0 -87
- hud-0.6.17/hud/cli/tests/test_sync_export.py +0 -58
- hud-0.6.17/hud/cli/tests/test_sync_projects.py +0 -176
- hud-0.6.17/hud/cli/tests/test_task.py +0 -43
- hud-0.6.17/hud/cli/tests/test_trace.py +0 -47
- hud-0.6.17/hud/cli/tests/test_usage.py +0 -134
- hud-0.6.17/hud/cli/trace.py +0 -206
- hud-0.6.17/hud/cli/utils/__init__.py +0 -1
- hud-0.6.17/hud/cli/utils/api.py +0 -21
- hud-0.6.17/hud/cli/utils/build_display.py +0 -279
- hud-0.6.17/hud/cli/utils/build_logs.py +0 -230
- hud-0.6.17/hud/cli/utils/config.py +0 -115
- hud-0.6.17/hud/cli/utils/context.py +0 -282
- hud-0.6.17/hud/cli/utils/display.py +0 -100
- hud-0.6.17/hud/cli/utils/jobs.py +0 -38
- hud-0.6.17/hud/cli/utils/project.py +0 -270
- hud-0.6.17/hud/cli/utils/registry.py +0 -100
- hud-0.6.17/hud/cli/utils/source.py +0 -563
- hud-0.6.17/hud/cli/utils/tasks.py +0 -29
- hud-0.6.17/hud/cli/utils/tests/test_build_display.py +0 -49
- hud-0.6.17/hud/cli/utils/tests/test_config.py +0 -58
- hud-0.6.17/hud/cli/utils/tests/test_context.py +0 -92
- hud-0.6.17/hud/cli/utils/tests/test_project.py +0 -276
- hud-0.6.17/hud/cli/utils/tests/test_registry.py +0 -76
- hud-0.6.17/hud/cli/utils/tests/test_source.py +0 -321
- hud-0.6.17/hud/cli/utils/tests/test_tasks.py +0 -80
- hud-0.6.17/hud/cli/utils/tests/test_version_check.py +0 -109
- hud-0.6.17/hud/cli/utils/usage.py +0 -179
- hud-0.6.17/hud/cli/utils/version_check.py +0 -237
- hud-0.6.17/hud/utils/gateway.py +0 -141
- hud-0.6.17/hud/utils/modules.py +0 -79
- hud-0.6.17/hud/utils/tests/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/LICENSE +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/base.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/browser_use/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/browser_use/agent.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/claude/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/claude/sdk/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/claude/sdk/agent.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/claude/sdk/computer_mcp.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/claude/tools/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/claude/tools/base.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/claude/tools/coding.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/claude/tools/computer.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/claude/tools/hosted.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/claude/tools/mcp_proxy.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/claude/tools/settings.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/claude/tools/tests/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/claude/tools/tests/test_computer.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/gemini/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/gemini/settings.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/gemini/tools/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/gemini/tools/base.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/gemini/tools/coding.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/gemini/tools/computer.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/gemini/tools/filesystem.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/gemini/tools/hosted.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/gemini/tools/mcp_proxy.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/gemini/tools/tests/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/gemini/tools/tests/test_computer.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/misc/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/misc/response_automation.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai/tools/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai/tools/apply_patch.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai/tools/base.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai/tools/coding.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai/tools/hosted.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai/tools/mcp_proxy.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai/tools/strict_schema.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai/tools/tests/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai/tools/tests/test_strict_schema.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai_compatible/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai_compatible/tools/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai_compatible/tools/base.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai_compatible/tools/filesystem.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/openai_compatible/tools/mcp_proxy.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/robot/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/robot/adapter.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/robot/agent.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/robot/batching.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/robot/dataset.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/robot/model.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tests/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tests/test_apply_patch.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tests/test_claude_agent.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tests/test_claude_sdk_agent.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tests/test_openai_compatible_agent.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tests/test_provider_native_tools.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tests/test_robot_dataset_writer.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tests/test_trace.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tools/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tools/base.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tools/hosted.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tools/mcp.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/agents/tools/rfb.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/capabilities/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/capabilities/base.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/capabilities/cdp.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/capabilities/mcp.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/capabilities/rfb.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/capabilities/robot.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/capabilities/ssh.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/capabilities/tests/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/capabilities/tests/test_rfb.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/capabilities/tests/test_ssh.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/cli/tests/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/clients/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/clients/client.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/clients/tests/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/clients/tests/test_connect.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/arguments.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/egress.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/env.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/robot/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/robot/bridge.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/robot/endpoint.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/robot/gym.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/server.py +0 -0
- {hud-0.6.17/hud/cli/utils → hud-0.6.18.dev2/hud/environment}/tests/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/tests/conftest.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/tests/test_arguments.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/tests/test_capability_backing.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/tests/test_file_tracker.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/tests/test_file_tracking.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/tests/test_manifest.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/tests/test_platform.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/tests/test_robot_regressions.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/tests/test_server.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/tests/test_sessions.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/environment/tests/test_tunnel.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/chat.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/docker-seccomp.json +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/file_tracking.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/runtime/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/runtime/compose.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/runtime/core.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/runtime/daytona.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/runtime/docker.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/runtime/hud.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/runtime/modal.py +0 -0
- {hud-0.6.17/hud/environment → hud-0.6.18.dev2/hud/eval}/tests/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/tests/test_chat.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/tests/test_docker_provider.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/tests/test_file_tracking_observer.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/tests/test_job.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/tests/test_shared.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/tests/test_sync.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/eval/tests/test_task.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/graders/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/graders/base.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/graders/bash.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/graders/combine.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/graders/judge.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/graders/results.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/graders/text.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/Dockerfile +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/adapt.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/env.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/export.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/install.sh +0 -0
- {hud-0.6.17/hud/eval → hud-0.6.18.dev2/hud/integrations/harbor}/tests/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/conftest.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/agent-lifecycle/environment/Dockerfile +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/agent-lifecycle/instruction.md +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/agent-lifecycle/solution/solve.sh +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/agent-lifecycle/task.toml +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/agent-lifecycle/tests/test.sh +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/environment/Dockerfile +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/environment/docker-compose.yaml +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/environment/mcp-server/Dockerfile +0 -0
- {hud-0.6.17/hud/integrations/harbor/tests → hud-0.6.18.dev2/hud/integrations/harbor/tests/tasks/hello-mcp/environment/mcp-server}/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/environment/mcp-server/server.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/instruction.md +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/solution/solve.sh +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/task.toml +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/tests/test.sh +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/phase-boundary/environment/Dockerfile +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/phase-boundary/environment/entrypoint.sh +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/phase-boundary/instruction.md +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/phase-boundary/solution/solve.sh +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/phase-boundary/task.toml +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/phase-boundary/tests/test.sh +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/sidecar-reachability/environment/Dockerfile +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/sidecar-reachability/environment/docker-compose.yaml +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/sidecar-reachability/instruction.md +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/sidecar-reachability/solution/solve.sh +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/sidecar-reachability/task.toml +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/sidecar-reachability/tests/Dockerfile +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/sidecar-reachability/tests/test.sh +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/environment/Dockerfile +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/environment/entrypoint.sh +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/instruction.md +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/solution/solve.sh +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/task.toml +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/tests/test.sh +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/test_contract.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/test_harbor.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/test_integration.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/patches/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/patches/mcp_patches.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/patches/tests/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/patches/tests/test_warnings.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/patches/warnings.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/py.typed +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/telemetry/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/telemetry/context.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/telemetry/instrument.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/telemetry/robot/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/telemetry/robot/video.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/telemetry/span.py +0 -0
- {hud-0.6.17/hud/integrations/harbor/tests/tasks/hello-mcp/environment/mcp-server → hud-0.6.18.dev2/hud/telemetry/tests}/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/telemetry/tests/test_instrument.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/train/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/train/client.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/train/tests/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/train/types.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/types.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/docker.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/exceptions.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/hints.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/naming.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/serialization.py +0 -0
- {hud-0.6.17/hud/telemetry → hud-0.6.18.dev2/hud/utils}/tests/__init__.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/tests/test_exceptions.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/tests/test_hints.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/tests/test_hud_console.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/tests/test_process.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/tests/test_serialization.py +0 -0
- {hud-0.6.17 → hud-0.6.18.dev2}/hud/utils/time.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: hud
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.18.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
|
|
@@ -234,17 +234,7 @@ A **capability** is a connection the environment exposes; a **harness** attaches
|
|
|
234
234
|
|
|
235
235
|
From the [platform UI](https://hud.ai) you can run batches, compare models on the same taskset, and inspect every trace.
|
|
236
236
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
```bash
|
|
240
|
-
hud project list # projects you can see
|
|
241
|
-
hud project use browser-evals # writes projectId to .hud/config.json
|
|
242
|
-
hud project # where does a deploy here land?
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
`hud deploy` and `hud sync tasks` also take `--project <name-or-id>` as a one-command override; it does not change the directory configuration. Use `hud set HUD_DEFAULT_PROJECT=<name-or-id>` for a machine-wide fallback. Precedence is the flag, then the directory's `.hud/config.json`, then `HUD_DEFAULT_PROJECT`, then your team default. An environment or taskset that already exists stays where it is; naming a different project fails rather than moving it.
|
|
246
|
-
|
|
247
|
-
→ [Projects](https://docs.hud.ai/v6/reference/projects) · [Run & deploy](https://docs.hud.ai/v6/reference/runtime)
|
|
237
|
+
→ [Run & deploy](https://docs.hud.ai/v6/reference/runtime)
|
|
248
238
|
|
|
249
239
|
## Train on rewards
|
|
250
240
|
|
|
@@ -148,17 +148,7 @@ A **capability** is a connection the environment exposes; a **harness** attaches
|
|
|
148
148
|
|
|
149
149
|
From the [platform UI](https://hud.ai) you can run batches, compare models on the same taskset, and inspect every trace.
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
```bash
|
|
154
|
-
hud project list # projects you can see
|
|
155
|
-
hud project use browser-evals # writes projectId to .hud/config.json
|
|
156
|
-
hud project # where does a deploy here land?
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
`hud deploy` and `hud sync tasks` also take `--project <name-or-id>` as a one-command override; it does not change the directory configuration. Use `hud set HUD_DEFAULT_PROJECT=<name-or-id>` for a machine-wide fallback. Precedence is the flag, then the directory's `.hud/config.json`, then `HUD_DEFAULT_PROJECT`, then your team default. An environment or taskset that already exists stays where it is; naming a different project fails rather than moving it.
|
|
160
|
-
|
|
161
|
-
→ [Projects](https://docs.hud.ai/v6/reference/projects) · [Run & deploy](https://docs.hud.ai/v6/reference/runtime)
|
|
151
|
+
→ [Run & deploy](https://docs.hud.ai/v6/reference/runtime)
|
|
162
152
|
|
|
163
153
|
## Train on rewards
|
|
164
154
|
|
|
@@ -10,11 +10,7 @@ from typing import TYPE_CHECKING, Any, cast
|
|
|
10
10
|
from hud.settings import settings
|
|
11
11
|
from hud.types import AgentType
|
|
12
12
|
from hud.utils.exceptions import HudAuthenticationError
|
|
13
|
-
from hud.utils.gateway import
|
|
14
|
-
gateway_model_aliases,
|
|
15
|
-
list_gateway_models,
|
|
16
|
-
normalize_gateway_model_id,
|
|
17
|
-
)
|
|
13
|
+
from hud.utils.gateway import resolve_gateway_model
|
|
18
14
|
|
|
19
15
|
if TYPE_CHECKING:
|
|
20
16
|
from typing import TypeAlias
|
|
@@ -31,10 +27,11 @@ if TYPE_CHECKING:
|
|
|
31
27
|
def create_agent(model: str, **kwargs: Any) -> GatewayAgent:
|
|
32
28
|
"""Create an agent routed through the HUD gateway.
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
gateway client locally
|
|
36
|
-
serialize the config and rebuild
|
|
37
|
-
clients remain custom/BYOK and are not
|
|
30
|
+
Sets ``gateway=True`` on the config instead of attaching a client, so the
|
|
31
|
+
provider agent builds the gateway client locally and
|
|
32
|
+
:class:`~hud.eval.runtime.HostedRuntime` can serialize the config and rebuild
|
|
33
|
+
it remotely. Explicitly supplied clients remain custom/BYOK and are not
|
|
34
|
+
serializable.
|
|
38
35
|
|
|
39
36
|
For direct API access with provider API keys, instantiate the agent classes
|
|
40
37
|
directly.
|
|
@@ -49,72 +46,40 @@ def create_agent(model: str, **kwargs: Any) -> GatewayAgent:
|
|
|
49
46
|
if not settings.api_key:
|
|
50
47
|
raise HudAuthenticationError("HUD_API_KEY is required to create a gateway agent")
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
model = normalize_gateway_model_id(model)
|
|
54
|
-
agent_type = next((candidate for candidate in AgentType if candidate.value == model), None)
|
|
55
|
-
if agent_type is not None:
|
|
56
|
-
model_id = model
|
|
57
|
-
else:
|
|
58
|
-
try:
|
|
59
|
-
gateway_models = list_gateway_models()
|
|
60
|
-
except Exception:
|
|
61
|
-
gateway_models = []
|
|
62
|
-
gateway_models = list(gateway_models)
|
|
63
|
-
for gateway_model in gateway_models:
|
|
64
|
-
if model in (
|
|
65
|
-
gateway_model.id,
|
|
66
|
-
gateway_model.name,
|
|
67
|
-
gateway_model.model_name,
|
|
68
|
-
):
|
|
69
|
-
agent_str = gateway_model.sdk_agent_type
|
|
70
|
-
if agent_str == "operator":
|
|
71
|
-
raise ValueError(
|
|
72
|
-
"Operator agent is no longer supported; use openai with a supported "
|
|
73
|
-
"OpenAI computer model."
|
|
74
|
-
)
|
|
75
|
-
if agent_str == "gemini_cua":
|
|
76
|
-
raise ValueError(
|
|
77
|
-
"Gemini CUA agent is no longer supported; use gemini with a supported "
|
|
78
|
-
"Gemini computer-use model."
|
|
79
|
-
)
|
|
80
|
-
if not isinstance(agent_str, str):
|
|
81
|
-
raise ValueError(f"Model '{model}' has invalid agent type metadata")
|
|
82
|
-
|
|
83
|
-
try:
|
|
84
|
-
agent_type = AgentType(agent_str)
|
|
85
|
-
except ValueError as exc:
|
|
86
|
-
raise ValueError(f"Model '{model}' has invalid agent type metadata") from exc
|
|
87
|
-
model_id = gateway_model.model_name or model
|
|
88
|
-
break
|
|
89
|
-
else:
|
|
90
|
-
import difflib
|
|
91
|
-
|
|
92
|
-
known = [c.value for c in AgentType] + [
|
|
93
|
-
n
|
|
94
|
-
for gm in gateway_models
|
|
95
|
-
for n in (gm.id, gm.name, gm.model_name)
|
|
96
|
-
if isinstance(n, str)
|
|
97
|
-
]
|
|
98
|
-
known.extend(gateway_model_aliases())
|
|
99
|
-
near = difflib.get_close_matches(requested_model, known, n=3, cutoff=0.5)
|
|
100
|
-
hint = (
|
|
101
|
-
f" Did you mean: {', '.join(near)}?"
|
|
102
|
-
if near
|
|
103
|
-
else " Run `hud models` to list available models."
|
|
104
|
-
)
|
|
105
|
-
source = (
|
|
106
|
-
"the HUD gateway registry"
|
|
107
|
-
if gateway_models
|
|
108
|
-
else "the HUD gateway registry (empty — is HUD_API_KEY set?)"
|
|
109
|
-
)
|
|
110
|
-
raise ValueError(f"Model {requested_model!r} not found in {source}.{hint}")
|
|
111
|
-
|
|
49
|
+
agent_type, model_id = resolve_agent_model(model)
|
|
112
50
|
kwargs.setdefault("model", model_id)
|
|
51
|
+
kwargs["gateway"] = True
|
|
113
52
|
# cls/config_cls are matched unions; the pairing is correct by construction.
|
|
114
53
|
config = agent_type.config_cls(**kwargs)
|
|
115
54
|
return agent_type.cls(cast("Any", config))
|
|
116
55
|
|
|
117
56
|
|
|
57
|
+
def resolve_agent_model(model: str) -> tuple[AgentType, str]:
|
|
58
|
+
"""Resolve a catalog id/name/slug or an agent type without constructing a client."""
|
|
59
|
+
agent_type = next((candidate for candidate in AgentType if candidate.value == model), None)
|
|
60
|
+
if agent_type is not None:
|
|
61
|
+
return agent_type, model
|
|
62
|
+
try:
|
|
63
|
+
entry = resolve_gateway_model(model)
|
|
64
|
+
except HudAuthenticationError:
|
|
65
|
+
types = ", ".join(candidate.value for candidate in AgentType)
|
|
66
|
+
raise ValueError(
|
|
67
|
+
f"Resolving {model!r} needs the HUD model catalog; set HUD_API_KEY, or pass an "
|
|
68
|
+
f"agent type ({types}) and choose the model with its config."
|
|
69
|
+
) from None
|
|
70
|
+
if entry.sdk_agent_type in ("operator", "gemini_cua"):
|
|
71
|
+
replacement = "openai" if entry.sdk_agent_type == "operator" else "gemini"
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"The {entry.sdk_agent_type} agent is no longer supported; use {replacement} with a "
|
|
74
|
+
"supported computer-use model."
|
|
75
|
+
)
|
|
76
|
+
try:
|
|
77
|
+
agent_type = AgentType(entry.sdk_agent_type)
|
|
78
|
+
except ValueError as exc:
|
|
79
|
+
raise ValueError(f"Model {model!r} has invalid agent type metadata") from exc
|
|
80
|
+
return agent_type, entry.model_name or model
|
|
81
|
+
|
|
82
|
+
|
|
118
83
|
_LAZY_EXPORTS = {
|
|
119
84
|
"ClaudeAgent": ("hud.agents.claude", "ClaudeAgent"),
|
|
120
85
|
"ClaudeSDKAgent": ("hud.agents.claude", "ClaudeSDKAgent"),
|
|
@@ -134,6 +99,7 @@ __all__ = [
|
|
|
134
99
|
"OpenAIAgent",
|
|
135
100
|
"OpenAIChatAgent",
|
|
136
101
|
"create_agent",
|
|
102
|
+
"resolve_agent_model",
|
|
137
103
|
]
|
|
138
104
|
|
|
139
105
|
|
|
@@ -38,7 +38,6 @@ from anthropic.types.beta import (
|
|
|
38
38
|
|
|
39
39
|
from hud.agents.tool_agent import RunState, ToolAgent
|
|
40
40
|
from hud.agents.types import AgentStep, Citation, ClaudeConfig, Usage
|
|
41
|
-
from hud.settings import settings
|
|
42
41
|
from hud.types import MCPToolCall, MCPToolResult
|
|
43
42
|
from hud.utils import gateway
|
|
44
43
|
|
|
@@ -108,12 +107,11 @@ class ClaudeAgent(ToolAgent[BetaMessageParam, ClaudeConfig]):
|
|
|
108
107
|
def _resolve_client(self) -> AsyncAnthropic | AsyncAnthropicBedrock:
|
|
109
108
|
if self.config.model_client is not None:
|
|
110
109
|
return cast("AsyncAnthropic | AsyncAnthropicBedrock", self.config.model_client)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
"No API key found for Claude. Set HUD_API_KEY (gateway) or ANTHROPIC_API_KEY.",
|
|
110
|
+
return cast(
|
|
111
|
+
"AsyncAnthropic | AsyncAnthropicBedrock",
|
|
112
|
+
gateway.build_model_client(
|
|
113
|
+
"anthropic", model=self.config.model, gateway=self.config.gateway
|
|
114
|
+
),
|
|
117
115
|
)
|
|
118
116
|
|
|
119
117
|
# ─── ToolAgent hooks ──────────────────────────────────────────────
|
|
@@ -4,18 +4,19 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import base64
|
|
6
6
|
import logging
|
|
7
|
-
from typing import Any, cast
|
|
7
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
8
8
|
|
|
9
9
|
import mcp.types as mcp_types
|
|
10
|
-
from google import genai
|
|
11
10
|
from google.genai import types as genai_types
|
|
12
11
|
|
|
13
12
|
from hud.agents.tool_agent import RunState, ToolAgent
|
|
14
13
|
from hud.agents.types import AgentStep, Citation, GeminiConfig, Usage
|
|
15
|
-
from hud.settings import settings
|
|
16
14
|
from hud.types import MCPToolCall, MCPToolResult
|
|
17
15
|
from hud.utils import gateway
|
|
18
16
|
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from google import genai
|
|
19
|
+
|
|
19
20
|
from .settings import gemini_agent_settings
|
|
20
21
|
from .tools import (
|
|
21
22
|
PREDEFINED_COMPUTER_USE_FUNCTIONS,
|
|
@@ -54,14 +55,7 @@ class GeminiAgent(ToolAgent[genai_types.Content, GeminiConfig]):
|
|
|
54
55
|
|
|
55
56
|
model_client = config.model_client
|
|
56
57
|
if model_client is None:
|
|
57
|
-
|
|
58
|
-
model_client = gateway.build_gateway_client("gemini")
|
|
59
|
-
elif settings.gemini_api_key:
|
|
60
|
-
model_client = genai.Client(api_key=settings.gemini_api_key)
|
|
61
|
-
else:
|
|
62
|
-
raise ValueError(
|
|
63
|
-
"No API key for Gemini. Set HUD_API_KEY or GEMINI_API_KEY.",
|
|
64
|
-
)
|
|
58
|
+
model_client = gateway.build_model_client("gemini", gateway=config.gateway)
|
|
65
59
|
|
|
66
60
|
self.gemini_client: genai.Client = cast("genai.Client", model_client)
|
|
67
61
|
self.temperature = config.temperature
|
|
@@ -230,6 +224,9 @@ class GeminiAgent(ToolAgent[genai_types.Content, GeminiConfig]):
|
|
|
230
224
|
|
|
231
225
|
if candidate.finish_reason is not None:
|
|
232
226
|
result.finish_reason = candidate.finish_reason.name
|
|
227
|
+
if result.finish_reason == "MALFORMED_FUNCTION_CALL":
|
|
228
|
+
result.error = "Provider returned a malformed function call"
|
|
229
|
+
result.stop_reason = "malformed_tool_call"
|
|
233
230
|
|
|
234
231
|
return result
|
|
235
232
|
|
|
@@ -25,8 +25,8 @@ from openai.types.responses.response_input_param import (
|
|
|
25
25
|
from openai.types.shared_params.reasoning import Reasoning # noqa: TC002
|
|
26
26
|
|
|
27
27
|
from hud.agents.tool_agent import DegenerateTurnError, RunState, ToolAgent
|
|
28
|
+
from hud.agents.tools.base import result_text
|
|
28
29
|
from hud.agents.types import AgentStep, Citation, OpenAIConfig, Usage
|
|
29
|
-
from hud.settings import settings
|
|
30
30
|
from hud.types import MCPToolCall, MCPToolResult
|
|
31
31
|
from hud.utils import gateway
|
|
32
32
|
|
|
@@ -72,14 +72,7 @@ class OpenAIAgent(ToolAgent[ResponseInputItemParam, OpenAIConfig]):
|
|
|
72
72
|
|
|
73
73
|
model_client = config.model_client
|
|
74
74
|
if model_client is None:
|
|
75
|
-
|
|
76
|
-
model_client = gateway.build_gateway_client("openai")
|
|
77
|
-
elif settings.openai_api_key:
|
|
78
|
-
model_client = AsyncOpenAI(api_key=settings.openai_api_key)
|
|
79
|
-
else:
|
|
80
|
-
raise ValueError(
|
|
81
|
-
"No API key for OpenAI. Set HUD_API_KEY or OPENAI_API_KEY.",
|
|
82
|
-
)
|
|
75
|
+
model_client = gateway.build_model_client("openai", gateway=config.gateway)
|
|
83
76
|
|
|
84
77
|
self.openai_client: AsyncOpenAI = cast("AsyncOpenAI", model_client)
|
|
85
78
|
self._model = config.model
|
|
@@ -116,8 +109,10 @@ class OpenAIAgent(ToolAgent[ResponseInputItemParam, OpenAIConfig]):
|
|
|
116
109
|
if isinstance(tool, OpenAIComputerTool):
|
|
117
110
|
screenshot = last_image_content(result)
|
|
118
111
|
if screenshot is None:
|
|
119
|
-
|
|
120
|
-
|
|
112
|
+
raise RuntimeError(
|
|
113
|
+
f"Computer tool result missing screenshot for call {call.id}: "
|
|
114
|
+
f"{result_text(result)}"
|
|
115
|
+
)
|
|
121
116
|
output = ComputerCallOutput(
|
|
122
117
|
type="computer_call_output",
|
|
123
118
|
call_id=call.id,
|
|
@@ -142,7 +137,17 @@ class OpenAIAgent(ToolAgent[ResponseInputItemParam, OpenAIConfig]):
|
|
|
142
137
|
acknowledged.append(raw_check)
|
|
143
138
|
if acknowledged:
|
|
144
139
|
output["acknowledged_safety_checks"] = acknowledged
|
|
145
|
-
|
|
140
|
+
if result.isError:
|
|
141
|
+
return [
|
|
142
|
+
output,
|
|
143
|
+
self._format_message(
|
|
144
|
+
"user",
|
|
145
|
+
f"Computer call {call.id} stopped with an error. "
|
|
146
|
+
"Remaining actions in this call were not executed.\n"
|
|
147
|
+
f"{result_text(result)}",
|
|
148
|
+
),
|
|
149
|
+
]
|
|
150
|
+
return output
|
|
146
151
|
|
|
147
152
|
if isinstance(tool, OpenAIShellTool):
|
|
148
153
|
structured = (
|
|
@@ -150,8 +155,6 @@ class OpenAIAgent(ToolAgent[ResponseInputItemParam, OpenAIConfig]):
|
|
|
150
155
|
)
|
|
151
156
|
output_list = structured.get("output")
|
|
152
157
|
if not isinstance(output_list, list):
|
|
153
|
-
from hud.agents.tools.base import result_text
|
|
154
|
-
|
|
155
158
|
text = result_text(result)
|
|
156
159
|
output_list = [shell_output("", text, 1 if result.isError else 0)]
|
|
157
160
|
response: dict[str, Any] = {
|
|
@@ -313,7 +316,7 @@ class OpenAIAgent(ToolAgent[ResponseInputItemParam, OpenAIConfig]):
|
|
|
313
316
|
elif item.action is not None:
|
|
314
317
|
arguments = item.action.to_dict()
|
|
315
318
|
else:
|
|
316
|
-
|
|
319
|
+
arguments = {"actions": []}
|
|
317
320
|
call_dict: dict[str, Any] = {
|
|
318
321
|
"name": "computer",
|
|
319
322
|
"arguments": arguments,
|
|
@@ -38,17 +38,24 @@ def last_image_content(result: MCPToolResult) -> mcp_types.ImageContent | None:
|
|
|
38
38
|
OPENAI_KEY_ALIASES: dict[str, str] = {
|
|
39
39
|
"return": "Return",
|
|
40
40
|
"escape": "Escape",
|
|
41
|
+
"esc": "Escape",
|
|
41
42
|
"arrowup": "Up",
|
|
43
|
+
"up": "Up",
|
|
42
44
|
"arrowdown": "Down",
|
|
45
|
+
"down": "Down",
|
|
43
46
|
"arrowleft": "Left",
|
|
47
|
+
"left": "Left",
|
|
44
48
|
"arrowright": "Right",
|
|
49
|
+
"right": "Right",
|
|
45
50
|
"backspace": "BackSpace",
|
|
46
51
|
"delete": "Delete",
|
|
52
|
+
"del": "Delete",
|
|
47
53
|
"tab": "Tab",
|
|
48
54
|
"space": "space",
|
|
49
55
|
"control": "Control_L",
|
|
50
56
|
"ctrl": "Control_L",
|
|
51
57
|
"alt": "Alt_L",
|
|
58
|
+
"option": "Alt_L",
|
|
52
59
|
"shift": "Shift_L",
|
|
53
60
|
"meta": "Super_L",
|
|
54
61
|
"cmd": "Super_L",
|
|
@@ -62,18 +69,6 @@ OPENAI_KEY_ALIASES: dict[str, str] = {
|
|
|
62
69
|
"enter": "Return",
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
_SCREENSHOT_ACTIONS = {
|
|
66
|
-
"screenshot",
|
|
67
|
-
"click",
|
|
68
|
-
"double_click",
|
|
69
|
-
"scroll",
|
|
70
|
-
"type",
|
|
71
|
-
"move",
|
|
72
|
-
"keypress",
|
|
73
|
-
"drag",
|
|
74
|
-
"wait",
|
|
75
|
-
}
|
|
76
|
-
|
|
77
72
|
|
|
78
73
|
class OpenAIComputerTool(RFBTool):
|
|
79
74
|
"""Translate OpenAI native computer calls into RFBTool primitives."""
|
|
@@ -90,14 +85,16 @@ class OpenAIComputerTool(RFBTool):
|
|
|
90
85
|
|
|
91
86
|
async def execute(self, arguments: dict[str, Any]) -> MCPToolResult:
|
|
92
87
|
actions = arguments.get("actions")
|
|
93
|
-
if
|
|
88
|
+
if "actions" in arguments:
|
|
89
|
+
if not isinstance(actions, list):
|
|
90
|
+
return await self._error_result("actions must be a list")
|
|
94
91
|
action_list = cast("list[Any]", actions)
|
|
95
92
|
if not action_list:
|
|
96
|
-
return
|
|
93
|
+
return await self._error_result("actions list is empty")
|
|
97
94
|
result = MCPToolResult(content=[], isError=False)
|
|
98
95
|
for index, raw_action in enumerate(action_list):
|
|
99
96
|
if not isinstance(raw_action, dict):
|
|
100
|
-
return
|
|
97
|
+
return await self._error_result("actions must be objects")
|
|
101
98
|
action = cast("dict[str, Any]", raw_action)
|
|
102
99
|
result = await self._execute_one(
|
|
103
100
|
action,
|
|
@@ -108,6 +105,17 @@ class OpenAIComputerTool(RFBTool):
|
|
|
108
105
|
return result
|
|
109
106
|
return await self._execute_one(arguments, ensure_screenshot=True)
|
|
110
107
|
|
|
108
|
+
async def _error_result(self, message: str) -> MCPToolResult:
|
|
109
|
+
result = tool_err(message)
|
|
110
|
+
try:
|
|
111
|
+
result.content.extend((await self.screenshot()).content)
|
|
112
|
+
except TimeoutError as exc:
|
|
113
|
+
exc.add_note(message)
|
|
114
|
+
raise
|
|
115
|
+
except Exception as exc:
|
|
116
|
+
return tool_err(f"{message}\nScreenshot capture also failed: {exc}")
|
|
117
|
+
return result
|
|
118
|
+
|
|
111
119
|
async def _execute_one(
|
|
112
120
|
self,
|
|
113
121
|
arguments: dict[str, Any],
|
|
@@ -116,26 +124,28 @@ class OpenAIComputerTool(RFBTool):
|
|
|
116
124
|
) -> MCPToolResult:
|
|
117
125
|
action_type = arguments.get("type")
|
|
118
126
|
if not isinstance(action_type, str):
|
|
119
|
-
return
|
|
127
|
+
return await self._error_result("type is required")
|
|
120
128
|
|
|
121
129
|
if action_type == "response":
|
|
122
130
|
text = arguments.get("text")
|
|
123
131
|
if not isinstance(text, str):
|
|
124
|
-
return
|
|
125
|
-
|
|
132
|
+
return await self._error_result("text is required for response")
|
|
133
|
+
result = MCPToolResult(
|
|
126
134
|
content=[mcp_types.TextContent(type="text", text=text)],
|
|
127
135
|
)
|
|
136
|
+
if ensure_screenshot:
|
|
137
|
+
result.content.extend((await self.screenshot()).content)
|
|
138
|
+
return result
|
|
128
139
|
|
|
129
140
|
try:
|
|
130
141
|
await self._dispatch(action_type, arguments)
|
|
142
|
+
except TimeoutError:
|
|
143
|
+
raise
|
|
131
144
|
except Exception as exc:
|
|
132
145
|
logger.exception("OpenAIComputerTool action %s failed", action_type)
|
|
133
|
-
return
|
|
146
|
+
return await self._error_result(f"computer action {action_type!r} failed: {exc}")
|
|
134
147
|
|
|
135
|
-
|
|
136
|
-
ensure_screenshot and action_type in _SCREENSHOT_ACTIONS and action_type != "screenshot"
|
|
137
|
-
)
|
|
138
|
-
if action_type == "screenshot" or needs_screenshot:
|
|
148
|
+
if action_type == "screenshot" or ensure_screenshot:
|
|
139
149
|
return await self.screenshot()
|
|
140
150
|
return MCPToolResult(content=[], isError=False)
|
|
141
151
|
|
|
@@ -144,13 +154,13 @@ class OpenAIComputerTool(RFBTool):
|
|
|
144
154
|
return
|
|
145
155
|
|
|
146
156
|
if action_type == "click":
|
|
147
|
-
|
|
148
|
-
if
|
|
149
|
-
button = "middle"
|
|
150
|
-
elif isinstance(button_raw, str):
|
|
151
|
-
button = button_raw
|
|
152
|
-
else:
|
|
157
|
+
button = args.get("button", "left")
|
|
158
|
+
if button is None:
|
|
153
159
|
button = "left"
|
|
160
|
+
elif button == "wheel":
|
|
161
|
+
button = "middle"
|
|
162
|
+
if button not in ("left", "middle", "right"):
|
|
163
|
+
raise ValueError(f"Unsupported mouse button: {button!r}")
|
|
154
164
|
hold = _hold_keys(args.get("keys"))
|
|
155
165
|
await self.click(
|
|
156
166
|
args.get("x"),
|
|
@@ -183,23 +193,28 @@ class OpenAIComputerTool(RFBTool):
|
|
|
183
193
|
|
|
184
194
|
elif action_type == "type":
|
|
185
195
|
text = args.get("text")
|
|
186
|
-
if isinstance(text, str):
|
|
187
|
-
|
|
196
|
+
if not isinstance(text, str):
|
|
197
|
+
raise ValueError("text is required for type")
|
|
198
|
+
await self.type_text(text)
|
|
188
199
|
|
|
189
200
|
elif action_type == "wait":
|
|
190
|
-
|
|
201
|
+
duration = args.get("ms")
|
|
202
|
+
ms = 1000 if duration is None else int(duration)
|
|
203
|
+
if ms < 0:
|
|
204
|
+
raise ValueError("wait duration must be non-negative")
|
|
191
205
|
await self.wait(ms)
|
|
192
206
|
|
|
193
207
|
elif action_type == "move":
|
|
194
208
|
x, y = args.get("x"), args.get("y")
|
|
195
|
-
if x is
|
|
196
|
-
|
|
209
|
+
if x is None or y is None:
|
|
210
|
+
raise ValueError("x and y are required for move")
|
|
211
|
+
await self.move(int(x), int(y))
|
|
197
212
|
|
|
198
213
|
elif action_type == "keypress":
|
|
199
|
-
keys = args.get("keys")
|
|
200
|
-
if
|
|
201
|
-
|
|
202
|
-
|
|
214
|
+
keys = _hold_keys(args.get("keys"))
|
|
215
|
+
if not keys:
|
|
216
|
+
raise ValueError("keypress requires at least one key")
|
|
217
|
+
await self.press_keys(keys)
|
|
203
218
|
|
|
204
219
|
elif action_type == "drag":
|
|
205
220
|
path_raw = args.get("path")
|
|
@@ -208,7 +223,7 @@ class OpenAIComputerTool(RFBTool):
|
|
|
208
223
|
points = cast("list[dict[str, Any]]", path_raw)
|
|
209
224
|
if len(points) < 2:
|
|
210
225
|
raise ValueError("drag requires a path with at least 2 points")
|
|
211
|
-
path = [(int(p
|
|
226
|
+
path = [(int(p["x"]), int(p["y"])) for p in points]
|
|
212
227
|
hold = _hold_keys(args.get("keys"))
|
|
213
228
|
await self.drag(path, hold_keys=hold)
|
|
214
229
|
|
|
@@ -224,9 +239,11 @@ def _map_key(key: str) -> str:
|
|
|
224
239
|
|
|
225
240
|
|
|
226
241
|
def _hold_keys(keys: Any) -> list[str] | None:
|
|
227
|
-
if
|
|
242
|
+
if keys is None:
|
|
228
243
|
return None
|
|
229
|
-
|
|
244
|
+
if not isinstance(keys, list) or not all(isinstance(key, str) and key for key in keys):
|
|
245
|
+
raise ValueError("keys must be a list of non-empty strings")
|
|
246
|
+
return [_map_key(key) for key in cast("list[str]", keys)]
|
|
230
247
|
|
|
231
248
|
|
|
232
249
|
__all__ = ["OPENAI_COMPUTER_SPEC", "OpenAIComputerTool"]
|
|
@@ -9,12 +9,16 @@ from types import SimpleNamespace
|
|
|
9
9
|
from typing import TYPE_CHECKING, Any
|
|
10
10
|
from unittest.mock import AsyncMock, patch
|
|
11
11
|
|
|
12
|
+
import mcp.types as mcp_types
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
12
15
|
from hud.agents.openai.tools.computer import (
|
|
13
16
|
OpenAIComputerTool,
|
|
14
17
|
_hold_keys,
|
|
15
18
|
_map_key,
|
|
16
19
|
)
|
|
17
|
-
from hud.agents.tools.base import result_text
|
|
20
|
+
from hud.agents.tools.base import result_text
|
|
21
|
+
from hud.types import MCPToolResult
|
|
18
22
|
|
|
19
23
|
if TYPE_CHECKING:
|
|
20
24
|
from collections.abc import Iterable
|
|
@@ -27,9 +31,11 @@ class RecordingOpenAI(OpenAIComputerTool):
|
|
|
27
31
|
self.calls: list[tuple[Any, ...]] = []
|
|
28
32
|
self.client = SimpleNamespace(width=200, height=100)
|
|
29
33
|
|
|
30
|
-
async def screenshot(self) ->
|
|
34
|
+
async def screenshot(self) -> MCPToolResult:
|
|
31
35
|
self.calls.append(("screenshot",))
|
|
32
|
-
return
|
|
36
|
+
return MCPToolResult(
|
|
37
|
+
content=[mcp_types.ImageContent(type="image", data="c2hvdA==", mimeType="image/png")],
|
|
38
|
+
)
|
|
33
39
|
|
|
34
40
|
async def click(
|
|
35
41
|
self,
|
|
@@ -76,8 +82,10 @@ class RecordingOpenAI(OpenAIComputerTool):
|
|
|
76
82
|
def test_key_mapping() -> None:
|
|
77
83
|
assert _map_key("ctrl") == "Control_L"
|
|
78
84
|
assert _map_key("x") == "x"
|
|
85
|
+
assert _map_key("ESC") == "Escape"
|
|
86
|
+
assert _map_key("RIGHT") == "Right"
|
|
79
87
|
assert _hold_keys(["ctrl", "c"]) == ["Control_L", "c"]
|
|
80
|
-
assert _hold_keys(
|
|
88
|
+
assert _hold_keys(None) is None
|
|
81
89
|
|
|
82
90
|
|
|
83
91
|
def test_to_params() -> None:
|
|
@@ -99,19 +107,23 @@ async def test_type_and_keypress() -> None:
|
|
|
99
107
|
assert ("keys", ("Control_L", "c")) in tool.calls
|
|
100
108
|
|
|
101
109
|
|
|
102
|
-
|
|
110
|
+
@pytest.mark.parametrize(("ms", "seconds"), [(500, 0.5), (0, 0)])
|
|
111
|
+
async def test_drag_and_wait(ms: int, seconds: float) -> None:
|
|
103
112
|
tool = RecordingOpenAI()
|
|
104
113
|
await tool.execute({"type": "drag", "path": [{"x": 0, "y": 0}, {"x": 5, "y": 5}]})
|
|
105
114
|
with patch("hud.agents.tools.rfb.asyncio.sleep", new_callable=AsyncMock) as sleep:
|
|
106
|
-
await tool.execute({"type": "wait", "ms":
|
|
115
|
+
result = await tool.execute({"type": "wait", "ms": ms})
|
|
107
116
|
assert ("drag", ((0, 0), (5, 5))) in tool.calls
|
|
108
|
-
|
|
117
|
+
assert not result.isError
|
|
118
|
+
sleep.assert_awaited_once_with(seconds)
|
|
109
119
|
|
|
110
120
|
|
|
111
|
-
async def
|
|
121
|
+
async def test_response_action_returns_text_and_screenshot() -> None:
|
|
112
122
|
tool = RecordingOpenAI()
|
|
113
123
|
result = await tool.execute({"type": "response", "text": "all done"})
|
|
114
124
|
assert result_text(result) == "all done"
|
|
125
|
+
assert any(isinstance(block, mcp_types.ImageContent) for block in result.content)
|
|
126
|
+
assert tool.calls[-1] == ("screenshot",)
|
|
115
127
|
|
|
116
128
|
|
|
117
129
|
async def test_actions_list_runs_each() -> None:
|
|
@@ -123,12 +135,40 @@ async def test_actions_list_runs_each() -> None:
|
|
|
123
135
|
assert ("type", "a") in tool.calls
|
|
124
136
|
|
|
125
137
|
|
|
126
|
-
|
|
138
|
+
@pytest.mark.parametrize(
|
|
139
|
+
"arguments", [{"actions": []}, {"actions": "invalid", "type": "move", "x": 3, "y": 4}]
|
|
140
|
+
)
|
|
141
|
+
async def test_invalid_actions_errors(arguments: dict[str, Any]) -> None:
|
|
127
142
|
tool = RecordingOpenAI()
|
|
128
|
-
assert (await tool.execute(
|
|
143
|
+
assert (await tool.execute(arguments)).isError
|
|
144
|
+
assert tool.calls == [("screenshot",)]
|
|
129
145
|
|
|
130
146
|
|
|
131
147
|
async def test_invalid_type_errors() -> None:
|
|
132
148
|
tool = RecordingOpenAI()
|
|
133
149
|
assert (await tool.execute({"type": "frobnicate"})).isError
|
|
134
150
|
assert (await tool.execute({})).isError
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@pytest.mark.parametrize(
|
|
154
|
+
"action",
|
|
155
|
+
[
|
|
156
|
+
{"type": "type"},
|
|
157
|
+
{"type": "move", "x": 10},
|
|
158
|
+
{"type": "keypress", "keys": "ESC"},
|
|
159
|
+
{"type": "keypress", "keys": []},
|
|
160
|
+
{"type": "drag", "path": [{"x": 1, "y": 2}, {"x": 3}]},
|
|
161
|
+
{"type": "click", "button": 1},
|
|
162
|
+
{"type": "wait", "ms": -1},
|
|
163
|
+
],
|
|
164
|
+
)
|
|
165
|
+
async def test_invalid_action_stops_batch_and_returns_screenshot(action: dict[str, Any]) -> None:
|
|
166
|
+
tool = RecordingOpenAI()
|
|
167
|
+
result = await tool.execute(
|
|
168
|
+
{"actions": [{"type": "move", "x": 3, "y": 4}, action, {"type": "type", "text": "later"}]},
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
assert result.isError
|
|
172
|
+
assert result_text(result)
|
|
173
|
+
assert any(isinstance(block, mcp_types.ImageContent) for block in result.content)
|
|
174
|
+
assert tool.calls == [("move", 3, 4), ("screenshot",)]
|