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