hud-python 0.5.38__tar.gz → 0.5.40__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_python-0.5.38 → hud_python-0.5.40}/PKG-INFO +2 -2
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/gemini.py +149 -8
- hud_python-0.5.40/hud/agents/gemini_cua.py +43 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/tests/test_gemini.py +224 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/types.py +3 -1
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_build.py +1 -1
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_cli_init.py +5 -1
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/tests/test_gemini_tools.py +3 -2
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/computer/gemini.py +79 -94
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/computer/glm.py +3 -15
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/computer/hud.py +69 -1
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/computer/tests/test_computer.py +58 -2
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/computer/tests/test_glm_computer.py +20 -13
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/utils/tests/test_version.py +1 -1
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/version.py +1 -1
- {hud_python-0.5.38 → hud_python-0.5.40}/pyproject.toml +2 -2
- hud_python-0.5.38/hud/agents/gemini_cua.py +0 -366
- {hud_python-0.5.38 → hud_python-0.5.40}/.gitignore +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/LICENSE +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/README.md +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/examples/README.md +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/__main__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/base.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/claude.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/gateway.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/grounded_openai.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/misc/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/misc/integration_test_agent.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/misc/response_agent.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/openai.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/openai_chat.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/operator.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/resolver.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/tests/conftest.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/tests/test_base.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/tests/test_base_runtime.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/tests/test_claude.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/tests/test_grounded_openai_agent.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/tests/test_integration_test_agent.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/tests/test_openai.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/tests/test_operator.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/tests/test_resolver.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/agents/tests/test_run_eval.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/__main__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/analyze.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/build.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/cancel.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/convert/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/convert/base.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/convert/harbor.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/convert/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/convert/tests/conftest.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/convert/tests/test_harbor.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/debug.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/deploy.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/dev.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/eval.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/flows/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/flows/dev.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/flows/init.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/flows/tasks.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/flows/templates.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/flows/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/flows/tests/test_dev.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/init.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/link.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/login.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/models.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/push.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/rl.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/scenario.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/sync.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_analysis_utils.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_analyze.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_analyze_metadata.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_analyze_module.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_build_failure.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_build_module.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_cli_main.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_cli_more_wrappers.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_cli_root.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_convert.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_debug.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_debug_directory_mode.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_deploy.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_dev.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_eval.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_eval_bedrock.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_init.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_lockfile_utils.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_main_module.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_mcp_server.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_push.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_push_happy.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_push_wrapper.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_rl.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_scenario.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_sync.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/tests/test_utils.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/analysis.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/api.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/args.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/build_display.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/build_logs.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/collect.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/config.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/context.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/docker.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/env_check.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/environment.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/git.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/interactive.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/lockfile.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/logging.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/metadata.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/name_check.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/project_config.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/server.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/source_hash.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/tasks.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/taskset.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/tests/test_collect.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/tests/test_config.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/tests/test_docker.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/tests/test_docker_hints.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/tests/test_env_check.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/tests/test_environment.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/tests/test_git.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/tests/test_interactive_module.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/tests/test_logging_utils.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/tests/test_metadata.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/tests/test_source_hash.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/tests/test_tasks.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/validation.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/version_check.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/cli/utils/viewer.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/datasets/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/datasets/loader.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/datasets/runner.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/datasets/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/datasets/tests/test_loader.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/datasets/tests/test_utils.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/datasets/utils.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/connection.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/connectors/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/connectors/base.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/connectors/local.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/connectors/mcp_config.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/connectors/openai.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/connectors/remote.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/environment.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/integrations/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/integrations/adk.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/integrations/anthropic.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/integrations/gemini.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/integrations/langchain.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/integrations/llamaindex.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/integrations/openai.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/mock.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/router.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/scenarios.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/tests/test_connection.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/tests/test_connectors.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/tests/test_environment.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/tests/test_integrations.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/tests/test_local_connectors.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/tests/test_scenarios.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/tests/test_session_id.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/tests/test_tools.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/types.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/utils/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/utils/formats.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/utils/schema.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/environment/utils/tool_wrappers.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/eval/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/eval/context.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/eval/display.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/eval/instrument.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/eval/manager.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/eval/parallel.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/eval/task.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/eval/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/eval/tests/test_context.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/eval/tests/test_eval.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/eval/tests/test_manager.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/eval/tests/test_parallel.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/eval/tests/test_task.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/eval/types.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/eval/utils.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/native/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/native/chat.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/native/graders.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/native/permissions.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/native/skills.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/native/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/native/tests/test_graders.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/patches/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/patches/mcp_patches.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/patches/warnings.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/py.typed +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/server/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/server/context.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/server/helper/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/server/low_level.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/server/router.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/server/server.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/server/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/server/tests/test_add_tool.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/server/tests/test_context.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/server/tests/test_mcp_server_handlers.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/server/tests/test_mcp_server_integration.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/server/tests/test_mcp_server_more.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/server/tests/test_prefix_naming.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/server/tests/test_run_wrapper.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/server/tests/test_server_extra.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/server/tests/test_sigterm_runner.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/services/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/services/chat.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/services/chat_service.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/services/reply_metadata.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/services/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/services/tests/test_chat.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/services/tests/test_chat_service.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/settings.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/shared/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/shared/exceptions.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/shared/hints.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/shared/requests.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/shared/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/shared/tests/test_exceptions.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/shared/tests/test_hints.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/shared/tests/test_requests.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/telemetry/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/telemetry/exporter.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/telemetry/instrument.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/telemetry/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/telemetry/tests/test_eval_telemetry.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/telemetry/tests/test_exporter.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/telemetry/tests/test_instrument.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/agent.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/base.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/apply_patch.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/bash.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/edit.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/gemini_edit.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/gemini_shell.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/gemini_write.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/session.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/shell.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/tests/test_apply_patch.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/tests/test_bash.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/tests/test_bash_extended.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/tests/test_bash_integration.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/tests/test_edit.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/tests/test_shell.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/coding/utils.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/computer/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/computer/anthropic.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/computer/openai.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/computer/qwen.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/computer/settings.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/computer/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/computer/tests/test_compression.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/computer/tests/test_computer_actions.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/elicitation.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/executors/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/executors/base.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/executors/pyautogui.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/executors/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/executors/tests/test_base_executor.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/executors/tests/test_pyautogui_executor.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/executors/xdo.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/filesystem/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/filesystem/base.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/filesystem/gemini.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/filesystem/gemini_read_many.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/filesystem/glob.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/filesystem/grep.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/filesystem/list.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/filesystem/read.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/filesystem/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/filesystem/tests/test_glob.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/filesystem/tests/test_grep.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/filesystem/tests/test_list.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/filesystem/tests/test_read.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/filesystem/tests/test_read_many.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/grounding/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/grounding/config.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/grounding/grounded_tool.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/grounding/grounder.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/grounding/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/grounding/tests/test_grounded_tool.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/hosted/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/hosted/base.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/hosted/code_execution.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/hosted/google_search.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/hosted/tool_search.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/hosted/url_context.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/hosted/web_fetch.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/hosted/web_search.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/jupyter.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/memory/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/memory/base.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/memory/claude.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/memory/gemini.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/memory/session.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/memory/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/memory/tests/test_claude.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/memory/tests/test_gemini.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/memory/tests/test_session.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/native_types.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/playwright.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/response.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/submit.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/tests/test_agent_tool.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/tests/test_base.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/tests/test_elicitation.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/tests/test_init.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/tests/test_jupyter_tool.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/tests/test_native_tool_e2e.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/tests/test_native_types.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/tests/test_playwright_tool.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/tests/test_response.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/tests/test_submit.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/tests/test_tools.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/tests/test_tools_init.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/tests/test_types.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/tests/test_utils.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/types.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/tools/utils.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/types.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/utils/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/utils/env.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/utils/hud_console.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/utils/mcp.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/utils/pretty_errors.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/utils/serialization.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/utils/strict_schema.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/utils/tests/__init__.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/utils/tests/test_init.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/utils/tests/test_pretty_errors.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/utils/tests/test_serialization.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/utils/tests/test_tool_shorthand.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/utils/tool_shorthand.py +0 -0
- {hud_python-0.5.38 → hud_python-0.5.40}/hud/utils/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hud-python
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.40
|
|
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
|
|
@@ -34,7 +34,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
34
34
|
Classifier: Programming Language :: Python :: 3.11
|
|
35
35
|
Classifier: Programming Language :: Python :: 3.12
|
|
36
36
|
Requires-Python: <3.13,>=3.11
|
|
37
|
-
Requires-Dist: a2a-sdk
|
|
37
|
+
Requires-Dist: a2a-sdk==0.3.26
|
|
38
38
|
Requires-Dist: blessed>=1.20.0
|
|
39
39
|
Requires-Dist: fastmcp==3.0.2
|
|
40
40
|
Requires-Dist: httpx<1,>=0.23.0
|
|
@@ -10,6 +10,11 @@ from google import genai
|
|
|
10
10
|
from google.genai import types as genai_types
|
|
11
11
|
|
|
12
12
|
from hud.settings import settings
|
|
13
|
+
from hud.tools.computer.gemini import (
|
|
14
|
+
PREDEFINED_COMPUTER_USE_FUNCTIONS,
|
|
15
|
+
normalize_gemini_computer_use_args,
|
|
16
|
+
)
|
|
17
|
+
from hud.tools.computer.settings import computer_settings
|
|
13
18
|
from hud.types import AgentType, BaseAgentConfig, InferenceResult, MCPToolCall, MCPToolResult
|
|
14
19
|
from hud.utils.hud_console import HUDConsole
|
|
15
20
|
from hud.utils.types import with_signature
|
|
@@ -107,10 +112,17 @@ class GeminiAgent(MCPAgent):
|
|
|
107
112
|
self.top_p = self.config.top_p
|
|
108
113
|
self.top_k = self.config.top_k
|
|
109
114
|
self.max_output_tokens = self.config.max_output_tokens
|
|
115
|
+
self.thinking_level = self.config.thinking_level
|
|
116
|
+
self.include_thoughts = self.config.include_thoughts
|
|
110
117
|
self.hud_console = HUDConsole(logger=logger)
|
|
111
118
|
|
|
112
119
|
# Track mapping from Gemini tool names to MCP tool names
|
|
113
120
|
self._gemini_to_mcp_tool_map: dict[str, str] = {}
|
|
121
|
+
self._computer_tool_name: str | None = None
|
|
122
|
+
self.excluded_predefined_functions = list(self.config.excluded_predefined_functions)
|
|
123
|
+
self.max_recent_turn_with_screenshots = (
|
|
124
|
+
computer_settings.GEMINI_MAX_RECENT_TURN_WITH_SCREENSHOTS
|
|
125
|
+
)
|
|
114
126
|
self.gemini_tools: genai_types.ToolListUnion = []
|
|
115
127
|
|
|
116
128
|
def _on_tools_ready(self) -> None:
|
|
@@ -146,6 +158,7 @@ class GeminiAgent(MCPAgent):
|
|
|
146
158
|
|
|
147
159
|
async def get_response(self, messages: list[genai_types.Content]) -> InferenceResult:
|
|
148
160
|
"""Get response from Gemini including any tool calls."""
|
|
161
|
+
self._remove_old_screenshots(messages)
|
|
149
162
|
tools = self.gemini_tools
|
|
150
163
|
|
|
151
164
|
citations_enabled = bool(
|
|
@@ -154,6 +167,18 @@ class GeminiAgent(MCPAgent):
|
|
|
154
167
|
if citations_enabled and not self._has_google_search_tool():
|
|
155
168
|
tools = [*list(tools), genai_types.Tool(google_search=genai_types.GoogleSearch())]
|
|
156
169
|
|
|
170
|
+
thinking_config = None
|
|
171
|
+
if self.thinking_level is not None or self.include_thoughts:
|
|
172
|
+
thinking_level = (
|
|
173
|
+
genai_types.ThinkingLevel(self.thinking_level.upper())
|
|
174
|
+
if self.thinking_level is not None
|
|
175
|
+
else None
|
|
176
|
+
)
|
|
177
|
+
thinking_config = genai_types.ThinkingConfig(
|
|
178
|
+
thinking_level=thinking_level,
|
|
179
|
+
include_thoughts=self.include_thoughts,
|
|
180
|
+
)
|
|
181
|
+
|
|
157
182
|
# Build generate content config
|
|
158
183
|
generate_config = genai_types.GenerateContentConfig(
|
|
159
184
|
temperature=self.temperature,
|
|
@@ -162,6 +187,7 @@ class GeminiAgent(MCPAgent):
|
|
|
162
187
|
max_output_tokens=self.max_output_tokens,
|
|
163
188
|
tools=tools,
|
|
164
189
|
system_instruction=self.system_prompt,
|
|
190
|
+
thinking_config=thinking_config,
|
|
165
191
|
)
|
|
166
192
|
|
|
167
193
|
# Use async API to avoid blocking the event loop
|
|
@@ -181,8 +207,22 @@ class GeminiAgent(MCPAgent):
|
|
|
181
207
|
collected_tool_calls: list[MCPToolCall] = []
|
|
182
208
|
|
|
183
209
|
if not response.candidates:
|
|
184
|
-
|
|
185
|
-
|
|
210
|
+
detail_parts = []
|
|
211
|
+
for attr in ("prompt_feedback", "usage_metadata"):
|
|
212
|
+
value = getattr(response, attr, None)
|
|
213
|
+
if value is None:
|
|
214
|
+
continue
|
|
215
|
+
if hasattr(value, "model_dump_json"):
|
|
216
|
+
value_repr = value.model_dump_json()
|
|
217
|
+
elif hasattr(value, "model_dump"):
|
|
218
|
+
value_repr = repr(value.model_dump())
|
|
219
|
+
else:
|
|
220
|
+
value_repr = repr(value)
|
|
221
|
+
detail_parts.append(f"{attr}={value_repr}")
|
|
222
|
+
details = "; ".join(detail_parts) if detail_parts else "no response metadata"
|
|
223
|
+
raise RuntimeError(
|
|
224
|
+
f"Gemini response returned no candidates for model {self.config.model}. {details}"
|
|
225
|
+
)
|
|
186
226
|
|
|
187
227
|
candidate = response.candidates[0]
|
|
188
228
|
|
|
@@ -282,11 +322,24 @@ class GeminiAgent(MCPAgent):
|
|
|
282
322
|
return None
|
|
283
323
|
|
|
284
324
|
func_name = part.function_call.name or ""
|
|
285
|
-
mcp_tool_name = self._gemini_to_mcp_tool_map.get(func_name, func_name)
|
|
286
325
|
raw_args = dict(part.function_call.args) if part.function_call.args else {}
|
|
326
|
+
mcp_tool_name = self._gemini_to_mcp_tool_map.get(func_name)
|
|
327
|
+
|
|
328
|
+
if mcp_tool_name:
|
|
329
|
+
return MCPToolCall(
|
|
330
|
+
name=mcp_tool_name,
|
|
331
|
+
arguments=raw_args,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
if self._computer_tool_name and func_name in PREDEFINED_COMPUTER_USE_FUNCTIONS:
|
|
335
|
+
return MCPToolCall(
|
|
336
|
+
name=self._computer_tool_name,
|
|
337
|
+
arguments=normalize_gemini_computer_use_args(func_name, raw_args),
|
|
338
|
+
gemini_name=func_name, # type: ignore[arg-type]
|
|
339
|
+
)
|
|
287
340
|
|
|
288
341
|
return MCPToolCall(
|
|
289
|
-
name=
|
|
342
|
+
name=func_name,
|
|
290
343
|
arguments=raw_args,
|
|
291
344
|
)
|
|
292
345
|
|
|
@@ -303,18 +356,47 @@ class GeminiAgent(MCPAgent):
|
|
|
303
356
|
|
|
304
357
|
# Convert MCP tool results to Gemini format
|
|
305
358
|
response_dict: dict[str, Any] = {}
|
|
359
|
+
is_computer_call = (
|
|
360
|
+
self._computer_tool_name is not None and tool_call.name == self._computer_tool_name
|
|
361
|
+
)
|
|
306
362
|
|
|
307
363
|
if result.isError:
|
|
308
364
|
# Extract error message from content
|
|
309
365
|
error_msg = "Tool execution failed"
|
|
310
366
|
for content in result.content:
|
|
311
367
|
if isinstance(content, types.TextContent):
|
|
368
|
+
if content.text.startswith("__URL__:"):
|
|
369
|
+
continue
|
|
312
370
|
error_msg = content.text
|
|
313
371
|
break
|
|
314
372
|
response_dict["error"] = error_msg
|
|
373
|
+
if is_computer_call:
|
|
374
|
+
response_dict["url"] = self._extract_url(result) or "about:blank"
|
|
315
375
|
else:
|
|
316
376
|
# Process success content
|
|
317
377
|
response_dict["success"] = True
|
|
378
|
+
|
|
379
|
+
screenshot_parts: list[genai_types.FunctionResponsePart] = []
|
|
380
|
+
if is_computer_call:
|
|
381
|
+
url = self._extract_url(result)
|
|
382
|
+
for content in result.content:
|
|
383
|
+
if isinstance(content, types.ImageContent):
|
|
384
|
+
import base64
|
|
385
|
+
|
|
386
|
+
image_bytes = base64.b64decode(content.data)
|
|
387
|
+
screenshot_parts.append(
|
|
388
|
+
genai_types.FunctionResponsePart(
|
|
389
|
+
inline_data=genai_types.FunctionResponseBlob(
|
|
390
|
+
mime_type=content.mimeType or "image/png",
|
|
391
|
+
data=image_bytes,
|
|
392
|
+
)
|
|
393
|
+
)
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
response_dict["url"] = url or "about:blank"
|
|
397
|
+
if tool_call.arguments and tool_call.arguments.get("safety_decision"):
|
|
398
|
+
response_dict["safety_acknowledgement"] = True
|
|
399
|
+
else:
|
|
318
400
|
# Add text content to response
|
|
319
401
|
for content in result.content:
|
|
320
402
|
if isinstance(content, types.TextContent):
|
|
@@ -325,6 +407,7 @@ class GeminiAgent(MCPAgent):
|
|
|
325
407
|
function_response = genai_types.FunctionResponse(
|
|
326
408
|
name=gemini_name,
|
|
327
409
|
response=response_dict,
|
|
410
|
+
parts=screenshot_parts if screenshot_parts else None,
|
|
328
411
|
)
|
|
329
412
|
function_responses.append(function_response)
|
|
330
413
|
|
|
@@ -336,6 +419,13 @@ class GeminiAgent(MCPAgent):
|
|
|
336
419
|
)
|
|
337
420
|
]
|
|
338
421
|
|
|
422
|
+
@staticmethod
|
|
423
|
+
def _extract_url(result: MCPToolResult) -> str | None:
|
|
424
|
+
for content in result.content:
|
|
425
|
+
if isinstance(content, types.TextContent) and content.text.startswith("__URL__:"):
|
|
426
|
+
return content.text.replace("__URL__:", "", 1)
|
|
427
|
+
return None
|
|
428
|
+
|
|
339
429
|
def _map_role(self, role: str) -> str:
|
|
340
430
|
"""Gemini uses 'model' instead of 'assistant' for non-user turns."""
|
|
341
431
|
if role == "assistant":
|
|
@@ -356,6 +446,7 @@ class GeminiAgent(MCPAgent):
|
|
|
356
446
|
Uses shared categorize_tools() for role-based exclusion.
|
|
357
447
|
"""
|
|
358
448
|
self._gemini_to_mcp_tool_map = {}
|
|
449
|
+
self._computer_tool_name = None
|
|
359
450
|
self.gemini_tools = []
|
|
360
451
|
|
|
361
452
|
categorized = self._categorized_tools
|
|
@@ -417,12 +508,19 @@ class GeminiAgent(MCPAgent):
|
|
|
417
508
|
Returns:
|
|
418
509
|
Gemini-specific tool or None if not supported
|
|
419
510
|
"""
|
|
420
|
-
# Currently Gemini native tools are similar to function tools
|
|
421
|
-
# This method exists for future expansion (e.g., computer_use native support)
|
|
422
511
|
match spec.api_type:
|
|
423
512
|
case "computer_use":
|
|
424
|
-
|
|
425
|
-
|
|
513
|
+
self._computer_tool_name = tool.name
|
|
514
|
+
excluded_functions = [
|
|
515
|
+
*self.excluded_predefined_functions,
|
|
516
|
+
*self._colliding_predefined_function_names(tool.name),
|
|
517
|
+
]
|
|
518
|
+
return genai_types.Tool(
|
|
519
|
+
computer_use=genai_types.ComputerUse(
|
|
520
|
+
environment=genai_types.Environment.ENVIRONMENT_BROWSER,
|
|
521
|
+
excluded_predefined_functions=excluded_functions,
|
|
522
|
+
)
|
|
523
|
+
)
|
|
426
524
|
case _:
|
|
427
525
|
# Unknown native type - try as function tool
|
|
428
526
|
logger.debug(
|
|
@@ -432,6 +530,49 @@ class GeminiAgent(MCPAgent):
|
|
|
432
530
|
)
|
|
433
531
|
return self._to_gemini_tool(tool)
|
|
434
532
|
|
|
533
|
+
def _colliding_predefined_function_names(self, computer_tool_name: str) -> list[str]:
|
|
534
|
+
"""Exclude predefined computer actions shadowed by generic MCP tools."""
|
|
535
|
+
if not self._available_tools:
|
|
536
|
+
return []
|
|
537
|
+
|
|
538
|
+
generic_names = {
|
|
539
|
+
tool.name
|
|
540
|
+
for tool in self._available_tools
|
|
541
|
+
if tool.name != computer_tool_name and not self.resolve_native_spec(tool)
|
|
542
|
+
}
|
|
543
|
+
return sorted(set(PREDEFINED_COMPUTER_USE_FUNCTIONS) & generic_names)
|
|
544
|
+
|
|
545
|
+
def _remove_old_screenshots(self, messages: list[genai_types.Content]) -> None:
|
|
546
|
+
"""Drop older Gemini Computer Use screenshots to keep context growth bounded."""
|
|
547
|
+
if self._computer_tool_name is None:
|
|
548
|
+
return
|
|
549
|
+
|
|
550
|
+
turn_with_screenshots_found = 0
|
|
551
|
+
for content in reversed(messages):
|
|
552
|
+
if content.role != "user" or not content.parts:
|
|
553
|
+
continue
|
|
554
|
+
|
|
555
|
+
has_screenshot = any(
|
|
556
|
+
part.function_response
|
|
557
|
+
and part.function_response.parts
|
|
558
|
+
and part.function_response.name in PREDEFINED_COMPUTER_USE_FUNCTIONS
|
|
559
|
+
for part in content.parts
|
|
560
|
+
)
|
|
561
|
+
if not has_screenshot:
|
|
562
|
+
continue
|
|
563
|
+
|
|
564
|
+
turn_with_screenshots_found += 1
|
|
565
|
+
if turn_with_screenshots_found <= self.max_recent_turn_with_screenshots:
|
|
566
|
+
continue
|
|
567
|
+
|
|
568
|
+
for part in content.parts:
|
|
569
|
+
if (
|
|
570
|
+
part.function_response
|
|
571
|
+
and part.function_response.parts
|
|
572
|
+
and part.function_response.name in PREDEFINED_COMPUTER_USE_FUNCTIONS
|
|
573
|
+
):
|
|
574
|
+
part.function_response.parts = None
|
|
575
|
+
|
|
435
576
|
def _to_gemini_tool(self, tool: types.Tool) -> genai_types.Tool | None:
|
|
436
577
|
"""Convert a single MCP tool to Gemini function tool format.
|
|
437
578
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Gemini Computer Use preset agent.
|
|
2
|
+
|
|
3
|
+
The native Computer Use implementation lives in GeminiAgent. This class only
|
|
4
|
+
keeps the gemini_cua agent type/default model preset.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, ClassVar
|
|
10
|
+
|
|
11
|
+
from hud.tools.computer.settings import computer_settings
|
|
12
|
+
from hud.types import AgentType, BaseAgentConfig
|
|
13
|
+
from hud.utils.types import with_signature
|
|
14
|
+
|
|
15
|
+
from .base import MCPAgent
|
|
16
|
+
from .gemini import GeminiAgent
|
|
17
|
+
from .types import GeminiCUAConfig, GeminiCUACreateParams
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GeminiCUAAgent(GeminiAgent):
|
|
21
|
+
"""
|
|
22
|
+
Gemini Computer Use Agent that extends GeminiAgent with computer use capabilities.
|
|
23
|
+
|
|
24
|
+
This agent uses Gemini's native computer use capabilities but executes
|
|
25
|
+
tools through MCP servers instead of direct implementation.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
metadata: ClassVar[dict[str, Any] | None] = {
|
|
29
|
+
"display_width": computer_settings.GEMINI_COMPUTER_WIDTH,
|
|
30
|
+
"display_height": computer_settings.GEMINI_COMPUTER_HEIGHT,
|
|
31
|
+
}
|
|
32
|
+
required_tools: ClassVar[list[str]] = ["gemini_computer"]
|
|
33
|
+
config_cls: ClassVar[type[BaseAgentConfig]] = GeminiCUAConfig
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def agent_type(cls) -> AgentType:
|
|
37
|
+
"""Return the AgentType for Gemini CUA."""
|
|
38
|
+
return AgentType.GEMINI_CUA
|
|
39
|
+
|
|
40
|
+
@with_signature(GeminiCUACreateParams)
|
|
41
|
+
@classmethod
|
|
42
|
+
def create(cls, **kwargs: Any) -> GeminiCUAAgent: # pyright: ignore[reportIncompatibleMethodOverride]
|
|
43
|
+
return MCPAgent.create.__func__(cls, **kwargs) # type: ignore[return-value]
|
|
@@ -229,6 +229,33 @@ class TestGeminiAgent:
|
|
|
229
229
|
assert response.tool_calls == []
|
|
230
230
|
assert response.done is True
|
|
231
231
|
|
|
232
|
+
@pytest.mark.asyncio
|
|
233
|
+
async def test_get_response_raises_on_no_candidates(
|
|
234
|
+
self, mock_gemini_client: MagicMock
|
|
235
|
+
) -> None:
|
|
236
|
+
"""A no-candidate Gemini response should fail loudly, not submit an empty answer."""
|
|
237
|
+
with patch("hud.settings.settings.telemetry_enabled", False):
|
|
238
|
+
agent = GeminiAgent.create(
|
|
239
|
+
model_client=mock_gemini_client,
|
|
240
|
+
model="gemini-3-flash-preview",
|
|
241
|
+
validate_api_key=False,
|
|
242
|
+
)
|
|
243
|
+
agent.gemini_tools = []
|
|
244
|
+
agent._initialized = True
|
|
245
|
+
|
|
246
|
+
mock_response = MagicMock()
|
|
247
|
+
mock_response.candidates = []
|
|
248
|
+
mock_response.prompt_feedback = "blocked"
|
|
249
|
+
mock_response.usage_metadata = None
|
|
250
|
+
mock_gemini_client.aio.models.generate_content = AsyncMock(return_value=mock_response)
|
|
251
|
+
|
|
252
|
+
messages = [
|
|
253
|
+
genai_types.Content(role="user", parts=[genai_types.Part.from_text(text="Status?")])
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
with pytest.raises(RuntimeError, match="returned no candidates"):
|
|
257
|
+
await agent.get_response(messages)
|
|
258
|
+
|
|
232
259
|
@pytest.mark.asyncio
|
|
233
260
|
async def test_get_response_with_thinking(self, mock_gemini_client: MagicMock) -> None:
|
|
234
261
|
"""Test getting response with thinking content."""
|
|
@@ -271,6 +298,42 @@ class TestGeminiAgent:
|
|
|
271
298
|
assert response.content == "Here is my answer"
|
|
272
299
|
assert response.reasoning == "Let me reason through this..."
|
|
273
300
|
|
|
301
|
+
@pytest.mark.asyncio
|
|
302
|
+
async def test_get_response_passes_thinking_config(self, mock_gemini_client: MagicMock) -> None:
|
|
303
|
+
"""Gemini 3 thinking options should be passed to GenerateContentConfig."""
|
|
304
|
+
with patch("hud.settings.settings.telemetry_enabled", False):
|
|
305
|
+
agent = GeminiAgent.create(
|
|
306
|
+
model_client=mock_gemini_client,
|
|
307
|
+
model="gemini-3-flash-preview",
|
|
308
|
+
validate_api_key=False,
|
|
309
|
+
thinking_level="high",
|
|
310
|
+
include_thoughts=True,
|
|
311
|
+
)
|
|
312
|
+
agent.gemini_tools = []
|
|
313
|
+
agent._initialized = True
|
|
314
|
+
|
|
315
|
+
mock_response = MagicMock()
|
|
316
|
+
mock_candidate = MagicMock()
|
|
317
|
+
text_part = MagicMock()
|
|
318
|
+
text_part.text = "Answer"
|
|
319
|
+
text_part.function_call = None
|
|
320
|
+
text_part.thought = False
|
|
321
|
+
mock_candidate.content = MagicMock()
|
|
322
|
+
mock_candidate.content.parts = [text_part]
|
|
323
|
+
mock_response.candidates = [mock_candidate]
|
|
324
|
+
|
|
325
|
+
mock_gemini_client.aio.models.generate_content = AsyncMock(return_value=mock_response)
|
|
326
|
+
|
|
327
|
+
messages = [
|
|
328
|
+
genai_types.Content(role="user", parts=[genai_types.Part.from_text(text="Hi")])
|
|
329
|
+
]
|
|
330
|
+
await agent.get_response(messages)
|
|
331
|
+
|
|
332
|
+
config = mock_gemini_client.aio.models.generate_content.call_args.kwargs["config"]
|
|
333
|
+
assert config.thinking_config is not None
|
|
334
|
+
assert config.thinking_config.include_thoughts is True
|
|
335
|
+
assert config.thinking_config.thinking_level.value == "HIGH"
|
|
336
|
+
|
|
274
337
|
@pytest.mark.asyncio
|
|
275
338
|
async def test_convert_tools_for_gemini(self, mock_gemini_client: MagicMock) -> None:
|
|
276
339
|
"""Test converting MCP tools to Gemini format."""
|
|
@@ -298,6 +361,167 @@ class TestGeminiAgent:
|
|
|
298
361
|
assert gemini_tool.function_declarations is not None
|
|
299
362
|
assert gemini_tool.function_declarations[0].name == "my_tool"
|
|
300
363
|
|
|
364
|
+
@pytest.mark.asyncio
|
|
365
|
+
async def test_regular_agent_uses_native_computer_use(
|
|
366
|
+
self, mock_gemini_client: MagicMock
|
|
367
|
+
) -> None:
|
|
368
|
+
"""GeminiAgent should register GeminiComputerTool as native Computer Use."""
|
|
369
|
+
computer_tool = types.Tool(
|
|
370
|
+
name="gemini_computer",
|
|
371
|
+
description="Control computer with mouse, keyboard, and screenshots",
|
|
372
|
+
inputSchema={"type": "object", "properties": {}},
|
|
373
|
+
)
|
|
374
|
+
computer_tool.meta = {
|
|
375
|
+
"native_tools": {
|
|
376
|
+
"gemini": {
|
|
377
|
+
"api_type": "computer_use",
|
|
378
|
+
"api_name": "gemini_computer",
|
|
379
|
+
"role": "computer",
|
|
380
|
+
"supported_models": ["gemini-3-flash-preview"],
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
tools = [
|
|
385
|
+
computer_tool,
|
|
386
|
+
]
|
|
387
|
+
ctx = MockEvalContext(tools=tools)
|
|
388
|
+
agent = GeminiAgent.create(
|
|
389
|
+
model_client=mock_gemini_client,
|
|
390
|
+
model="gemini-3-flash-preview",
|
|
391
|
+
validate_api_key=False,
|
|
392
|
+
excluded_predefined_functions=["drag_and_drop"],
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
agent.ctx = ctx
|
|
396
|
+
await agent._initialize_from_ctx(ctx)
|
|
397
|
+
|
|
398
|
+
assert agent._computer_tool_name == "gemini_computer"
|
|
399
|
+
assert len(agent.gemini_tools) == 1
|
|
400
|
+
computer_tool = agent.gemini_tools[0]
|
|
401
|
+
assert isinstance(computer_tool, genai_types.Tool)
|
|
402
|
+
assert computer_tool.computer_use is not None
|
|
403
|
+
assert computer_tool.computer_use.excluded_predefined_functions == ["drag_and_drop"]
|
|
404
|
+
|
|
405
|
+
@pytest.mark.asyncio
|
|
406
|
+
async def test_computer_use_excludes_colliding_generic_tool_names(
|
|
407
|
+
self, mock_gemini_client: MagicMock
|
|
408
|
+
) -> None:
|
|
409
|
+
"""Generic tools named like predefined actions should not be hijacked."""
|
|
410
|
+
computer_tool = types.Tool(
|
|
411
|
+
name="gemini_computer",
|
|
412
|
+
description="Control computer with mouse, keyboard, and screenshots",
|
|
413
|
+
inputSchema={"type": "object", "properties": {}},
|
|
414
|
+
)
|
|
415
|
+
computer_tool.meta = {
|
|
416
|
+
"native_tools": {
|
|
417
|
+
"gemini": {
|
|
418
|
+
"api_type": "computer_use",
|
|
419
|
+
"api_name": "gemini_computer",
|
|
420
|
+
"role": "computer",
|
|
421
|
+
"supported_models": ["gemini-3-flash-preview"],
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
navigate_tool = types.Tool(
|
|
426
|
+
name="navigate",
|
|
427
|
+
description="A non-computer navigation helper",
|
|
428
|
+
inputSchema={"type": "object", "properties": {"url": {"type": "string"}}},
|
|
429
|
+
)
|
|
430
|
+
ctx = MockEvalContext(tools=[computer_tool, navigate_tool])
|
|
431
|
+
agent = GeminiAgent.create(
|
|
432
|
+
model_client=mock_gemini_client,
|
|
433
|
+
model="gemini-3-flash-preview",
|
|
434
|
+
validate_api_key=False,
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
agent.ctx = ctx
|
|
438
|
+
await agent._initialize_from_ctx(ctx)
|
|
439
|
+
|
|
440
|
+
computer_use_tool = next(
|
|
441
|
+
tool for tool in agent.gemini_tools if getattr(tool, "computer_use", None) is not None
|
|
442
|
+
)
|
|
443
|
+
computer_use = getattr(computer_use_tool, "computer_use", None)
|
|
444
|
+
assert computer_use is not None
|
|
445
|
+
assert "navigate" in (computer_use.excluded_predefined_functions or [])
|
|
446
|
+
function_call = MagicMock()
|
|
447
|
+
function_call.name = "navigate"
|
|
448
|
+
function_call.args = {"url": "https://example.com"}
|
|
449
|
+
tool_call = agent._extract_tool_call(MagicMock(function_call=function_call))
|
|
450
|
+
assert tool_call is not None
|
|
451
|
+
assert tool_call.name == "navigate"
|
|
452
|
+
assert tool_call.arguments == {"url": "https://example.com"}
|
|
453
|
+
|
|
454
|
+
def test_regular_agent_routes_computer_use_function_call(
|
|
455
|
+
self, mock_gemini_client: MagicMock
|
|
456
|
+
) -> None:
|
|
457
|
+
"""Gemini Computer Use calls should route to the MCP computer tool."""
|
|
458
|
+
agent = GeminiAgent.create(
|
|
459
|
+
model_client=mock_gemini_client,
|
|
460
|
+
validate_api_key=False,
|
|
461
|
+
)
|
|
462
|
+
agent._computer_tool_name = "gemini_computer"
|
|
463
|
+
|
|
464
|
+
function_call = MagicMock()
|
|
465
|
+
function_call.name = "click_at"
|
|
466
|
+
function_call.args = {"x": 500, "y": 250, "safety_decision": {"decision": "allowed"}}
|
|
467
|
+
part = MagicMock(function_call=function_call)
|
|
468
|
+
|
|
469
|
+
tool_call = agent._extract_tool_call(part)
|
|
470
|
+
|
|
471
|
+
assert tool_call is not None
|
|
472
|
+
assert tool_call.name == "gemini_computer"
|
|
473
|
+
assert tool_call.arguments == {
|
|
474
|
+
"action": "click_at",
|
|
475
|
+
"safety_decision": {"decision": "allowed"},
|
|
476
|
+
"x": 500,
|
|
477
|
+
"y": 250,
|
|
478
|
+
}
|
|
479
|
+
assert getattr(tool_call, "gemini_name") == "click_at"
|
|
480
|
+
|
|
481
|
+
@pytest.mark.asyncio
|
|
482
|
+
async def test_regular_agent_formats_computer_use_results(
|
|
483
|
+
self, mock_gemini_client: MagicMock
|
|
484
|
+
) -> None:
|
|
485
|
+
"""GeminiAgent should return URL and screenshot parts for native computer use."""
|
|
486
|
+
agent = GeminiAgent.create(
|
|
487
|
+
model_client=mock_gemini_client,
|
|
488
|
+
validate_api_key=False,
|
|
489
|
+
)
|
|
490
|
+
agent._computer_tool_name = "gemini_computer"
|
|
491
|
+
screenshot = base64.b64encode(b"png bytes").decode()
|
|
492
|
+
tool_calls = [
|
|
493
|
+
MCPToolCall(
|
|
494
|
+
name="gemini_computer",
|
|
495
|
+
arguments={"action": "click_at", "safety_decision": {"decision": "allowed"}},
|
|
496
|
+
gemini_name="click_at", # type: ignore[arg-type]
|
|
497
|
+
)
|
|
498
|
+
]
|
|
499
|
+
tool_results = [
|
|
500
|
+
MCPToolResult(
|
|
501
|
+
content=[
|
|
502
|
+
types.TextContent(type="text", text="__URL__:https://example.com"),
|
|
503
|
+
types.ImageContent(type="image", data=screenshot, mimeType="image/png"),
|
|
504
|
+
],
|
|
505
|
+
isError=False,
|
|
506
|
+
)
|
|
507
|
+
]
|
|
508
|
+
|
|
509
|
+
messages = await agent.format_tool_results(tool_calls, tool_results)
|
|
510
|
+
|
|
511
|
+
parts = messages[0].parts
|
|
512
|
+
assert parts is not None
|
|
513
|
+
function_response = parts[0].function_response
|
|
514
|
+
assert function_response is not None
|
|
515
|
+
assert function_response.name == "click_at"
|
|
516
|
+
response = function_response.response
|
|
517
|
+
assert response is not None
|
|
518
|
+
assert response["url"] == "https://example.com"
|
|
519
|
+
assert response["safety_acknowledgement"] is True
|
|
520
|
+
assert function_response.parts is not None
|
|
521
|
+
inline_data = function_response.parts[0].inline_data
|
|
522
|
+
assert inline_data is not None
|
|
523
|
+
assert inline_data.mime_type == "image/png"
|
|
524
|
+
|
|
301
525
|
|
|
302
526
|
class TestGeminiToolConversion:
|
|
303
527
|
"""Tests for tool conversion to Gemini format."""
|
|
@@ -64,6 +64,9 @@ class GeminiConfig(BaseAgentConfig):
|
|
|
64
64
|
top_k: int = 40
|
|
65
65
|
max_output_tokens: int = 8192
|
|
66
66
|
validate_api_key: bool = True
|
|
67
|
+
excluded_predefined_functions: list[str] = Field(default_factory=list)
|
|
68
|
+
thinking_level: Literal["minimal", "low", "medium", "high"] | None = None
|
|
69
|
+
include_thoughts: bool = True
|
|
67
70
|
|
|
68
71
|
|
|
69
72
|
class GeminiCreateParams(BaseCreateParams, GeminiConfig):
|
|
@@ -79,7 +82,6 @@ class GeminiCUAConfig(GeminiConfig):
|
|
|
79
82
|
model: str = Field(
|
|
80
83
|
default="gemini-2.5-computer-use-preview-10-2025", validation_alias=_model_alias
|
|
81
84
|
)
|
|
82
|
-
excluded_predefined_functions: list[str] = Field(default_factory=list)
|
|
83
85
|
|
|
84
86
|
|
|
85
87
|
class GeminiCUACreateParams(BaseCreateParams, GeminiCUAConfig):
|
|
@@ -61,7 +61,7 @@ class TestIncrementVersion:
|
|
|
61
61
|
def test_increment_minor(self):
|
|
62
62
|
"""Test incrementing minor version."""
|
|
63
63
|
assert increment_version("1.2.3", "minor") == "1.3.0"
|
|
64
|
-
assert increment_version("0.5.
|
|
64
|
+
assert increment_version("0.5.40", "minor") == "0.6.0"
|
|
65
65
|
|
|
66
66
|
def test_increment_major(self):
|
|
67
67
|
"""Test incrementing major version."""
|
|
@@ -131,10 +131,14 @@ class TestCLICommands:
|
|
|
131
131
|
|
|
132
132
|
def test_version_import_error(self) -> None:
|
|
133
133
|
"""Test version command when version unavailable."""
|
|
134
|
+
import re
|
|
135
|
+
|
|
136
|
+
ansi_escape = re.compile(r"\x1b\[[0-9;]*m")
|
|
134
137
|
with patch.dict("sys.modules", {"hud": None}):
|
|
135
138
|
result = runner.invoke(app, ["version"])
|
|
136
139
|
assert result.exit_code == 0
|
|
137
|
-
|
|
140
|
+
clean_output = ansi_escape.sub("", result.output)
|
|
141
|
+
assert "HUD CLI version: unknown" in clean_output
|
|
138
142
|
|
|
139
143
|
def test_mcp_command(self) -> None:
|
|
140
144
|
"""Test mcp server command."""
|
|
@@ -86,7 +86,8 @@ class TestGeminiEditTool:
|
|
|
86
86
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
87
87
|
tool = GeminiEditTool(base_directory=tmpdir)
|
|
88
88
|
result = tool._resolve_path("test.txt")
|
|
89
|
-
|
|
89
|
+
expected = Path(tmpdir).resolve() / "test.txt"
|
|
90
|
+
assert result == expected
|
|
90
91
|
|
|
91
92
|
def test_resolve_path_absolute(self) -> None:
|
|
92
93
|
"""Test path resolution with absolute path."""
|
|
@@ -213,7 +214,7 @@ class TestGeminiEditTool:
|
|
|
213
214
|
async def test_file_history_saved(self) -> None:
|
|
214
215
|
"""Test that file history is saved for potential undo."""
|
|
215
216
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
216
|
-
test_file = Path(tmpdir) / "test.txt"
|
|
217
|
+
test_file = Path(tmpdir).resolve() / "test.txt"
|
|
217
218
|
test_file.write_text("original content")
|
|
218
219
|
|
|
219
220
|
tool = GeminiEditTool(base_directory=tmpdir)
|