hud-python 0.5.5__tar.gz → 0.5.7__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.5 → hud_python-0.5.7}/PKG-INFO +1 -1
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/claude.py +45 -9
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/gemini_cua.py +3 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/operator.py +2 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/tests/test_claude.py +2 -4
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/dev.py +11 -2
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/__init__.py +5 -3
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/connection.py +32 -4
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/environment.py +134 -69
- hud_python-0.5.7/hud/environment/router.py +263 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/scenarios.py +212 -153
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/tests/test_environment.py +16 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/tests/test_scenarios.py +351 -14
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/eval/context.py +1 -3
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/utils/tests/test_version.py +1 -1
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/version.py +1 -1
- {hud_python-0.5.5 → hud_python-0.5.7}/pyproject.toml +1 -1
- hud_python-0.5.5/hud/environment/router.py +0 -112
- {hud_python-0.5.5 → hud_python-0.5.7}/.gitignore +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/LICENSE +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/README.md +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/examples/README.md +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/__main__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/base.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/gateway.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/gemini.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/grounded_openai.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/misc/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/misc/integration_test_agent.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/misc/response_agent.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/openai.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/openai_chat.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/resolver.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/tests/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/tests/conftest.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/tests/test_base.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/tests/test_base_runtime.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/tests/test_client.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/tests/test_gemini.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/tests/test_grounded_openai_agent.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/tests/test_openai.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/tests/test_operator.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/tests/test_resolver.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/agents/tests/test_run_eval.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/__main__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/analyze.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/build.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/clone.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/debug.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/eval.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/flows/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/flows/dev.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/flows/init.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/flows/tasks.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/flows/templates.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/flows/tests/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/flows/tests/test_dev.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/get.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/init.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/list_func.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/pull.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/push.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/remove.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/rft.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/rft_status.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_analyze.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_analyze_metadata.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_analyze_module.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_build.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_build_failure.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_build_module.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_cli_init.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_cli_main.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_cli_more_wrappers.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_cli_root.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_clone.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_convert.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_cursor.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_debug.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_dev.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_eval.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_eval_bedrock.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_init.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_list_func.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_main_module.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_mcp_server.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_pull.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_push.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_push_happy.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_push_wrapper.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_registry.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/tests/test_utils.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/celebrate.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/config.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/cursor.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/docker.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/env_check.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/environment.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/git.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/interactive.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/local_runner.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/logging.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/metadata.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/package_runner.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/registry.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/remote_runner.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/runner.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/server.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/source_hash.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tasks.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/test_config.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/test_docker.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/test_docker_hints.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/test_env_check.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/test_environment.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/test_git.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/test_interactive_module.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/test_local_runner.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/test_logging_utils.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/test_metadata.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/test_package_runner.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/test_registry_utils.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/test_remote_runner.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/test_runner_modules.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/test_source_hash.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/tests/test_tasks.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/version_check.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/cli/utils/viewer.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/clients/README.md +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/clients/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/clients/base.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/clients/environment.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/clients/fastmcp.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/clients/mcp_use.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/clients/tests/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/clients/tests/test_analyze_scenarios.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/clients/tests/test_client_integration.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/clients/tests/test_fastmcp.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/clients/tests/test_mcp_use_retry.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/clients/tests/test_protocol.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/clients/utils/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/clients/utils/mcp_use_retry.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/clients/utils/retry.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/clients/utils/retry_transport.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/datasets/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/datasets/loader.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/datasets/runner.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/datasets/tests/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/datasets/tests/test_loader.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/datasets/tests/test_utils.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/datasets/utils.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/connectors/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/connectors/base.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/connectors/local.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/connectors/mcp_config.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/connectors/openai.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/connectors/remote.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/integrations/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/integrations/adk.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/integrations/anthropic.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/integrations/gemini.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/integrations/langchain.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/integrations/llamaindex.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/integrations/openai.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/mock.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/tests/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/tests/test_connection.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/tests/test_connectors.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/tests/test_integrations.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/tests/test_local_connectors.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/tests/test_tools.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/types.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/utils/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/utils/formats.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/utils/schema.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/environment/utils/tool_wrappers.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/eval/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/eval/display.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/eval/instrument.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/eval/manager.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/eval/parallel.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/eval/task.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/eval/tests/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/eval/tests/test_context.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/eval/tests/test_eval.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/eval/tests/test_manager.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/eval/tests/test_parallel.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/eval/tests/test_task.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/eval/types.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/eval/utils.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/native/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/native/comparator.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/native/tests/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/native/tests/test_comparator.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/native/tests/test_native_init.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/patches/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/patches/mcp_patches.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/patches/warnings.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/py.typed +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/samples/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/samples/browser.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/server/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/server/context.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/server/helper/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/server/low_level.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/server/router.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/server/server.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/server/tests/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/server/tests/test_add_tool.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/server/tests/test_context.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/server/tests/test_mcp_server_handlers.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/server/tests/test_mcp_server_integration.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/server/tests/test_mcp_server_more.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/server/tests/test_run_wrapper.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/server/tests/test_server_extra.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/server/tests/test_sigterm_runner.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/settings.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/shared/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/shared/exceptions.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/shared/hints.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/shared/requests.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/shared/tests/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/shared/tests/test_exceptions.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/shared/tests/test_hints.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/shared/tests/test_requests.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/telemetry/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/telemetry/exporter.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/telemetry/instrument.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/telemetry/tests/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/telemetry/tests/test_eval_telemetry.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/telemetry/tests/test_exporter.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/telemetry/tests/test_instrument.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/agent.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/apply_patch.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/base.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/bash.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/computer/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/computer/anthropic.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/computer/gemini.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/computer/hud.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/computer/openai.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/computer/qwen.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/computer/settings.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/edit.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/executors/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/executors/base.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/executors/pyautogui.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/executors/tests/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/executors/tests/test_base_executor.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/executors/tests/test_pyautogui_executor.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/executors/xdo.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/grounding/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/grounding/config.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/grounding/grounded_tool.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/grounding/grounder.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/grounding/tests/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/grounding/tests/test_grounded_tool.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/jupyter.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/playwright.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/response.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/shell.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/submit.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_agent_tool.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_apply_patch.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_base.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_bash.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_bash_extended.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_computer.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_computer_actions.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_edit.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_init.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_jupyter_tool.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_playwright_tool.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_response.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_shell.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_submit.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_tools.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_tools_init.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_types.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/tests/test_utils.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/types.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/tools/utils.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/types.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/utils/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/utils/env.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/utils/hud_console.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/utils/mcp.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/utils/pretty_errors.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/utils/strict_schema.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/utils/telemetry.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/utils/tests/__init__.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/utils/tests/test_init.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/utils/tests/test_mcp.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/utils/tests/test_pretty_errors.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/utils/tests/test_telemetry.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/utils/tests/test_tool_shorthand.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/utils/tool_shorthand.py +0 -0
- {hud_python-0.5.5 → hud_python-0.5.7}/hud/utils/types.py +0 -0
|
@@ -5,16 +5,18 @@ from __future__ import annotations
|
|
|
5
5
|
import copy
|
|
6
6
|
import logging
|
|
7
7
|
from inspect import cleandoc
|
|
8
|
-
from typing import Any, ClassVar, Literal, cast
|
|
8
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Literal, cast
|
|
9
9
|
|
|
10
10
|
import mcp.types as types
|
|
11
11
|
from anthropic import AsyncAnthropic, AsyncAnthropicBedrock, Omit
|
|
12
12
|
from anthropic.types import CacheControlEphemeralParam
|
|
13
13
|
from anthropic.types.beta import (
|
|
14
14
|
BetaBase64ImageSourceParam,
|
|
15
|
+
BetaBase64PDFSourceParam,
|
|
15
16
|
BetaContentBlockParam,
|
|
16
17
|
BetaImageBlockParam,
|
|
17
18
|
BetaMessageParam,
|
|
19
|
+
BetaRequestDocumentBlockParam,
|
|
18
20
|
BetaTextBlockParam,
|
|
19
21
|
BetaToolBash20250124Param,
|
|
20
22
|
BetaToolComputerUse20250124Param,
|
|
@@ -33,6 +35,9 @@ from hud.utils.types import with_signature
|
|
|
33
35
|
|
|
34
36
|
from .base import BaseCreateParams, MCPAgent
|
|
35
37
|
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from collections.abc import Sequence
|
|
40
|
+
|
|
36
41
|
logger = logging.getLogger(__name__)
|
|
37
42
|
|
|
38
43
|
|
|
@@ -220,7 +225,10 @@ class ClaudeAgent(MCPAgent):
|
|
|
220
225
|
async def format_tool_results(
|
|
221
226
|
self, tool_calls: list[MCPToolCall], tool_results: list[MCPToolResult]
|
|
222
227
|
) -> list[BetaMessageParam]:
|
|
223
|
-
"""Format tool results into Claude messages.
|
|
228
|
+
"""Format tool results into Claude messages.
|
|
229
|
+
|
|
230
|
+
Handles EmbeddedResource (PDFs), images, and text content.
|
|
231
|
+
"""
|
|
224
232
|
# Process each tool result
|
|
225
233
|
user_content = []
|
|
226
234
|
|
|
@@ -232,7 +240,9 @@ class ClaudeAgent(MCPAgent):
|
|
|
232
240
|
continue
|
|
233
241
|
|
|
234
242
|
# Convert MCP tool results to Claude format
|
|
235
|
-
claude_blocks
|
|
243
|
+
claude_blocks: list[
|
|
244
|
+
BetaTextBlockParam | BetaImageBlockParam | BetaRequestDocumentBlockParam
|
|
245
|
+
] = []
|
|
236
246
|
|
|
237
247
|
if result.isError:
|
|
238
248
|
# Extract error message from content
|
|
@@ -249,6 +259,16 @@ class ClaudeAgent(MCPAgent):
|
|
|
249
259
|
claude_blocks.append(text_to_content_block(content.text))
|
|
250
260
|
elif isinstance(content, types.ImageContent):
|
|
251
261
|
claude_blocks.append(base64_to_content_block(content.data))
|
|
262
|
+
elif isinstance(content, types.EmbeddedResource):
|
|
263
|
+
# Handle embedded resources (PDFs)
|
|
264
|
+
resource = content.resource
|
|
265
|
+
if (
|
|
266
|
+
isinstance(resource, types.BlobResourceContents)
|
|
267
|
+
and resource.mimeType == "application/pdf"
|
|
268
|
+
):
|
|
269
|
+
claude_blocks.append(
|
|
270
|
+
document_to_content_block(base64_data=resource.blob)
|
|
271
|
+
)
|
|
252
272
|
|
|
253
273
|
# Add tool result
|
|
254
274
|
user_content.append(tool_use_content_block(tool_use_id, claude_blocks))
|
|
@@ -303,7 +323,7 @@ class ClaudeAgent(MCPAgent):
|
|
|
303
323
|
display_width_px=computer_settings.ANTHROPIC_COMPUTER_WIDTH,
|
|
304
324
|
display_height_px=computer_settings.ANTHROPIC_COMPUTER_HEIGHT,
|
|
305
325
|
)
|
|
306
|
-
elif tool.name == "computer":
|
|
326
|
+
elif tool.name == "computer" or tool.name.endswith("_computer"):
|
|
307
327
|
logger.warning(
|
|
308
328
|
"Renamed tool %s to 'computer', dropping original 'computer' tool",
|
|
309
329
|
selected_computer_tool.name,
|
|
@@ -330,11 +350,14 @@ class ClaudeAgent(MCPAgent):
|
|
|
330
350
|
self.claude_tools = []
|
|
331
351
|
for tool in available_tools:
|
|
332
352
|
claude_tool = to_api_tool(tool)
|
|
333
|
-
if claude_tool is None
|
|
353
|
+
if claude_tool is None:
|
|
334
354
|
continue
|
|
335
|
-
|
|
355
|
+
tool_name = claude_tool.get("name")
|
|
356
|
+
if tool_name is None:
|
|
357
|
+
continue
|
|
358
|
+
if tool_name == "computer":
|
|
336
359
|
self.has_computer_tool = True
|
|
337
|
-
self.tool_mapping[
|
|
360
|
+
self.tool_mapping[tool_name] = tool.name
|
|
338
361
|
self.claude_tools.append(claude_tool)
|
|
339
362
|
|
|
340
363
|
def _add_prompt_caching(self, messages: list[BetaMessageParam]) -> list[BetaMessageParam]:
|
|
@@ -380,8 +403,21 @@ def text_to_content_block(text: str) -> BetaTextBlockParam:
|
|
|
380
403
|
return {"type": "text", "text": text}
|
|
381
404
|
|
|
382
405
|
|
|
406
|
+
def document_to_content_block(base64_data: str) -> BetaRequestDocumentBlockParam:
|
|
407
|
+
"""Convert base64 PDF to Claude document content block."""
|
|
408
|
+
return BetaRequestDocumentBlockParam(
|
|
409
|
+
type="document",
|
|
410
|
+
source=BetaBase64PDFSourceParam(
|
|
411
|
+
type="base64",
|
|
412
|
+
media_type="application/pdf",
|
|
413
|
+
data=base64_data,
|
|
414
|
+
),
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
|
|
383
418
|
def tool_use_content_block(
|
|
384
|
-
tool_use_id: str,
|
|
419
|
+
tool_use_id: str,
|
|
420
|
+
content: Sequence[BetaTextBlockParam | BetaImageBlockParam | BetaRequestDocumentBlockParam],
|
|
385
421
|
) -> BetaToolResultBlockParam:
|
|
386
422
|
"""Create tool result content block."""
|
|
387
|
-
return {"type": "tool_result", "tool_use_id": tool_use_id, "content": content}
|
|
423
|
+
return {"type": "tool_result", "tool_use_id": tool_use_id, "content": content} # pyright: ignore[reportReturnType]
|
|
@@ -129,6 +129,8 @@ class OperatorAgent(OpenAIAgent):
|
|
|
129
129
|
display_height=self._operator_display_height,
|
|
130
130
|
environment=self._operator_environment,
|
|
131
131
|
)
|
|
132
|
+
if tool.name == "computer" or tool.name.endswith("_computer"):
|
|
133
|
+
return None
|
|
132
134
|
return super()._to_openai_tool(tool)
|
|
133
135
|
|
|
134
136
|
def _extract_tool_call(self, item: Any) -> MCPToolCall | None:
|
|
@@ -22,7 +22,7 @@ from hud.types import MCPToolCall, MCPToolResult
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
23
|
from collections.abc import Generator
|
|
24
24
|
|
|
25
|
-
from anthropic.types.beta import
|
|
25
|
+
from anthropic.types.beta import BetaMessageParam
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class MockEvalContext(EvalContext):
|
|
@@ -123,9 +123,7 @@ class TestClaudeHelperFunctions:
|
|
|
123
123
|
def test_tool_use_content_block(self) -> None:
|
|
124
124
|
"""Test tool result content block creation."""
|
|
125
125
|
tool_use_id = "tool_123"
|
|
126
|
-
content
|
|
127
|
-
text_to_content_block("Result text")
|
|
128
|
-
]
|
|
126
|
+
content = [text_to_content_block("Result text")]
|
|
129
127
|
|
|
130
128
|
result = tool_use_content_block(tool_use_id, content)
|
|
131
129
|
|
|
@@ -250,6 +250,15 @@ async def run_mcp_module(
|
|
|
250
250
|
elif hasattr(module, "__dict__") and attr_name in module.__dict__:
|
|
251
251
|
mcp_server = module.__dict__[attr_name]
|
|
252
252
|
|
|
253
|
+
# If default 'mcp' not found, try 'env' as fallback
|
|
254
|
+
if mcp_server is None and attr_name == "mcp":
|
|
255
|
+
for fallback in ["env", "environment", "server"]:
|
|
256
|
+
if hasattr(module, fallback):
|
|
257
|
+
mcp_server = getattr(module, fallback)
|
|
258
|
+
if verbose:
|
|
259
|
+
hud_console.info(f"Found '{fallback}' instead of 'mcp'")
|
|
260
|
+
break
|
|
261
|
+
|
|
253
262
|
if mcp_server is None:
|
|
254
263
|
hud_console.error(f"Module '{module_name}' does not have '{attr_name}' defined")
|
|
255
264
|
hud_console.info("")
|
|
@@ -258,8 +267,8 @@ async def run_mcp_module(
|
|
|
258
267
|
hud_console.info("")
|
|
259
268
|
hud_console.info("[bold cyan]Expected structure:[/bold cyan]")
|
|
260
269
|
hud_console.info(" from hud.environment import Environment")
|
|
261
|
-
hud_console.info(
|
|
262
|
-
raise AttributeError(f"Module '{module_name}' must define '
|
|
270
|
+
hud_console.info(" env = Environment('my-env') # or mcp = ...")
|
|
271
|
+
raise AttributeError(f"Module '{module_name}' must define 'mcp', 'env', or 'environment'")
|
|
263
272
|
|
|
264
273
|
# Only show full header on first run, brief message on reload
|
|
265
274
|
if is_reload:
|
|
@@ -27,8 +27,8 @@ Usage:
|
|
|
27
27
|
from hud.environment.connection import ConnectionConfig, ConnectionType, Connector
|
|
28
28
|
from hud.environment.environment import Environment
|
|
29
29
|
from hud.environment.mock import MockMixin, generate_mock_value
|
|
30
|
-
from hud.environment.router import ConflictResolution, ToolRouter
|
|
31
|
-
from hud.environment.scenarios import ScenarioMixin
|
|
30
|
+
from hud.environment.router import ConflictResolution, MCPRouter, ToolRouter
|
|
31
|
+
from hud.environment.scenarios import ScenarioMixin, ScenarioSession
|
|
32
32
|
from hud.environment.types import EnvConfig
|
|
33
33
|
from hud.environment.utils import ToolFormat, format_result, parse_tool_call, parse_tool_calls
|
|
34
34
|
|
|
@@ -39,10 +39,12 @@ __all__ = [
|
|
|
39
39
|
"Connector",
|
|
40
40
|
"EnvConfig",
|
|
41
41
|
"Environment",
|
|
42
|
+
"MCPRouter",
|
|
42
43
|
"MockMixin",
|
|
43
44
|
"ScenarioMixin",
|
|
45
|
+
"ScenarioSession",
|
|
44
46
|
"ToolFormat",
|
|
45
|
-
"ToolRouter",
|
|
47
|
+
"ToolRouter", # Backwards compat alias for MCPRouter
|
|
46
48
|
"format_result",
|
|
47
49
|
"generate_mock_value",
|
|
48
50
|
"parse_tool_call",
|
|
@@ -68,6 +68,8 @@ class Connector:
|
|
|
68
68
|
self.connection_type = connection_type
|
|
69
69
|
self.client: FastMCPClient[Any] | None = None
|
|
70
70
|
self._tools_cache: list[mcp_types.Tool] | None = None
|
|
71
|
+
self._prompts_cache: list[mcp_types.Prompt] | None = None
|
|
72
|
+
self._resources_cache: list[mcp_types.Resource] | None = None
|
|
71
73
|
|
|
72
74
|
def copy(self) -> Connector:
|
|
73
75
|
"""Create a copy of this connector with fresh (unconnected) state.
|
|
@@ -101,6 +103,14 @@ class Connector:
|
|
|
101
103
|
def cached_tools(self) -> list[mcp_types.Tool]:
|
|
102
104
|
return self._tools_cache or []
|
|
103
105
|
|
|
106
|
+
@property
|
|
107
|
+
def cached_prompts(self) -> list[mcp_types.Prompt]:
|
|
108
|
+
return self._prompts_cache or []
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def cached_resources(self) -> list[mcp_types.Resource]:
|
|
112
|
+
return self._resources_cache or []
|
|
113
|
+
|
|
104
114
|
async def connect(self) -> None:
|
|
105
115
|
"""Create FastMCP client and connect.
|
|
106
116
|
|
|
@@ -115,14 +125,20 @@ class Connector:
|
|
|
115
125
|
await self.client.__aenter__()
|
|
116
126
|
|
|
117
127
|
async def disconnect(self) -> None:
|
|
118
|
-
"""Disconnect and clear
|
|
128
|
+
"""Disconnect and clear all caches."""
|
|
119
129
|
if self.client is not None and self.is_connected:
|
|
120
130
|
await self.client.__aexit__(None, None, None)
|
|
121
131
|
self.client = None
|
|
122
132
|
self._tools_cache = None
|
|
133
|
+
self._prompts_cache = None
|
|
134
|
+
self._resources_cache = None
|
|
123
135
|
|
|
124
136
|
async def list_tools(self) -> list[mcp_types.Tool]:
|
|
125
|
-
"""Fetch tools from server, apply filters/transforms/prefix, and cache.
|
|
137
|
+
"""Fetch tools from server, apply filters/transforms/prefix, and cache.
|
|
138
|
+
|
|
139
|
+
Always fetches fresh data from the server (no caching check).
|
|
140
|
+
The result is cached for use by router.build() via cached_tools property.
|
|
141
|
+
"""
|
|
126
142
|
if self.client is None:
|
|
127
143
|
raise RuntimeError("Not connected - call connect() first")
|
|
128
144
|
tools = await self.client.list_tools()
|
|
@@ -178,14 +194,26 @@ class Connector:
|
|
|
178
194
|
return await self.client.call_tool_mcp(name, arguments or {})
|
|
179
195
|
|
|
180
196
|
async def list_resources(self) -> list[mcp_types.Resource]:
|
|
197
|
+
"""Fetch resources from server and cache.
|
|
198
|
+
|
|
199
|
+
Always fetches fresh data from the server (no caching check).
|
|
200
|
+
The result is cached for use by router.build_resources() via cached_resources property.
|
|
201
|
+
"""
|
|
181
202
|
if self.client is None:
|
|
182
203
|
raise RuntimeError("Not connected - call connect() first")
|
|
183
|
-
|
|
204
|
+
self._resources_cache = await self.client.list_resources()
|
|
205
|
+
return self._resources_cache
|
|
184
206
|
|
|
185
207
|
async def list_prompts(self) -> list[mcp_types.Prompt]:
|
|
208
|
+
"""Fetch prompts from server and cache.
|
|
209
|
+
|
|
210
|
+
Always fetches fresh data from the server (no caching check).
|
|
211
|
+
The result is cached for use by router.build_prompts() via cached_prompts property.
|
|
212
|
+
"""
|
|
186
213
|
if self.client is None:
|
|
187
214
|
raise RuntimeError("Not connected - call connect() first")
|
|
188
|
-
|
|
215
|
+
self._prompts_cache = await self.client.list_prompts()
|
|
216
|
+
return self._prompts_cache
|
|
189
217
|
|
|
190
218
|
async def read_resource(
|
|
191
219
|
self, uri: str
|
|
@@ -119,6 +119,26 @@ class Environment(
|
|
|
119
119
|
|
|
120
120
|
MAX_CONCURRENT_CONNECTIONS = 10
|
|
121
121
|
|
|
122
|
+
@staticmethod
|
|
123
|
+
def _normalize_name(name: str) -> str:
|
|
124
|
+
"""Normalize environment name to lowercase with hyphens.
|
|
125
|
+
|
|
126
|
+
- Strips whitespace
|
|
127
|
+
- Replaces spaces and underscores with hyphens
|
|
128
|
+
- Lowercases the result
|
|
129
|
+
- Removes any non-alphanumeric characters except hyphens
|
|
130
|
+
"""
|
|
131
|
+
import re
|
|
132
|
+
|
|
133
|
+
normalized = name.strip().lower()
|
|
134
|
+
normalized = normalized.replace(" ", "-").replace("_", "-")
|
|
135
|
+
# Keep only alphanumeric and hyphens
|
|
136
|
+
normalized = re.sub(r"[^a-z0-9-]", "", normalized)
|
|
137
|
+
# Collapse multiple hyphens
|
|
138
|
+
normalized = re.sub(r"-+", "-", normalized)
|
|
139
|
+
# Strip leading/trailing hyphens
|
|
140
|
+
return normalized.strip("-") or "environment"
|
|
141
|
+
|
|
122
142
|
def __init__(
|
|
123
143
|
self,
|
|
124
144
|
name: str = "environment",
|
|
@@ -126,10 +146,15 @@ class Environment(
|
|
|
126
146
|
conflict_resolution: ConflictResolution = ConflictResolution.PREFIX,
|
|
127
147
|
**fastmcp_kwargs: Any,
|
|
128
148
|
) -> None:
|
|
149
|
+
# Normalize name to prevent casing/spacing issues
|
|
150
|
+
name = self._normalize_name(name)
|
|
129
151
|
super().__init__(name=name, instructions=instructions, **fastmcp_kwargs)
|
|
130
152
|
self._connections: dict[str, Connector] = {}
|
|
131
153
|
self._router = ToolRouter(conflict_resolution=conflict_resolution)
|
|
132
|
-
|
|
154
|
+
# Granular routing flags - only rebuild what's invalidated
|
|
155
|
+
self._tool_routing_built = False
|
|
156
|
+
self._prompt_routing_built = False
|
|
157
|
+
self._resource_routing_built = False
|
|
133
158
|
self._in_context = False
|
|
134
159
|
|
|
135
160
|
# Tool call queues - run after connections established
|
|
@@ -182,6 +207,10 @@ class Environment(
|
|
|
182
207
|
|
|
183
208
|
return tools
|
|
184
209
|
|
|
210
|
+
def add_tool(self, obj: Any, **kwargs: Any) -> None:
|
|
211
|
+
super().add_tool(obj, **kwargs)
|
|
212
|
+
self._tool_routing_built = False # Only invalidate tool routing
|
|
213
|
+
|
|
185
214
|
async def call_tool(self, call: Any, /, **kwargs: Any) -> Any:
|
|
186
215
|
"""Call a tool, auto-detecting format and returning matching result format.
|
|
187
216
|
|
|
@@ -312,7 +341,7 @@ class Environment(
|
|
|
312
341
|
"""Connect all connectors, build routing, run setup tools."""
|
|
313
342
|
self._in_context = True
|
|
314
343
|
|
|
315
|
-
# Connect to all servers
|
|
344
|
+
# Connect to all servers and fetch tools/prompts/resources in parallel
|
|
316
345
|
sem = asyncio.Semaphore(self.MAX_CONCURRENT_CONNECTIONS)
|
|
317
346
|
errors: list[tuple[str, Exception]] = []
|
|
318
347
|
|
|
@@ -320,7 +349,12 @@ class Environment(
|
|
|
320
349
|
async with sem:
|
|
321
350
|
try:
|
|
322
351
|
await conn.connect()
|
|
323
|
-
|
|
352
|
+
# Batch fetch all MCP primitives in parallel for performance
|
|
353
|
+
await asyncio.gather(
|
|
354
|
+
conn.list_tools(),
|
|
355
|
+
conn.list_prompts(),
|
|
356
|
+
conn.list_resources(),
|
|
357
|
+
)
|
|
324
358
|
except Exception as e:
|
|
325
359
|
errors.append((name, e))
|
|
326
360
|
|
|
@@ -369,7 +403,10 @@ class Environment(
|
|
|
369
403
|
if self._connections:
|
|
370
404
|
await asyncio.gather(*[c.disconnect() for c in self._connections.values()])
|
|
371
405
|
self._router.clear()
|
|
372
|
-
self.
|
|
406
|
+
self._tool_routing_built = False
|
|
407
|
+
self._prompt_routing_built = False
|
|
408
|
+
self._resource_routing_built = False
|
|
409
|
+
self._active_session = None # Clear stale scenario state
|
|
373
410
|
|
|
374
411
|
async def run_async(
|
|
375
412
|
self,
|
|
@@ -388,9 +425,22 @@ class Environment(
|
|
|
388
425
|
)
|
|
389
426
|
|
|
390
427
|
async def _build_routing(self) -> None:
|
|
428
|
+
"""Build routing for tools, prompts, and resources in parallel.
|
|
429
|
+
|
|
430
|
+
Only rebuilds what's actually invalidated for performance.
|
|
431
|
+
"""
|
|
432
|
+
tasks = []
|
|
433
|
+
if not self._tool_routing_built:
|
|
434
|
+
tasks.append(self._build_tool_routing())
|
|
435
|
+
if not self._prompt_routing_built:
|
|
436
|
+
tasks.append(self._build_prompt_routing())
|
|
437
|
+
if not self._resource_routing_built:
|
|
438
|
+
tasks.append(self._build_resource_routing())
|
|
439
|
+
if tasks:
|
|
440
|
+
await asyncio.gather(*tasks)
|
|
441
|
+
|
|
442
|
+
async def _build_tool_routing(self) -> None:
|
|
391
443
|
"""Build tool routing from local tools and connection caches."""
|
|
392
|
-
# Use get_tools() not list_tools() - it includes mounted servers without
|
|
393
|
-
# requiring MCP server communication (via_server=False)
|
|
394
444
|
local_tools_dict = await self._tool_manager.get_tools()
|
|
395
445
|
local_tools = list(local_tools_dict.values())
|
|
396
446
|
self._router.build(
|
|
@@ -398,9 +448,23 @@ class Environment(
|
|
|
398
448
|
connections=self._connections,
|
|
399
449
|
connection_order=list(self._connections.keys()),
|
|
400
450
|
)
|
|
401
|
-
self._routing_built = True
|
|
402
451
|
# Populate mock schemas for auto-generated mock values
|
|
403
452
|
self._populate_mock_schemas()
|
|
453
|
+
self._tool_routing_built = True
|
|
454
|
+
|
|
455
|
+
async def _build_prompt_routing(self) -> None:
|
|
456
|
+
"""Build prompt routing from local prompts and connections."""
|
|
457
|
+
local_prompts_dict = await self._prompt_manager.get_prompts()
|
|
458
|
+
local_prompts = [p.to_mcp_prompt() for p in local_prompts_dict.values()]
|
|
459
|
+
self._router.build_prompts(local_prompts, self._connections)
|
|
460
|
+
self._prompt_routing_built = True
|
|
461
|
+
|
|
462
|
+
async def _build_resource_routing(self) -> None:
|
|
463
|
+
"""Build resource routing from local resources and connections."""
|
|
464
|
+
local_resources_dict = await self._resource_manager.get_resources()
|
|
465
|
+
local_resources = [r.to_mcp_resource() for r in local_resources_dict.values()]
|
|
466
|
+
self._router.build_resources(local_resources, self._connections)
|
|
467
|
+
self._resource_routing_built = True
|
|
404
468
|
|
|
405
469
|
# =========================================================================
|
|
406
470
|
# MCP Protocol Overrides - Include connector tools in MCP responses
|
|
@@ -416,8 +480,8 @@ class Environment(
|
|
|
416
480
|
|
|
417
481
|
async def _env_list_tools(self) -> list[mcp_types.Tool]:
|
|
418
482
|
"""Return all tools including those from connectors."""
|
|
419
|
-
if not self.
|
|
420
|
-
await self.
|
|
483
|
+
if not self._tool_routing_built:
|
|
484
|
+
await self._build_tool_routing()
|
|
421
485
|
return self._router.tools
|
|
422
486
|
|
|
423
487
|
async def _env_call_tool(self, name: str, arguments: dict[str, Any] | None = None) -> list[Any]:
|
|
@@ -430,10 +494,10 @@ class Environment(
|
|
|
430
494
|
# =========================================================================
|
|
431
495
|
|
|
432
496
|
async def list_tools(self) -> list[mcp_types.Tool]:
|
|
433
|
-
"""Refresh tools from all connections and rebuild routing."""
|
|
497
|
+
"""Refresh tools from all connections and rebuild tool routing."""
|
|
434
498
|
if self._connections:
|
|
435
499
|
await asyncio.gather(*[c.list_tools() for c in self._connections.values()])
|
|
436
|
-
await self.
|
|
500
|
+
await self._build_tool_routing()
|
|
437
501
|
return self._router.tools
|
|
438
502
|
|
|
439
503
|
async def _execute_tool(self, name: str, arguments: dict[str, Any]) -> MCPToolResult:
|
|
@@ -446,6 +510,10 @@ class Environment(
|
|
|
446
510
|
logger.debug("Mock mode: returning mock result for tool %s", name)
|
|
447
511
|
return self._get_mock_result(name, arguments)
|
|
448
512
|
|
|
513
|
+
# Rebuild tool routing if invalidated (e.g., after add_tool)
|
|
514
|
+
if not self._tool_routing_built:
|
|
515
|
+
await self._build_tool_routing()
|
|
516
|
+
|
|
449
517
|
if self._router.is_local(name):
|
|
450
518
|
# Call tool manager directly to avoid FastMCP context requirement
|
|
451
519
|
result = await self._tool_manager.call_tool(name, arguments)
|
|
@@ -471,86 +539,83 @@ class Environment(
|
|
|
471
539
|
# =========================================================================
|
|
472
540
|
|
|
473
541
|
async def list_resources(self) -> list[mcp_types.Resource]:
|
|
474
|
-
"""
|
|
475
|
-
local = list((await self._resource_manager.get_resources()).values())
|
|
476
|
-
resources: list[mcp_types.Resource] = [r.to_mcp_resource() for r in local]
|
|
477
|
-
|
|
542
|
+
"""Refresh resources from all connections and rebuild resource routing."""
|
|
478
543
|
if self._connections:
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
for r in results:
|
|
483
|
-
if isinstance(r, list):
|
|
484
|
-
resources.extend(r)
|
|
485
|
-
|
|
486
|
-
return resources
|
|
544
|
+
await asyncio.gather(*[c.list_resources() for c in self._connections.values()])
|
|
545
|
+
await self._build_resource_routing()
|
|
546
|
+
return self._router.resources
|
|
487
547
|
|
|
488
548
|
async def read_resource(
|
|
489
549
|
self, uri: str
|
|
490
550
|
) -> list[mcp_types.TextResourceContents | mcp_types.BlobResourceContents]:
|
|
491
|
-
"""Read a resource by URI
|
|
551
|
+
"""Read a resource by URI using router for connection lookup."""
|
|
492
552
|
from pydantic import AnyUrl
|
|
493
553
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
if isinstance(result, str):
|
|
498
|
-
return [mcp_types.TextResourceContents(uri=resource_uri, text=result)]
|
|
499
|
-
import base64
|
|
554
|
+
# Ensure resource routing is built
|
|
555
|
+
if not self._resource_routing_built:
|
|
556
|
+
await self._build_resource_routing()
|
|
500
557
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
uri=resource_uri, blob=base64.b64encode(result).decode()
|
|
504
|
-
)
|
|
505
|
-
]
|
|
506
|
-
except Exception as e:
|
|
507
|
-
logger.debug("Local resource read failed for %s: %s", uri, e)
|
|
558
|
+
# Use router to find which connection has this resource
|
|
559
|
+
conn_name = self._router.get_resource_connection(uri)
|
|
508
560
|
|
|
509
|
-
|
|
561
|
+
if conn_name is None:
|
|
562
|
+
# Local resource
|
|
510
563
|
try:
|
|
511
|
-
|
|
564
|
+
result = await self._resource_manager.read_resource(uri)
|
|
565
|
+
resource_uri = AnyUrl(uri)
|
|
566
|
+
if isinstance(result, str):
|
|
567
|
+
return [mcp_types.TextResourceContents(uri=resource_uri, text=result)]
|
|
568
|
+
import base64
|
|
569
|
+
|
|
570
|
+
return [
|
|
571
|
+
mcp_types.BlobResourceContents(
|
|
572
|
+
uri=resource_uri, blob=base64.b64encode(result).decode()
|
|
573
|
+
)
|
|
574
|
+
]
|
|
512
575
|
except Exception as e:
|
|
513
|
-
logger.debug("
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
576
|
+
logger.debug("Local resource read failed for %s: %s", uri, e)
|
|
577
|
+
raise ValueError(f"Resource not found: {uri}") from e
|
|
578
|
+
else:
|
|
579
|
+
# Remote resource
|
|
580
|
+
conn = self._connections.get(conn_name)
|
|
581
|
+
if conn is None:
|
|
582
|
+
raise ValueError(f"Connection '{conn_name}' not found for resource '{uri}'")
|
|
583
|
+
return await conn.read_resource(uri)
|
|
517
584
|
|
|
518
585
|
# =========================================================================
|
|
519
586
|
# Prompt Operations
|
|
520
587
|
# =========================================================================
|
|
521
588
|
|
|
522
589
|
async def list_prompts(self) -> list[mcp_types.Prompt]:
|
|
523
|
-
"""
|
|
524
|
-
local = list((await self._prompt_manager.get_prompts()).values())
|
|
525
|
-
prompts: list[mcp_types.Prompt] = [p.to_mcp_prompt() for p in local]
|
|
526
|
-
|
|
590
|
+
"""Refresh prompts from all connections and rebuild prompt routing."""
|
|
527
591
|
if self._connections:
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
for r in results:
|
|
532
|
-
if isinstance(r, list):
|
|
533
|
-
prompts.extend(r)
|
|
534
|
-
|
|
535
|
-
return prompts
|
|
592
|
+
await asyncio.gather(*[c.list_prompts() for c in self._connections.values()])
|
|
593
|
+
await self._build_prompt_routing()
|
|
594
|
+
return self._router.prompts
|
|
536
595
|
|
|
537
596
|
async def get_prompt(
|
|
538
597
|
self, name: str, arguments: dict[str, Any] | None = None
|
|
539
598
|
) -> mcp_types.GetPromptResult:
|
|
540
|
-
"""Get a prompt by name
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
599
|
+
"""Get a prompt by name using router for connection lookup."""
|
|
600
|
+
# Ensure prompt routing is built
|
|
601
|
+
if not self._prompt_routing_built:
|
|
602
|
+
await self._build_prompt_routing()
|
|
603
|
+
|
|
604
|
+
# Use router to find which connection has this prompt
|
|
605
|
+
conn_name = self._router.get_prompt_connection(name)
|
|
545
606
|
|
|
546
|
-
|
|
607
|
+
if conn_name is None:
|
|
608
|
+
# Local prompt
|
|
547
609
|
try:
|
|
548
|
-
return await
|
|
610
|
+
return await self._prompt_manager.render_prompt(name, arguments or {})
|
|
549
611
|
except Exception as e:
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
612
|
+
raise ValueError(f"Prompt not found: {name}") from e
|
|
613
|
+
else:
|
|
614
|
+
# Remote prompt
|
|
615
|
+
conn = self._connections.get(conn_name)
|
|
616
|
+
if conn is None:
|
|
617
|
+
raise ValueError(f"Connection '{conn_name}' not found for prompt '{name}'")
|
|
618
|
+
return await conn.get_prompt(name, arguments)
|
|
554
619
|
|
|
555
620
|
# =========================================================================
|
|
556
621
|
# Server Methods
|
|
@@ -602,7 +667,7 @@ class Environment(
|
|
|
602
667
|
For v4 format: requires mcp_config, prompt, AND evaluate_tool
|
|
603
668
|
"""
|
|
604
669
|
# Check for local tools (registered via @env.tool)
|
|
605
|
-
if self._router.
|
|
670
|
+
if self._router._local_tool_names:
|
|
606
671
|
return False
|
|
607
672
|
# Check for local scenarios (registered via @env.scenario)
|
|
608
673
|
if getattr(self, "_scenarios", {}):
|
|
@@ -639,10 +704,10 @@ class Environment(
|
|
|
639
704
|
task.env.to_config() # {"prompt": "...", "mcp_config": {...}, ...}
|
|
640
705
|
```
|
|
641
706
|
"""
|
|
642
|
-
if self._router.
|
|
707
|
+
if self._router._local_tool_names:
|
|
643
708
|
raise ValueError(
|
|
644
709
|
f"Cannot serialize Environment with local tools: "
|
|
645
|
-
f"{list(self._router.
|
|
710
|
+
f"{list(self._router._local_tool_names)}. "
|
|
646
711
|
"Local tools require local execution. For remote submission, "
|
|
647
712
|
"use dict config or connect to a remote hub."
|
|
648
713
|
)
|