hud-python 0.5.39__tar.gz → 0.5.41__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. {hud_python-0.5.39 → hud_python-0.5.41}/PKG-INFO +1 -1
  2. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/gemini.py +149 -8
  3. hud_python-0.5.41/hud/agents/gemini_cua.py +43 -0
  4. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/tests/test_gemini.py +224 -0
  5. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/types.py +3 -1
  6. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/init.py +114 -41
  7. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_build.py +1 -1
  8. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_cli_init.py +5 -1
  9. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/environment.py +74 -33
  10. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/tests/test_environment.py +251 -0
  11. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/tests/test_gemini_tools.py +3 -2
  12. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/computer/gemini.py +115 -94
  13. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/computer/glm.py +3 -15
  14. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/computer/hud.py +69 -1
  15. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/computer/tests/test_computer.py +125 -3
  16. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/computer/tests/test_glm_computer.py +20 -13
  17. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/executors/base.py +29 -0
  18. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/executors/pyautogui.py +13 -20
  19. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/executors/tests/test_pyautogui_executor.py +10 -3
  20. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/executors/xdo.py +8 -4
  21. {hud_python-0.5.39 → hud_python-0.5.41}/hud/utils/tests/test_version.py +1 -1
  22. {hud_python-0.5.39 → hud_python-0.5.41}/hud/version.py +1 -1
  23. {hud_python-0.5.39 → hud_python-0.5.41}/pyproject.toml +1 -1
  24. hud_python-0.5.39/hud/agents/gemini_cua.py +0 -366
  25. {hud_python-0.5.39 → hud_python-0.5.41}/.gitignore +0 -0
  26. {hud_python-0.5.39 → hud_python-0.5.41}/LICENSE +0 -0
  27. {hud_python-0.5.39 → hud_python-0.5.41}/README.md +0 -0
  28. {hud_python-0.5.39 → hud_python-0.5.41}/examples/README.md +0 -0
  29. {hud_python-0.5.39 → hud_python-0.5.41}/hud/__init__.py +0 -0
  30. {hud_python-0.5.39 → hud_python-0.5.41}/hud/__main__.py +0 -0
  31. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/__init__.py +0 -0
  32. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/base.py +0 -0
  33. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/claude.py +0 -0
  34. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/gateway.py +0 -0
  35. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/grounded_openai.py +0 -0
  36. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/misc/__init__.py +0 -0
  37. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/misc/integration_test_agent.py +0 -0
  38. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/misc/response_agent.py +0 -0
  39. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/openai.py +0 -0
  40. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/openai_chat.py +0 -0
  41. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/operator.py +0 -0
  42. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/resolver.py +0 -0
  43. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/tests/__init__.py +0 -0
  44. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/tests/conftest.py +0 -0
  45. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/tests/test_base.py +0 -0
  46. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/tests/test_base_runtime.py +0 -0
  47. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/tests/test_claude.py +0 -0
  48. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/tests/test_grounded_openai_agent.py +0 -0
  49. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/tests/test_integration_test_agent.py +0 -0
  50. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/tests/test_openai.py +0 -0
  51. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/tests/test_operator.py +0 -0
  52. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/tests/test_resolver.py +0 -0
  53. {hud_python-0.5.39 → hud_python-0.5.41}/hud/agents/tests/test_run_eval.py +0 -0
  54. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/__init__.py +0 -0
  55. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/__main__.py +0 -0
  56. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/analyze.py +0 -0
  57. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/build.py +0 -0
  58. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/cancel.py +0 -0
  59. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/convert/__init__.py +0 -0
  60. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/convert/base.py +0 -0
  61. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/convert/harbor.py +0 -0
  62. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/convert/tests/__init__.py +0 -0
  63. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/convert/tests/conftest.py +0 -0
  64. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/convert/tests/test_harbor.py +0 -0
  65. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/debug.py +0 -0
  66. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/deploy.py +0 -0
  67. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/dev.py +0 -0
  68. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/eval.py +0 -0
  69. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/flows/__init__.py +0 -0
  70. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/flows/dev.py +0 -0
  71. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/flows/init.py +0 -0
  72. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/flows/tasks.py +0 -0
  73. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/flows/templates.py +0 -0
  74. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/flows/tests/__init__.py +0 -0
  75. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/flows/tests/test_dev.py +0 -0
  76. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/link.py +0 -0
  77. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/login.py +0 -0
  78. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/models.py +0 -0
  79. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/push.py +0 -0
  80. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/rl.py +0 -0
  81. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/scenario.py +0 -0
  82. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/sync.py +0 -0
  83. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/__init__.py +0 -0
  84. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_analysis_utils.py +0 -0
  85. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_analyze.py +0 -0
  86. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_analyze_metadata.py +0 -0
  87. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_analyze_module.py +0 -0
  88. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_build_failure.py +0 -0
  89. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_build_module.py +0 -0
  90. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_cli_main.py +0 -0
  91. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_cli_more_wrappers.py +0 -0
  92. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_cli_root.py +0 -0
  93. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_convert.py +0 -0
  94. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_debug.py +0 -0
  95. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_debug_directory_mode.py +0 -0
  96. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_deploy.py +0 -0
  97. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_dev.py +0 -0
  98. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_eval.py +0 -0
  99. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_eval_bedrock.py +0 -0
  100. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_init.py +0 -0
  101. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_lockfile_utils.py +0 -0
  102. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_main_module.py +0 -0
  103. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_mcp_server.py +0 -0
  104. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_push.py +0 -0
  105. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_push_happy.py +0 -0
  106. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_push_wrapper.py +0 -0
  107. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_rl.py +0 -0
  108. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_scenario.py +0 -0
  109. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_sync.py +0 -0
  110. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/tests/test_utils.py +0 -0
  111. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/__init__.py +0 -0
  112. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/analysis.py +0 -0
  113. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/api.py +0 -0
  114. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/args.py +0 -0
  115. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/build_display.py +0 -0
  116. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/build_logs.py +0 -0
  117. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/collect.py +0 -0
  118. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/config.py +0 -0
  119. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/context.py +0 -0
  120. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/docker.py +0 -0
  121. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/env_check.py +0 -0
  122. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/environment.py +0 -0
  123. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/git.py +0 -0
  124. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/interactive.py +0 -0
  125. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/lockfile.py +0 -0
  126. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/logging.py +0 -0
  127. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/metadata.py +0 -0
  128. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/name_check.py +0 -0
  129. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/project_config.py +0 -0
  130. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/server.py +0 -0
  131. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/source_hash.py +0 -0
  132. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/tasks.py +0 -0
  133. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/taskset.py +0 -0
  134. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/tests/__init__.py +0 -0
  135. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/tests/test_collect.py +0 -0
  136. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/tests/test_config.py +0 -0
  137. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/tests/test_docker.py +0 -0
  138. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/tests/test_docker_hints.py +0 -0
  139. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/tests/test_env_check.py +0 -0
  140. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/tests/test_environment.py +0 -0
  141. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/tests/test_git.py +0 -0
  142. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/tests/test_interactive_module.py +0 -0
  143. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/tests/test_logging_utils.py +0 -0
  144. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/tests/test_metadata.py +0 -0
  145. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/tests/test_source_hash.py +0 -0
  146. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/tests/test_tasks.py +0 -0
  147. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/validation.py +0 -0
  148. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/version_check.py +0 -0
  149. {hud_python-0.5.39 → hud_python-0.5.41}/hud/cli/utils/viewer.py +0 -0
  150. {hud_python-0.5.39 → hud_python-0.5.41}/hud/datasets/__init__.py +0 -0
  151. {hud_python-0.5.39 → hud_python-0.5.41}/hud/datasets/loader.py +0 -0
  152. {hud_python-0.5.39 → hud_python-0.5.41}/hud/datasets/runner.py +0 -0
  153. {hud_python-0.5.39 → hud_python-0.5.41}/hud/datasets/tests/__init__.py +0 -0
  154. {hud_python-0.5.39 → hud_python-0.5.41}/hud/datasets/tests/test_loader.py +0 -0
  155. {hud_python-0.5.39 → hud_python-0.5.41}/hud/datasets/tests/test_utils.py +0 -0
  156. {hud_python-0.5.39 → hud_python-0.5.41}/hud/datasets/utils.py +0 -0
  157. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/__init__.py +0 -0
  158. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/connection.py +0 -0
  159. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/connectors/__init__.py +0 -0
  160. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/connectors/base.py +0 -0
  161. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/connectors/local.py +0 -0
  162. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/connectors/mcp_config.py +0 -0
  163. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/connectors/openai.py +0 -0
  164. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/connectors/remote.py +0 -0
  165. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/integrations/__init__.py +0 -0
  166. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/integrations/adk.py +0 -0
  167. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/integrations/anthropic.py +0 -0
  168. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/integrations/gemini.py +0 -0
  169. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/integrations/langchain.py +0 -0
  170. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/integrations/llamaindex.py +0 -0
  171. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/integrations/openai.py +0 -0
  172. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/mock.py +0 -0
  173. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/router.py +0 -0
  174. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/scenarios.py +0 -0
  175. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/tests/__init__.py +0 -0
  176. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/tests/test_connection.py +0 -0
  177. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/tests/test_connectors.py +0 -0
  178. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/tests/test_integrations.py +0 -0
  179. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/tests/test_local_connectors.py +0 -0
  180. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/tests/test_scenarios.py +0 -0
  181. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/tests/test_session_id.py +0 -0
  182. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/tests/test_tools.py +0 -0
  183. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/types.py +0 -0
  184. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/utils/__init__.py +0 -0
  185. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/utils/formats.py +0 -0
  186. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/utils/schema.py +0 -0
  187. {hud_python-0.5.39 → hud_python-0.5.41}/hud/environment/utils/tool_wrappers.py +0 -0
  188. {hud_python-0.5.39 → hud_python-0.5.41}/hud/eval/__init__.py +0 -0
  189. {hud_python-0.5.39 → hud_python-0.5.41}/hud/eval/context.py +0 -0
  190. {hud_python-0.5.39 → hud_python-0.5.41}/hud/eval/display.py +0 -0
  191. {hud_python-0.5.39 → hud_python-0.5.41}/hud/eval/instrument.py +0 -0
  192. {hud_python-0.5.39 → hud_python-0.5.41}/hud/eval/manager.py +0 -0
  193. {hud_python-0.5.39 → hud_python-0.5.41}/hud/eval/parallel.py +0 -0
  194. {hud_python-0.5.39 → hud_python-0.5.41}/hud/eval/task.py +0 -0
  195. {hud_python-0.5.39 → hud_python-0.5.41}/hud/eval/tests/__init__.py +0 -0
  196. {hud_python-0.5.39 → hud_python-0.5.41}/hud/eval/tests/test_context.py +0 -0
  197. {hud_python-0.5.39 → hud_python-0.5.41}/hud/eval/tests/test_eval.py +0 -0
  198. {hud_python-0.5.39 → hud_python-0.5.41}/hud/eval/tests/test_manager.py +0 -0
  199. {hud_python-0.5.39 → hud_python-0.5.41}/hud/eval/tests/test_parallel.py +0 -0
  200. {hud_python-0.5.39 → hud_python-0.5.41}/hud/eval/tests/test_task.py +0 -0
  201. {hud_python-0.5.39 → hud_python-0.5.41}/hud/eval/types.py +0 -0
  202. {hud_python-0.5.39 → hud_python-0.5.41}/hud/eval/utils.py +0 -0
  203. {hud_python-0.5.39 → hud_python-0.5.41}/hud/native/__init__.py +0 -0
  204. {hud_python-0.5.39 → hud_python-0.5.41}/hud/native/chat.py +0 -0
  205. {hud_python-0.5.39 → hud_python-0.5.41}/hud/native/graders.py +0 -0
  206. {hud_python-0.5.39 → hud_python-0.5.41}/hud/native/permissions.py +0 -0
  207. {hud_python-0.5.39 → hud_python-0.5.41}/hud/native/skills.py +0 -0
  208. {hud_python-0.5.39 → hud_python-0.5.41}/hud/native/tests/__init__.py +0 -0
  209. {hud_python-0.5.39 → hud_python-0.5.41}/hud/native/tests/test_graders.py +0 -0
  210. {hud_python-0.5.39 → hud_python-0.5.41}/hud/patches/__init__.py +0 -0
  211. {hud_python-0.5.39 → hud_python-0.5.41}/hud/patches/mcp_patches.py +0 -0
  212. {hud_python-0.5.39 → hud_python-0.5.41}/hud/patches/warnings.py +0 -0
  213. {hud_python-0.5.39 → hud_python-0.5.41}/hud/py.typed +0 -0
  214. {hud_python-0.5.39 → hud_python-0.5.41}/hud/server/__init__.py +0 -0
  215. {hud_python-0.5.39 → hud_python-0.5.41}/hud/server/context.py +0 -0
  216. {hud_python-0.5.39 → hud_python-0.5.41}/hud/server/helper/__init__.py +0 -0
  217. {hud_python-0.5.39 → hud_python-0.5.41}/hud/server/low_level.py +0 -0
  218. {hud_python-0.5.39 → hud_python-0.5.41}/hud/server/router.py +0 -0
  219. {hud_python-0.5.39 → hud_python-0.5.41}/hud/server/server.py +0 -0
  220. {hud_python-0.5.39 → hud_python-0.5.41}/hud/server/tests/__init__.py +0 -0
  221. {hud_python-0.5.39 → hud_python-0.5.41}/hud/server/tests/test_add_tool.py +0 -0
  222. {hud_python-0.5.39 → hud_python-0.5.41}/hud/server/tests/test_context.py +0 -0
  223. {hud_python-0.5.39 → hud_python-0.5.41}/hud/server/tests/test_mcp_server_handlers.py +0 -0
  224. {hud_python-0.5.39 → hud_python-0.5.41}/hud/server/tests/test_mcp_server_integration.py +0 -0
  225. {hud_python-0.5.39 → hud_python-0.5.41}/hud/server/tests/test_mcp_server_more.py +0 -0
  226. {hud_python-0.5.39 → hud_python-0.5.41}/hud/server/tests/test_prefix_naming.py +0 -0
  227. {hud_python-0.5.39 → hud_python-0.5.41}/hud/server/tests/test_run_wrapper.py +0 -0
  228. {hud_python-0.5.39 → hud_python-0.5.41}/hud/server/tests/test_server_extra.py +0 -0
  229. {hud_python-0.5.39 → hud_python-0.5.41}/hud/server/tests/test_sigterm_runner.py +0 -0
  230. {hud_python-0.5.39 → hud_python-0.5.41}/hud/services/__init__.py +0 -0
  231. {hud_python-0.5.39 → hud_python-0.5.41}/hud/services/chat.py +0 -0
  232. {hud_python-0.5.39 → hud_python-0.5.41}/hud/services/chat_service.py +0 -0
  233. {hud_python-0.5.39 → hud_python-0.5.41}/hud/services/reply_metadata.py +0 -0
  234. {hud_python-0.5.39 → hud_python-0.5.41}/hud/services/tests/__init__.py +0 -0
  235. {hud_python-0.5.39 → hud_python-0.5.41}/hud/services/tests/test_chat.py +0 -0
  236. {hud_python-0.5.39 → hud_python-0.5.41}/hud/services/tests/test_chat_service.py +0 -0
  237. {hud_python-0.5.39 → hud_python-0.5.41}/hud/settings.py +0 -0
  238. {hud_python-0.5.39 → hud_python-0.5.41}/hud/shared/__init__.py +0 -0
  239. {hud_python-0.5.39 → hud_python-0.5.41}/hud/shared/exceptions.py +0 -0
  240. {hud_python-0.5.39 → hud_python-0.5.41}/hud/shared/hints.py +0 -0
  241. {hud_python-0.5.39 → hud_python-0.5.41}/hud/shared/requests.py +0 -0
  242. {hud_python-0.5.39 → hud_python-0.5.41}/hud/shared/tests/__init__.py +0 -0
  243. {hud_python-0.5.39 → hud_python-0.5.41}/hud/shared/tests/test_exceptions.py +0 -0
  244. {hud_python-0.5.39 → hud_python-0.5.41}/hud/shared/tests/test_hints.py +0 -0
  245. {hud_python-0.5.39 → hud_python-0.5.41}/hud/shared/tests/test_requests.py +0 -0
  246. {hud_python-0.5.39 → hud_python-0.5.41}/hud/telemetry/__init__.py +0 -0
  247. {hud_python-0.5.39 → hud_python-0.5.41}/hud/telemetry/exporter.py +0 -0
  248. {hud_python-0.5.39 → hud_python-0.5.41}/hud/telemetry/instrument.py +0 -0
  249. {hud_python-0.5.39 → hud_python-0.5.41}/hud/telemetry/tests/__init__.py +0 -0
  250. {hud_python-0.5.39 → hud_python-0.5.41}/hud/telemetry/tests/test_eval_telemetry.py +0 -0
  251. {hud_python-0.5.39 → hud_python-0.5.41}/hud/telemetry/tests/test_exporter.py +0 -0
  252. {hud_python-0.5.39 → hud_python-0.5.41}/hud/telemetry/tests/test_instrument.py +0 -0
  253. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/__init__.py +0 -0
  254. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/agent.py +0 -0
  255. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/base.py +0 -0
  256. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/__init__.py +0 -0
  257. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/apply_patch.py +0 -0
  258. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/bash.py +0 -0
  259. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/edit.py +0 -0
  260. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/gemini_edit.py +0 -0
  261. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/gemini_shell.py +0 -0
  262. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/gemini_write.py +0 -0
  263. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/session.py +0 -0
  264. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/shell.py +0 -0
  265. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/tests/__init__.py +0 -0
  266. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/tests/test_apply_patch.py +0 -0
  267. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/tests/test_bash.py +0 -0
  268. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/tests/test_bash_extended.py +0 -0
  269. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/tests/test_bash_integration.py +0 -0
  270. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/tests/test_edit.py +0 -0
  271. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/tests/test_shell.py +0 -0
  272. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/coding/utils.py +0 -0
  273. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/computer/__init__.py +0 -0
  274. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/computer/anthropic.py +0 -0
  275. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/computer/openai.py +0 -0
  276. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/computer/qwen.py +0 -0
  277. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/computer/settings.py +0 -0
  278. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/computer/tests/__init__.py +0 -0
  279. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/computer/tests/test_compression.py +0 -0
  280. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/computer/tests/test_computer_actions.py +0 -0
  281. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/elicitation.py +0 -0
  282. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/executors/__init__.py +0 -0
  283. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/executors/tests/__init__.py +0 -0
  284. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/executors/tests/test_base_executor.py +0 -0
  285. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/filesystem/__init__.py +0 -0
  286. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/filesystem/base.py +0 -0
  287. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/filesystem/gemini.py +0 -0
  288. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/filesystem/gemini_read_many.py +0 -0
  289. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/filesystem/glob.py +0 -0
  290. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/filesystem/grep.py +0 -0
  291. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/filesystem/list.py +0 -0
  292. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/filesystem/read.py +0 -0
  293. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/filesystem/tests/__init__.py +0 -0
  294. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/filesystem/tests/test_glob.py +0 -0
  295. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/filesystem/tests/test_grep.py +0 -0
  296. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/filesystem/tests/test_list.py +0 -0
  297. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/filesystem/tests/test_read.py +0 -0
  298. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/filesystem/tests/test_read_many.py +0 -0
  299. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/grounding/__init__.py +0 -0
  300. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/grounding/config.py +0 -0
  301. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/grounding/grounded_tool.py +0 -0
  302. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/grounding/grounder.py +0 -0
  303. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/grounding/tests/__init__.py +0 -0
  304. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/grounding/tests/test_grounded_tool.py +0 -0
  305. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/hosted/__init__.py +0 -0
  306. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/hosted/base.py +0 -0
  307. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/hosted/code_execution.py +0 -0
  308. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/hosted/google_search.py +0 -0
  309. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/hosted/tool_search.py +0 -0
  310. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/hosted/url_context.py +0 -0
  311. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/hosted/web_fetch.py +0 -0
  312. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/hosted/web_search.py +0 -0
  313. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/jupyter.py +0 -0
  314. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/memory/__init__.py +0 -0
  315. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/memory/base.py +0 -0
  316. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/memory/claude.py +0 -0
  317. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/memory/gemini.py +0 -0
  318. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/memory/session.py +0 -0
  319. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/memory/tests/__init__.py +0 -0
  320. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/memory/tests/test_claude.py +0 -0
  321. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/memory/tests/test_gemini.py +0 -0
  322. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/memory/tests/test_session.py +0 -0
  323. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/native_types.py +0 -0
  324. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/playwright.py +0 -0
  325. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/response.py +0 -0
  326. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/submit.py +0 -0
  327. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/tests/__init__.py +0 -0
  328. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/tests/test_agent_tool.py +0 -0
  329. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/tests/test_base.py +0 -0
  330. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/tests/test_elicitation.py +0 -0
  331. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/tests/test_init.py +0 -0
  332. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/tests/test_jupyter_tool.py +0 -0
  333. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/tests/test_native_tool_e2e.py +0 -0
  334. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/tests/test_native_types.py +0 -0
  335. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/tests/test_playwright_tool.py +0 -0
  336. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/tests/test_response.py +0 -0
  337. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/tests/test_submit.py +0 -0
  338. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/tests/test_tools.py +0 -0
  339. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/tests/test_tools_init.py +0 -0
  340. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/tests/test_types.py +0 -0
  341. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/tests/test_utils.py +0 -0
  342. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/types.py +0 -0
  343. {hud_python-0.5.39 → hud_python-0.5.41}/hud/tools/utils.py +0 -0
  344. {hud_python-0.5.39 → hud_python-0.5.41}/hud/types.py +0 -0
  345. {hud_python-0.5.39 → hud_python-0.5.41}/hud/utils/__init__.py +0 -0
  346. {hud_python-0.5.39 → hud_python-0.5.41}/hud/utils/env.py +0 -0
  347. {hud_python-0.5.39 → hud_python-0.5.41}/hud/utils/hud_console.py +0 -0
  348. {hud_python-0.5.39 → hud_python-0.5.41}/hud/utils/mcp.py +0 -0
  349. {hud_python-0.5.39 → hud_python-0.5.41}/hud/utils/pretty_errors.py +0 -0
  350. {hud_python-0.5.39 → hud_python-0.5.41}/hud/utils/serialization.py +0 -0
  351. {hud_python-0.5.39 → hud_python-0.5.41}/hud/utils/strict_schema.py +0 -0
  352. {hud_python-0.5.39 → hud_python-0.5.41}/hud/utils/tests/__init__.py +0 -0
  353. {hud_python-0.5.39 → hud_python-0.5.41}/hud/utils/tests/test_init.py +0 -0
  354. {hud_python-0.5.39 → hud_python-0.5.41}/hud/utils/tests/test_pretty_errors.py +0 -0
  355. {hud_python-0.5.39 → hud_python-0.5.41}/hud/utils/tests/test_serialization.py +0 -0
  356. {hud_python-0.5.39 → hud_python-0.5.41}/hud/utils/tests/test_tool_shorthand.py +0 -0
  357. {hud_python-0.5.39 → hud_python-0.5.41}/hud/utils/tool_shorthand.py +0 -0
  358. {hud_python-0.5.39 → hud_python-0.5.41}/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.39
3
+ Version: 0.5.41
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
@@ -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
- self.hud_console.warning("Response has no candidates")
185
- return result
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=mcp_tool_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
- # Gemini computer use is still a function tool
425
- return self._to_gemini_tool(tool)
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):