hud 0.6.12.dev0__tar.gz → 0.6.13.dev1__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.12.dev0 → hud-0.6.13.dev1}/PKG-INFO +9 -4
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/__init__.py +2 -5
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/__init__.py +19 -5
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/claude/tools/computer.py +17 -6
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/claude/tools/tests/test_computer.py +33 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/misc/response_automation.py +0 -1
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai/agent.py +6 -4
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai/tools/base.py +1 -2
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai/tools/coding.py +60 -17
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai/tools/computer.py +7 -1
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai_compatible/tools/filesystem.py +2 -9
- hud-0.6.13.dev1/hud/agents/robot/__init__.py +39 -0
- hud-0.6.13.dev1/hud/agents/robot/adapter.py +118 -0
- hud-0.6.13.dev1/hud/agents/robot/agent.py +174 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/robot/batching.py +23 -26
- hud-0.6.13.dev1/hud/agents/robot/dataset.py +243 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/robot/model.py +1 -1
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tests/test_base.py +42 -6
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tests/test_openai_agent.py +37 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tests/test_provider_native_tools.py +164 -41
- hud-0.6.13.dev1/hud/agents/tests/test_robot_dataset_writer.py +162 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tool_agent.py +5 -2
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tools/rfb.py +17 -5
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tools/ssh.py +22 -6
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/types.py +5 -1
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/capabilities/base.py +2 -1
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/capabilities/rfb.py +15 -7
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/capabilities/robot.py +44 -19
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/capabilities/ssh.py +7 -32
- hud-0.6.13.dev1/hud/capabilities/tests/test_rfb.py +76 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/__init__.py +4 -10
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/eval.py +2 -1
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/serve.py +16 -21
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/tests/test_cli_init.py +0 -11
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/trace.py +11 -8
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/source.py +2 -10
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/tests/test_version_check.py +3 -15
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/version_check.py +2 -34
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/clients/client.py +26 -11
- hud-0.6.13.dev1/hud/environment/__init__.py +100 -0
- hud-0.6.13.dev1/hud/environment/egress.py +661 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/environment/env.py +35 -0
- hud-0.6.13.dev1/hud/environment/robot/__init__.py +38 -0
- hud-0.6.13.dev1/hud/environment/robot/bridge.py +618 -0
- hud-0.6.13.dev1/hud/environment/robot/endpoint.py +350 -0
- hud-0.6.13.dev1/hud/environment/robot/gym.py +718 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/environment/server.py +172 -88
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/environment/tests/test_capability_backing.py +173 -0
- hud-0.6.13.dev1/hud/environment/tests/test_loader.py +161 -0
- hud-0.6.13.dev1/hud/environment/tests/test_robot_regressions.py +493 -0
- hud-0.6.13.dev1/hud/environment/tests/test_sessions.py +89 -0
- hud-0.6.13.dev1/hud/environment/tests/test_workspace.py +1140 -0
- hud-0.6.13.dev1/hud/environment/workspace.py +1533 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/__init__.py +2 -0
- hud-0.6.13.dev1/hud/eval/docker-seccomp.json +21 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/job.py +37 -6
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/run.py +131 -73
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/runtime.py +284 -75
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/sync.py +1 -1
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/task.py +18 -12
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/taskset.py +54 -28
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/tests/test_docker_provider.py +295 -11
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/tests/test_hosted.py +136 -5
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/tests/test_job.py +48 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/tests/test_rollout.py +227 -10
- hud-0.6.13.dev1/hud/eval/tests/test_shared.py +178 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/tests/test_task.py +28 -7
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/graders/bash.py +14 -14
- hud-0.6.13.dev1/hud/telemetry/robot/__init__.py +12 -0
- hud-0.6.13.dev1/hud/telemetry/robot/recorder.py +313 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/train/client.py +49 -6
- hud-0.6.13.dev1/hud/train/tests/__init__.py +1 -0
- hud-0.6.13.dev1/hud/train/tests/test_client.py +86 -0
- hud-0.6.13.dev1/hud/utils/docker.py +32 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/utils/gateway.py +33 -3
- hud-0.6.13.dev1/hud/utils/naming.py +19 -0
- hud-0.6.13.dev1/hud/utils/process.py +222 -0
- hud-0.6.13.dev1/hud/utils/tests/__init__.py +0 -0
- hud-0.6.13.dev1/hud/utils/tests/test_gateway.py +160 -0
- hud-0.6.13.dev1/hud/utils/tests/test_process.py +85 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/version.py +1 -1
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/pyproject.toml +13 -7
- hud-0.6.12.dev0/hud/agents/robot/__init__.py +0 -41
- hud-0.6.12.dev0/hud/agents/robot/_types.py +0 -12
- hud-0.6.12.dev0/hud/agents/robot/adapter.py +0 -105
- hud-0.6.12.dev0/hud/agents/robot/agent.py +0 -169
- hud-0.6.12.dev0/hud/agents/robot/record.py +0 -230
- hud-0.6.12.dev0/hud/environment/__init__.py +0 -58
- hud-0.6.12.dev0/hud/environment/robot/__init__.py +0 -29
- hud-0.6.12.dev0/hud/environment/robot/bridge.py +0 -176
- hud-0.6.12.dev0/hud/environment/robot/endpoint.py +0 -210
- hud-0.6.12.dev0/hud/environment/robot/sim_runner.py +0 -111
- hud-0.6.12.dev0/hud/environment/tests/test_loader.py +0 -31
- hud-0.6.12.dev0/hud/environment/tests/test_workspace.py +0 -244
- hud-0.6.12.dev0/hud/environment/workspace.py +0 -724
- hud-0.6.12.dev0/hud/utils/process.py +0 -130
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/.gitignore +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/LICENSE +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/README.md +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/__main__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/_legacy.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/base.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/browser_use/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/browser_use/agent.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/claude/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/claude/agent.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/claude/sdk/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/claude/sdk/agent.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/claude/sdk/computer_mcp.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/claude/tools/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/claude/tools/base.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/claude/tools/coding.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/claude/tools/hosted.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/claude/tools/mcp_proxy.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/claude/tools/settings.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/claude/tools/tests/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/gemini/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/gemini/agent.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/gemini/settings.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/gemini/tools/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/gemini/tools/base.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/gemini/tools/coding.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/gemini/tools/computer.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/gemini/tools/filesystem.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/gemini/tools/hosted.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/gemini/tools/mcp_proxy.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/gemini/tools/tests/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/gemini/tools/tests/test_computer.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/misc/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai/tools/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai/tools/apply_patch.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai/tools/hosted.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai/tools/mcp_proxy.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai/tools/strict_schema.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai/tools/tests/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai/tools/tests/test_computer.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai/tools/tests/test_strict_schema.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai_compatible/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai_compatible/agent.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai_compatible/tools/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai_compatible/tools/base.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/openai_compatible/tools/mcp_proxy.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tests/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tests/test_apply_patch.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tests/test_claude_agent.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tests/test_claude_sdk_agent.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tests/test_gemini_agent.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tests/test_openai_compatible_agent.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tests/test_tool_agent.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tests/test_trace.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tools/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tools/base.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tools/hosted.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/agents/tools/mcp.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/capabilities/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/capabilities/cdp.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/capabilities/mcp.py +0 -0
- {hud-0.6.12.dev0/hud/cli/utils → hud-0.6.13.dev1/hud/capabilities}/tests/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/__main__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/cancel.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/client.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/deploy.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/init.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/jobs.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/login.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/models.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/presets.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/sync.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/task.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/templates.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/tests/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/tests/test_cli_main.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/tests/test_cli_more_wrappers.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/tests/test_deploy.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/tests/test_eval_bedrock.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/tests/test_eval_config.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/tests/test_init.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/tests/test_main_module.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/tests/test_sync_export.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/api.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/build_display.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/build_logs.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/config.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/context.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/display.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/jobs.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/registry.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/tasks.py +0 -0
- {hud-0.6.12.dev0/hud/environment → hud-0.6.13.dev1/hud/cli/utils}/tests/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/tests/test_build_display.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/tests/test_config.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/tests/test_context.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/tests/test_registry.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/tests/test_source.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/cli/utils/tests/test_tasks.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/clients/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/clients/tests/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/clients/tests/test_connect.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/conftest.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/environment/file_tracker.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/environment/legacy.py +0 -0
- {hud-0.6.12.dev0/hud/eval → hud-0.6.13.dev1/hud/environment}/tests/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/environment/tests/conftest.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/environment/tests/test_file_tracker.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/environment/tests/test_file_tracking.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/environment/tests/test_legacy.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/environment/tests/test_manifest.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/environment/tests/test_server.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/environment/tests/test_tunnel.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/environment/utils.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/chat.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/file_tracking.py +0 -0
- {hud-0.6.12.dev0/hud/telemetry → hud-0.6.13.dev1/hud/eval}/tests/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/tests/test_chat.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/tests/test_file_tracking_observer.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/tests/test_local_runtime.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/eval/tests/test_sync.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/graders/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/graders/base.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/graders/combine.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/graders/judge.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/graders/results.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/graders/text.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/patches/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/patches/mcp_patches.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/patches/tests/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/patches/tests/test_warnings.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/patches/warnings.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/py.typed +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/server.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/settings.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/telemetry/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/telemetry/context.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/telemetry/exporter.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/telemetry/instrument.py +0 -0
- {hud-0.6.12.dev0/hud/agents → hud-0.6.13.dev1/hud/telemetry}/robot/video.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/telemetry/span.py +0 -0
- {hud-0.6.12.dev0/hud/utils → hud-0.6.13.dev1/hud/telemetry}/tests/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/telemetry/tests/test_exporter.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/telemetry/tests/test_instrument.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/train/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/train/base.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/train/types.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/types.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/utils/__init__.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/utils/exceptions.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/utils/hints.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/utils/hud_console.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/utils/modules.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/utils/platform.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/utils/requests.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/utils/serialization.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/utils/tests/test_exceptions.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/utils/tests/test_hints.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/utils/tests/test_hud_console.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/utils/tests/test_platform.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/utils/tests/test_requests.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/utils/tests/test_serialization.py +0 -0
- {hud-0.6.12.dev0 → hud-0.6.13.dev1}/hud/utils/time.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hud
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.13.dev1
|
|
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
|
|
@@ -37,16 +37,16 @@ Requires-Python: <3.13,>=3.11
|
|
|
37
37
|
Requires-Dist: anthropic>=0.78.0
|
|
38
38
|
Requires-Dist: asyncssh>=2.23.0
|
|
39
39
|
Requires-Dist: asyncvnc>=1.3.0
|
|
40
|
-
Requires-Dist: fastmcp==3.0
|
|
40
|
+
Requires-Dist: fastmcp==3.2.0
|
|
41
41
|
Requires-Dist: google-genai
|
|
42
42
|
Requires-Dist: httpx<1,>=0.23.0
|
|
43
43
|
Requires-Dist: mcp<2.0,>=1.24.0
|
|
44
|
-
Requires-Dist: openai
|
|
44
|
+
Requires-Dist: openai>=2.45.0
|
|
45
45
|
Requires-Dist: packaging>=21.0
|
|
46
46
|
Requires-Dist: pillow>=11.0.0
|
|
47
47
|
Requires-Dist: prompt-toolkit==3.0.51
|
|
48
48
|
Requires-Dist: pydantic-settings<3,>=2.2
|
|
49
|
-
Requires-Dist: pydantic<3,>=2.
|
|
49
|
+
Requires-Dist: pydantic<3,>=2.10
|
|
50
50
|
Requires-Dist: questionary==2.1.0
|
|
51
51
|
Requires-Dist: rich>=13.0.0
|
|
52
52
|
Requires-Dist: typer>=0.9.0
|
|
@@ -60,7 +60,11 @@ Requires-Dist: browser-use>=0.11.13; extra == 'browseruse'
|
|
|
60
60
|
Provides-Extra: daytona
|
|
61
61
|
Requires-Dist: daytona>=0.100; extra == 'daytona'
|
|
62
62
|
Provides-Extra: dev
|
|
63
|
+
Requires-Dist: av>=12; extra == 'dev'
|
|
63
64
|
Requires-Dist: dotenv>=0.9.9; extra == 'dev'
|
|
65
|
+
Requires-Dist: gymnasium>=1.0; extra == 'dev'
|
|
66
|
+
Requires-Dist: numpy>=1.24; extra == 'dev'
|
|
67
|
+
Requires-Dist: openpi-client>=0.1.2; extra == 'dev'
|
|
64
68
|
Requires-Dist: pyright==1.1.407; extra == 'dev'
|
|
65
69
|
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
66
70
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
@@ -71,6 +75,7 @@ Provides-Extra: modal
|
|
|
71
75
|
Requires-Dist: modal>=1.0; extra == 'modal'
|
|
72
76
|
Provides-Extra: robot
|
|
73
77
|
Requires-Dist: av>=12; extra == 'robot'
|
|
78
|
+
Requires-Dist: gymnasium>=1.0; extra == 'robot'
|
|
74
79
|
Requires-Dist: numpy>=1.24; extra == 'robot'
|
|
75
80
|
Requires-Dist: openpi-client>=0.1.2; extra == 'robot'
|
|
76
81
|
Provides-Extra: train
|
|
@@ -32,6 +32,7 @@ from .eval import (
|
|
|
32
32
|
from .telemetry.instrument import instrument
|
|
33
33
|
from .train import TrainingClient
|
|
34
34
|
from .types import Trace
|
|
35
|
+
from .version import __version__
|
|
35
36
|
|
|
36
37
|
_install_v5_compat()
|
|
37
38
|
|
|
@@ -56,11 +57,7 @@ __all__ = [
|
|
|
56
57
|
"Taskset",
|
|
57
58
|
"Trace",
|
|
58
59
|
"TrainingClient",
|
|
60
|
+
"__version__",
|
|
59
61
|
"connect",
|
|
60
62
|
"instrument",
|
|
61
63
|
]
|
|
62
|
-
|
|
63
|
-
try:
|
|
64
|
-
from .version import __version__
|
|
65
|
-
except ImportError:
|
|
66
|
-
__version__ = "unknown"
|
|
@@ -7,9 +7,10 @@ from __future__ import annotations
|
|
|
7
7
|
|
|
8
8
|
from typing import TYPE_CHECKING, Any, cast
|
|
9
9
|
|
|
10
|
+
from hud.settings import settings
|
|
10
11
|
from hud.types import AgentType
|
|
12
|
+
from hud.utils.exceptions import HudAuthenticationError
|
|
11
13
|
from hud.utils.gateway import (
|
|
12
|
-
build_gateway_client,
|
|
13
14
|
gateway_model_aliases,
|
|
14
15
|
list_gateway_models,
|
|
15
16
|
normalize_gateway_model_id,
|
|
@@ -30,14 +31,29 @@ if TYPE_CHECKING:
|
|
|
30
31
|
def create_agent(model: str, **kwargs: Any) -> GatewayAgent:
|
|
31
32
|
"""Create an agent routed through the HUD gateway.
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
Leaves ``model_client`` unset so provider agent constructors build the HUD
|
|
35
|
+
gateway client locally, while :class:`~hud.eval.runtime.HostedRuntime` can
|
|
36
|
+
serialize the config and rebuild the client remotely. Explicitly supplied
|
|
37
|
+
clients remain custom/BYOK and are not serializable.
|
|
38
|
+
|
|
39
|
+
For direct API access with provider API keys, instantiate the agent classes
|
|
40
|
+
directly.
|
|
34
41
|
"""
|
|
42
|
+
direct_credentials = [name for name in ("api_key", "base_url") if name in kwargs]
|
|
43
|
+
if direct_credentials:
|
|
44
|
+
names = ", ".join(direct_credentials)
|
|
45
|
+
raise ValueError(
|
|
46
|
+
f"create_agent routes through the HUD gateway and does not accept {names}; "
|
|
47
|
+
"instantiate the provider agent directly for custom/BYOK credentials"
|
|
48
|
+
)
|
|
49
|
+
if not settings.api_key:
|
|
50
|
+
raise HudAuthenticationError("HUD_API_KEY is required to create a gateway agent")
|
|
51
|
+
|
|
35
52
|
requested_model = model
|
|
36
53
|
model = normalize_gateway_model_id(model)
|
|
37
54
|
agent_type = next((candidate for candidate in AgentType if candidate.value == model), None)
|
|
38
55
|
if agent_type is not None:
|
|
39
56
|
model_id = model
|
|
40
|
-
provider_name = agent_type.gateway_provider
|
|
41
57
|
else:
|
|
42
58
|
try:
|
|
43
59
|
gateway_models = list_gateway_models()
|
|
@@ -69,7 +85,6 @@ def create_agent(model: str, **kwargs: Any) -> GatewayAgent:
|
|
|
69
85
|
except ValueError as exc:
|
|
70
86
|
raise ValueError(f"Model '{model}' has invalid agent type metadata") from exc
|
|
71
87
|
model_id = gateway_model.model_name or model
|
|
72
|
-
provider_name = gateway_model.provider.name or "openai"
|
|
73
88
|
break
|
|
74
89
|
else:
|
|
75
90
|
import difflib
|
|
@@ -95,7 +110,6 @@ def create_agent(model: str, **kwargs: Any) -> GatewayAgent:
|
|
|
95
110
|
raise ValueError(f"Model {requested_model!r} not found in {source}.{hint}")
|
|
96
111
|
|
|
97
112
|
kwargs.setdefault("model", model_id)
|
|
98
|
-
kwargs.setdefault("model_client", build_gateway_client(provider_name))
|
|
99
113
|
# cls/config_cls are matched unions; the pairing is correct by construction.
|
|
100
114
|
config = agent_type.config_cls(**kwargs)
|
|
101
115
|
return agent_type.cls(cast("Any", config))
|
|
@@ -278,13 +278,17 @@ class ClaudeComputerTool(RFBTool):
|
|
|
278
278
|
x0, y0, x1, y1 = (int(v) for v in region_seq)
|
|
279
279
|
except (TypeError, ValueError):
|
|
280
280
|
return tool_err("region must contain 4 integers")
|
|
281
|
-
|
|
282
|
-
cropped = _crop_png(
|
|
281
|
+
screenshot, _ = await self.client.screenshot_png("image/png")
|
|
282
|
+
cropped, mime_type = _crop_png(
|
|
283
|
+
screenshot,
|
|
284
|
+
(x0, y0, x1, y1),
|
|
285
|
+
self.screenshot_mime_type,
|
|
286
|
+
)
|
|
283
287
|
return MCPToolResult(
|
|
284
288
|
content=[
|
|
285
289
|
mcp_types.ImageContent(
|
|
286
290
|
type="image",
|
|
287
|
-
mimeType=
|
|
291
|
+
mimeType=mime_type,
|
|
288
292
|
data=base64.b64encode(cropped).decode("ascii"),
|
|
289
293
|
)
|
|
290
294
|
],
|
|
@@ -349,14 +353,21 @@ def _drag_path(arguments: dict[str, Any]) -> list[tuple[int, int]]:
|
|
|
349
353
|
return path
|
|
350
354
|
|
|
351
355
|
|
|
352
|
-
def _crop_png(
|
|
356
|
+
def _crop_png(
|
|
357
|
+
png: bytes,
|
|
358
|
+
region: tuple[int, int, int, int],
|
|
359
|
+
mime_type: str,
|
|
360
|
+
) -> tuple[bytes, str]:
|
|
353
361
|
from PIL import Image
|
|
354
362
|
|
|
355
363
|
image = Image.open(BytesIO(png))
|
|
356
364
|
cropped = image.crop(region)
|
|
357
365
|
buf = BytesIO()
|
|
358
|
-
|
|
359
|
-
|
|
366
|
+
if mime_type == "image/webp":
|
|
367
|
+
cropped.save(buf, format="WEBP", quality=85)
|
|
368
|
+
else:
|
|
369
|
+
cropped.save(buf, format="PNG")
|
|
370
|
+
return buf.getvalue(), mime_type
|
|
360
371
|
|
|
361
372
|
|
|
362
373
|
__all__ = ["CLAUDE_COMPUTER_SPECS", "ClaudeComputerTool"]
|
|
@@ -5,12 +5,18 @@ computer-use action dispatch (translation to RFB primitives), without a live VNC
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
from io import BytesIO
|
|
8
9
|
from types import SimpleNamespace
|
|
9
10
|
from typing import Any
|
|
11
|
+
from unittest.mock import AsyncMock, patch
|
|
12
|
+
|
|
13
|
+
import mcp.types as mcp_types
|
|
14
|
+
from PIL import Image
|
|
10
15
|
|
|
11
16
|
from hud.agents.claude.tools.computer import (
|
|
12
17
|
CLAUDE_COMPUTER_SPECS,
|
|
13
18
|
ClaudeComputerTool,
|
|
19
|
+
_crop_png,
|
|
14
20
|
_hold_keys,
|
|
15
21
|
_split_keys,
|
|
16
22
|
_translate_key,
|
|
@@ -25,6 +31,7 @@ class RecordingComputer(ClaudeComputerTool):
|
|
|
25
31
|
|
|
26
32
|
def __init__(self) -> None:
|
|
27
33
|
self.calls: list[tuple[Any, ...]] = []
|
|
34
|
+
self.screenshot_mime_type = "image/webp"
|
|
28
35
|
self.client = SimpleNamespace(width=200, height=100)
|
|
29
36
|
|
|
30
37
|
async def screenshot(self) -> Any:
|
|
@@ -147,3 +154,29 @@ async def test_unsupported_action_errors() -> None:
|
|
|
147
154
|
result = await tool.execute({"action": "frobnicate"})
|
|
148
155
|
assert result.isError
|
|
149
156
|
assert "unsupported" in result_text(result).lower()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_crop_reports_encoded_mime_type() -> None:
|
|
160
|
+
source = BytesIO()
|
|
161
|
+
Image.effect_noise((128, 128), 100).convert("RGB").save(source, format="PNG")
|
|
162
|
+
|
|
163
|
+
cropped, mime_type = _crop_png(source.getvalue(), (0, 0, 128, 128), "image/webp")
|
|
164
|
+
|
|
165
|
+
assert mime_type == "image/webp"
|
|
166
|
+
assert cropped.startswith(b"RIFF")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
async def test_zoom_reports_encoded_mime_type() -> None:
|
|
170
|
+
tool = RecordingComputer()
|
|
171
|
+
tool.client.screenshot_png = AsyncMock(return_value=(b"png", "image/png"))
|
|
172
|
+
|
|
173
|
+
with patch(
|
|
174
|
+
"hud.agents.claude.tools.computer._crop_png",
|
|
175
|
+
return_value=(b"webp", "image/webp"),
|
|
176
|
+
) as crop:
|
|
177
|
+
result = await tool._zoom({"region": [0, 0, 10, 10]})
|
|
178
|
+
|
|
179
|
+
tool.client.screenshot_png.assert_awaited_once_with("image/png")
|
|
180
|
+
crop.assert_called_once_with(b"png", (0, 0, 10, 10), "image/webp")
|
|
181
|
+
image = next(block for block in result.content if isinstance(block, mcp_types.ImageContent))
|
|
182
|
+
assert image.mimeType == "image/webp"
|
|
@@ -33,7 +33,7 @@ from hud.utils import gateway
|
|
|
33
33
|
from .tools import OpenAIComputerTool, OpenAIMCPProxyTool, OpenAIShellTool
|
|
34
34
|
from .tools.base import format_openai_result
|
|
35
35
|
from .tools.coding import shell_output
|
|
36
|
-
from .tools.computer import
|
|
36
|
+
from .tools.computer import last_image_content
|
|
37
37
|
|
|
38
38
|
if TYPE_CHECKING:
|
|
39
39
|
import mcp.types as mcp_types
|
|
@@ -104,8 +104,8 @@ class OpenAIAgent(ToolAgent[ResponseInputItemParam, OpenAIConfig]):
|
|
|
104
104
|
tool = state.tools.get(call.name)
|
|
105
105
|
|
|
106
106
|
if isinstance(tool, OpenAIComputerTool):
|
|
107
|
-
screenshot =
|
|
108
|
-
if
|
|
107
|
+
screenshot = last_image_content(result)
|
|
108
|
+
if screenshot is None:
|
|
109
109
|
logger.warning("Computer tool result missing screenshot for call %s", call.name)
|
|
110
110
|
return None
|
|
111
111
|
output = ComputerCallOutput(
|
|
@@ -115,7 +115,9 @@ class OpenAIAgent(ToolAgent[ResponseInputItemParam, OpenAIConfig]):
|
|
|
115
115
|
"Any",
|
|
116
116
|
{
|
|
117
117
|
"type": "computer_screenshot",
|
|
118
|
-
"image_url":
|
|
118
|
+
"image_url": (
|
|
119
|
+
f"data:{screenshot.mimeType or 'image/png'};base64,{screenshot.data}"
|
|
120
|
+
),
|
|
119
121
|
"detail": "original",
|
|
120
122
|
},
|
|
121
123
|
),
|
|
@@ -12,7 +12,6 @@ from openai.types.responses import (
|
|
|
12
12
|
ResponseInputFileContentParam,
|
|
13
13
|
ResponseInputImageContentParam,
|
|
14
14
|
ResponseInputTextContentParam,
|
|
15
|
-
ResponseInputTextParam,
|
|
16
15
|
)
|
|
17
16
|
from openai.types.responses.response_input_param import FunctionCallOutput, ResponseInputItemParam
|
|
18
17
|
|
|
@@ -76,7 +75,7 @@ def format_openai_result(call: MCPToolCall, result: MCPToolResult) -> ResponseIn
|
|
|
76
75
|
logger.warning("Unknown content block type: %s", type(block))
|
|
77
76
|
|
|
78
77
|
if not output_items:
|
|
79
|
-
output_items.append(
|
|
78
|
+
output_items.append(ResponseInputTextContentParam(type="input_text", text=""))
|
|
80
79
|
|
|
81
80
|
return cast(
|
|
82
81
|
"ResponseInputItemParam",
|
|
@@ -7,7 +7,6 @@ from typing import Any, cast
|
|
|
7
7
|
import mcp.types as mcp_types
|
|
8
8
|
|
|
9
9
|
from hud.agents.tools import SSHTool
|
|
10
|
-
from hud.agents.tools.base import result_text
|
|
11
10
|
from hud.types import MCPToolResult
|
|
12
11
|
|
|
13
12
|
from .base import OpenAIToolSpec
|
|
@@ -17,6 +16,9 @@ OPENAI_SHELL_SPEC = OpenAIToolSpec(
|
|
|
17
16
|
api_name="shell",
|
|
18
17
|
)
|
|
19
18
|
|
|
19
|
+
MAX_SHELL_OUTPUT_LENGTH = 10 * 1024 * 1024
|
|
20
|
+
TRUNCATION_MARKER = "[truncated]"
|
|
21
|
+
|
|
20
22
|
|
|
21
23
|
class OpenAIShellTool(SSHTool):
|
|
22
24
|
name = "shell"
|
|
@@ -32,14 +34,31 @@ class OpenAIShellTool(SSHTool):
|
|
|
32
34
|
return {"type": "shell", "environment": {"type": "local"}}
|
|
33
35
|
|
|
34
36
|
async def execute(self, arguments: dict[str, Any]) -> MCPToolResult:
|
|
37
|
+
requested_limit = arguments.get("max_output_length")
|
|
38
|
+
if requested_limit is None:
|
|
39
|
+
max_output_length = MAX_SHELL_OUTPUT_LENGTH
|
|
40
|
+
elif (
|
|
41
|
+
isinstance(requested_limit, bool)
|
|
42
|
+
or not isinstance(requested_limit, int)
|
|
43
|
+
or requested_limit <= 0
|
|
44
|
+
):
|
|
45
|
+
text = "max_output_length must be a positive integer"
|
|
46
|
+
return _shell_result(
|
|
47
|
+
[text],
|
|
48
|
+
is_error=True,
|
|
49
|
+
structured={"output": [shell_output("", text, 1)]},
|
|
50
|
+
)
|
|
51
|
+
else:
|
|
52
|
+
max_output_length = min(requested_limit, MAX_SHELL_OUTPUT_LENGTH)
|
|
53
|
+
|
|
35
54
|
def invalid_commands_result() -> MCPToolResult:
|
|
36
55
|
text = "commands must be a list of strings"
|
|
37
56
|
return _shell_result(
|
|
38
|
-
text,
|
|
57
|
+
[text],
|
|
39
58
|
is_error=True,
|
|
40
59
|
structured={
|
|
41
60
|
"output": [shell_output("", text, 1)],
|
|
42
|
-
"max_output_length":
|
|
61
|
+
"max_output_length": max_output_length,
|
|
43
62
|
},
|
|
44
63
|
)
|
|
45
64
|
|
|
@@ -54,7 +73,7 @@ class OpenAIShellTool(SSHTool):
|
|
|
54
73
|
command_list = cast("list[str]", raw_commands)
|
|
55
74
|
|
|
56
75
|
outputs: list[dict[str, Any]] = []
|
|
57
|
-
|
|
76
|
+
display_outputs: list[str] = []
|
|
58
77
|
is_error = False
|
|
59
78
|
env_arguments: dict[str, Any] = {}
|
|
60
79
|
timeout_ms = arguments.get("timeout_ms")
|
|
@@ -66,40 +85,64 @@ class OpenAIShellTool(SSHTool):
|
|
|
66
85
|
full_cmd = f"timeout {int(env_arguments['timeout_seconds'])} {command}"
|
|
67
86
|
else:
|
|
68
87
|
full_cmd = command
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
text_parts.append(text)
|
|
88
|
+
completed = await self.client.conn.run(full_cmd, check=False)
|
|
89
|
+
stdout = completed.stdout if isinstance(completed.stdout, str) else ""
|
|
90
|
+
stderr = completed.stderr if isinstance(completed.stderr, str) else ""
|
|
91
|
+
exit_code = completed.exit_status if completed.exit_status is not None else 1
|
|
92
|
+
stdout, stderr = _bound_output(stdout, stderr, max_output_length)
|
|
93
|
+
outputs.append(shell_output(stdout, stderr, exit_code))
|
|
94
|
+
display_outputs.append(stdout + stderr)
|
|
95
|
+
is_error = is_error or bool(exit_code)
|
|
78
96
|
|
|
79
97
|
return _shell_result(
|
|
80
|
-
|
|
98
|
+
display_outputs,
|
|
81
99
|
is_error=is_error,
|
|
82
100
|
structured={
|
|
83
101
|
"output": outputs,
|
|
84
|
-
"max_output_length":
|
|
102
|
+
"max_output_length": max_output_length,
|
|
85
103
|
},
|
|
86
104
|
)
|
|
87
105
|
|
|
88
106
|
|
|
89
107
|
def _shell_result(
|
|
90
|
-
|
|
108
|
+
texts: list[str],
|
|
91
109
|
*,
|
|
92
110
|
is_error: bool = False,
|
|
93
111
|
structured: dict[str, Any] | None = None,
|
|
94
112
|
) -> MCPToolResult:
|
|
95
113
|
payload = {"provider_tool": "shell", **(structured or {})}
|
|
96
114
|
return MCPToolResult(
|
|
97
|
-
content=[mcp_types.TextContent(type="text", text=text)
|
|
115
|
+
content=[mcp_types.TextContent(type="text", text=text) for text in texts if text],
|
|
98
116
|
isError=is_error,
|
|
99
117
|
structuredContent=payload,
|
|
100
118
|
)
|
|
101
119
|
|
|
102
120
|
|
|
121
|
+
def _bound_output(stdout: str, stderr: str, limit: int) -> tuple[str, str]:
|
|
122
|
+
total_length = len(stdout) + len(stderr)
|
|
123
|
+
if total_length <= limit:
|
|
124
|
+
return stdout, stderr
|
|
125
|
+
|
|
126
|
+
marker = TRUNCATION_MARKER[:limit]
|
|
127
|
+
available = limit - len(marker)
|
|
128
|
+
prefix_length = (available + 1) // 2
|
|
129
|
+
suffix_length = available // 2
|
|
130
|
+
|
|
131
|
+
stdout_prefix = stdout[:prefix_length]
|
|
132
|
+
stderr_prefix = stderr[: max(0, prefix_length - len(stdout))]
|
|
133
|
+
stderr_suffix = stderr[-suffix_length:] if suffix_length else ""
|
|
134
|
+
stdout_suffix_length = max(0, suffix_length - len(stderr))
|
|
135
|
+
stdout_suffix = stdout[-stdout_suffix_length:] if stdout_suffix_length else ""
|
|
136
|
+
|
|
137
|
+
if len(stdout_prefix) + len(stdout_suffix) < len(stdout):
|
|
138
|
+
stdout = stdout_prefix + marker + stdout_suffix
|
|
139
|
+
stderr = stderr_prefix + stderr_suffix
|
|
140
|
+
else:
|
|
141
|
+
stdout = stdout_prefix + stdout_suffix
|
|
142
|
+
stderr = stderr_prefix + marker + stderr_suffix
|
|
143
|
+
return stdout, stderr
|
|
144
|
+
|
|
145
|
+
|
|
103
146
|
def shell_output(stdout: str, stderr: str, exit_code: int) -> dict[str, Any]:
|
|
104
147
|
return {
|
|
105
148
|
"stdout": stdout,
|
|
@@ -23,9 +23,15 @@ OPENAI_COMPUTER_SPEC = OpenAIToolSpec(
|
|
|
23
23
|
|
|
24
24
|
def last_image_data(result: MCPToolResult) -> str | None:
|
|
25
25
|
"""Base64 data of the most recent screenshot block in a tool result."""
|
|
26
|
+
image = last_image_content(result)
|
|
27
|
+
return image.data if image is not None else None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def last_image_content(result: MCPToolResult) -> mcp_types.ImageContent | None:
|
|
31
|
+
"""Most recent screenshot block in a tool result."""
|
|
26
32
|
for block in reversed(result.content):
|
|
27
33
|
if isinstance(block, mcp_types.ImageContent):
|
|
28
|
-
return block
|
|
34
|
+
return block
|
|
29
35
|
return None
|
|
30
36
|
|
|
31
37
|
|
|
@@ -66,9 +66,6 @@ class ReadTool(_FilesystemTool):
|
|
|
66
66
|
path = arguments.get("filePath")
|
|
67
67
|
if not isinstance(path, str) or not path:
|
|
68
68
|
raise ValueError("filePath is required")
|
|
69
|
-
# Map once so the directory predicate and the file read agree on
|
|
70
|
-
# the same workspace-anchored path.
|
|
71
|
-
path = self.client.map_path(path)
|
|
72
69
|
offset = _read_offset(arguments.get("offset"))
|
|
73
70
|
limit = _positive_int(arguments.get("limit"), default=DEFAULT_READ_LIMIT, name="limit")
|
|
74
71
|
if not (await self.bash(f"test -d {shlex.quote(path)}")).isError:
|
|
@@ -196,9 +193,6 @@ class EditTool(_FilesystemTool):
|
|
|
196
193
|
path = arguments.get("filePath")
|
|
197
194
|
if not isinstance(path, str) or not path:
|
|
198
195
|
raise ValueError("filePath is required")
|
|
199
|
-
# Map once so existence checks, mkdir, and the write all target
|
|
200
|
-
# the same workspace-anchored path.
|
|
201
|
-
path = self.client.map_path(path)
|
|
202
196
|
old = arguments.get("oldString")
|
|
203
197
|
new = arguments.get("newString")
|
|
204
198
|
if not isinstance(old, str):
|
|
@@ -258,7 +252,6 @@ class WriteTool(_FilesystemTool):
|
|
|
258
252
|
path = arguments.get("filePath")
|
|
259
253
|
if not isinstance(path, str) or not path:
|
|
260
254
|
raise ValueError("filePath is required")
|
|
261
|
-
path = self.client.map_path(path)
|
|
262
255
|
content = arguments.get("content")
|
|
263
256
|
if not isinstance(content, str):
|
|
264
257
|
raise ValueError("content is required")
|
|
@@ -294,7 +287,7 @@ class GrepTool(_FilesystemTool):
|
|
|
294
287
|
pattern = arguments.get("pattern")
|
|
295
288
|
if not isinstance(pattern, str):
|
|
296
289
|
raise ValueError("pattern is required")
|
|
297
|
-
path =
|
|
290
|
+
path = str(arguments.get("path") or ".")
|
|
298
291
|
cmd = f"grep -rn {shlex.quote(pattern)} {shlex.quote(path)}"
|
|
299
292
|
include = arguments.get("include")
|
|
300
293
|
if isinstance(include, str) and include:
|
|
@@ -318,7 +311,7 @@ class GlobTool(_FilesystemTool):
|
|
|
318
311
|
pattern = arguments.get("pattern")
|
|
319
312
|
if not isinstance(pattern, str):
|
|
320
313
|
raise ValueError("pattern is required")
|
|
321
|
-
path =
|
|
314
|
+
path = str(arguments.get("path") or ".")
|
|
322
315
|
return await self.bash(f"find {shlex.quote(path)} -name {shlex.quote(pattern)}")
|
|
323
316
|
|
|
324
317
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Agent-side robot harness: drive a ``robot`` env with a VLA policy.
|
|
2
|
+
|
|
3
|
+
- :class:`~.agent.RobotAgent` — the harness: connects to the ``robot``
|
|
4
|
+
capability (claiming a slot token from the setup step when present), reads the contract,
|
|
5
|
+
drives one scalar connection with an open-loop chunk queue. Subclass and
|
|
6
|
+
set ``self.model`` + ``self.adapter``.
|
|
7
|
+
- :class:`~.model.Model` / :class:`~.model.LeRobotModel` /
|
|
8
|
+
:class:`~.model.RemoteModel` — the policy and its inference mechanics.
|
|
9
|
+
- :class:`~.adapter.Adapter` / :class:`~.adapter.LeRobotAdapter` /
|
|
10
|
+
:class:`~.adapter.OpenPIAdapter` — env <-> policy space translation.
|
|
11
|
+
- :class:`~.batching.BatchedAgent` — many concurrent single-env rollouts
|
|
12
|
+
sharing one batched model.
|
|
13
|
+
- :class:`~.dataset.DatasetWriter` — opt-in LeRobot v3 dataset recording
|
|
14
|
+
(``agent.save = True``).
|
|
15
|
+
|
|
16
|
+
This subpackage needs the ``robot`` extra (``pip install 'hud[robot]'``).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from .adapter import Adapter, LeRobotAdapter, OpenPIAdapter
|
|
22
|
+
from .agent import ROBOT_PROTOCOL, RobotAgent
|
|
23
|
+
from .batching import BatchedAgent, BatchedModel
|
|
24
|
+
from .dataset import DatasetWriter
|
|
25
|
+
from .model import LeRobotModel, Model, RemoteModel
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"ROBOT_PROTOCOL",
|
|
29
|
+
"Adapter",
|
|
30
|
+
"BatchedAgent",
|
|
31
|
+
"BatchedModel",
|
|
32
|
+
"DatasetWriter",
|
|
33
|
+
"LeRobotAdapter",
|
|
34
|
+
"LeRobotModel",
|
|
35
|
+
"Model",
|
|
36
|
+
"OpenPIAdapter",
|
|
37
|
+
"RemoteModel",
|
|
38
|
+
"RobotAgent",
|
|
39
|
+
]
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Translate observations and actions between env and policy spaces.
|
|
2
|
+
|
|
3
|
+
The loop calls ``bind``, ``reset``, ``adapt_observation``, and the action hooks.
|
|
4
|
+
Use :class:`LeRobotAdapter` for LeRobot models; subclass for custom wiring;
|
|
5
|
+
``adapter=None`` for pass-through.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
from numpy.typing import NDArray
|
|
14
|
+
|
|
15
|
+
#: A policy-emitted action / chunk array (the robot stack's shared alias).
|
|
16
|
+
ActionArray = NDArray[np.floating[Any]]
|
|
17
|
+
|
|
18
|
+
#: Image types the bundled adapters treat as cameras (vs the state vector).
|
|
19
|
+
IMAGE_TYPES = ("rgb", "bgr", "gray", "depth")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Adapter:
|
|
23
|
+
"""Translate between an env's observation/action spaces and a policy's.
|
|
24
|
+
|
|
25
|
+
Driven by :class:`~hud.agents.robot.agent.RobotAgent`: :meth:`bind` once
|
|
26
|
+
after connect, :meth:`reset` once per episode, then :meth:`adapt_observation`
|
|
27
|
+
per inference and the action hooks per chunk/step. Construct with the
|
|
28
|
+
policy's image-slot names; everything env-side is learned in :meth:`bind`.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
*,
|
|
34
|
+
model_image_keys: list[str] | None = None,
|
|
35
|
+
chunk_size: int | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
#: The policy's ordered image-slot names (model side; known at load time).
|
|
38
|
+
self.model_image_keys: list[str] = list(model_image_keys or [])
|
|
39
|
+
#: Open-loop horizon: how many predicted actions to execute before re-querying.
|
|
40
|
+
#: ``None`` = full model chunk. Applied by :class:`~.agent.RobotAgent`, not here —
|
|
41
|
+
#: so subclass ``adapt_chunk`` overrides stay free of truncation duty.
|
|
42
|
+
self.chunk_size: int | None = chunk_size
|
|
43
|
+
#: The env's action feature and observation layout (set in :meth:`bind`).
|
|
44
|
+
self.action_space: dict[str, Any] = {}
|
|
45
|
+
self.image_keys: list[str] = []
|
|
46
|
+
self.state_key: str | None = None
|
|
47
|
+
|
|
48
|
+
def bind(self, action_space: dict[str, Any], observation_space: dict[str, Any]) -> None:
|
|
49
|
+
"""Learn the env's layout from the contract (``client.spaces()``): image
|
|
50
|
+
features become the camera keys (in contract order), the first non-image
|
|
51
|
+
feature is the state. Override to derive extra env-side parameters."""
|
|
52
|
+
self.action_space = action_space or {}
|
|
53
|
+
self.image_keys = [n for n, f in observation_space.items() if f.get("type") in IMAGE_TYPES]
|
|
54
|
+
self.state_key = next(
|
|
55
|
+
(n for n, f in observation_space.items() if f.get("type") not in IMAGE_TYPES), None
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def reset(self) -> None:
|
|
59
|
+
"""Override only if the adapter is stateful across steps within an episode."""
|
|
60
|
+
|
|
61
|
+
def adapt_observation(self, obs: dict[str, Any], prompt: str) -> Any:
|
|
62
|
+
"""Translate an env observation + task prompt into the policy's input."""
|
|
63
|
+
raise NotImplementedError
|
|
64
|
+
|
|
65
|
+
def adapt_chunk(self, chunk: ActionArray, obs: dict[str, Any]) -> ActionArray:
|
|
66
|
+
"""Translate a freshly-inferred ``[T, A]`` chunk to env space, given the
|
|
67
|
+
(per-slot) query-time observation it was inferred from (default identity).
|
|
68
|
+
|
|
69
|
+
Called once per slot at inference time, so a chunk expressed relative to
|
|
70
|
+
the query state — e.g. joint *deltas* to be added to the query-time
|
|
71
|
+
joints — converts in one shot.
|
|
72
|
+
"""
|
|
73
|
+
return chunk
|
|
74
|
+
|
|
75
|
+
def adapt_action(self, action: ActionArray, obs: dict[str, Any]) -> ActionArray:
|
|
76
|
+
"""Per-step execution-time hook on the popped action (default identity)."""
|
|
77
|
+
return action
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class LeRobotAdapter(Adapter):
|
|
81
|
+
"""Vanilla LeRobot adapter for a standard image/state env, single or batched.
|
|
82
|
+
|
|
83
|
+
Maps env cameras onto the model's image slots in order and converts HWC
|
|
84
|
+
``uint8`` to CHW ``float`` in ``[0, 1]``; state and prompt pass through.
|
|
85
|
+
A batched observation (state ``[N, S]``) maps in one go — cameras become
|
|
86
|
+
``[N, C, H, W]`` and the shared task is repeated to ``N``. Actions are
|
|
87
|
+
identity (postprocess already returns env-space actions).
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def adapt_observation(self, obs: dict[str, Any], prompt: str) -> dict[str, Any]:
|
|
91
|
+
import torch # pyright: ignore[reportMissingImports]
|
|
92
|
+
|
|
93
|
+
torch_mod: Any = torch # torch ships no stubs; keep strict mode quiet
|
|
94
|
+
data = obs["data"]
|
|
95
|
+
state = np.asarray(data[self.state_key], dtype=np.float32)
|
|
96
|
+
batched = state.ndim > 1 # [N, S] vs [S]
|
|
97
|
+
batch: dict[str, Any] = {
|
|
98
|
+
"observation.state": torch_mod.from_numpy(state),
|
|
99
|
+
"task": [prompt] * len(state) if batched else prompt,
|
|
100
|
+
}
|
|
101
|
+
for model_key, env_key in zip(self.model_image_keys, self.image_keys, strict=False):
|
|
102
|
+
img = torch_mod.from_numpy(np.asarray(data[env_key]))
|
|
103
|
+
perm = (0, 3, 1, 2) if batched else (2, 0, 1)
|
|
104
|
+
batch[model_key] = img.permute(*perm).float() / 255.0
|
|
105
|
+
return batch
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class OpenPIAdapter(Adapter):
|
|
109
|
+
"""Unwraps ``obs['data']`` to OpenPI wire keys and attaches the prompt;
|
|
110
|
+
actions are pass-through."""
|
|
111
|
+
|
|
112
|
+
def adapt_observation(self, obs: dict[str, Any], prompt: str) -> dict[str, Any]:
|
|
113
|
+
out = dict(obs["data"])
|
|
114
|
+
out.setdefault("prompt", prompt)
|
|
115
|
+
return out
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
__all__ = ["ActionArray", "Adapter", "LeRobotAdapter", "OpenPIAdapter"]
|