omnimancer-cli 0.3.5__tar.gz → 0.3.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.
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/PKG-INFO +1 -1
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/h2l_command.py +6 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/h2l_headless.py +10 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/engine.py +40 -9
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/env_loader.py +4 -2
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/provider_initializer.py +31 -1
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/orchestrator.py +89 -18
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/worker.py +50 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/bedrock.py +33 -12
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer_cli.egg-info/PKG-INFO +1 -1
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/pyproject.toml +1 -1
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_bedrock_provider.py +103 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/LICENSE +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/README.md +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omn_fleet_hook.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/__init__.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/__main__.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/__init__.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/agent_loop.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/approval_formatter.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/approval_integration.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/approval_prompt.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/command_dispatch.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/commands.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/completion.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/display.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/dynamic_commands.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/file_mentions.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/handlers/__init__.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/headless.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/headless_checkpoint.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/interface.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/prompt_input.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/pt_completion.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/session.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/subagent.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/system_prompts.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/tool_handler.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/turn_notify.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/usage.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/__init__.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/__init__.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/approval_context.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/approval_interface.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/approval_manager.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/config.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/file_system_manager.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/program_executor.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/read_before_write_errors.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/status_core.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/status_manager.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/tool_definitions.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/types.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/web_client.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/workflow_orchestrator.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent_engine.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent_managers.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent_mode_manager.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/chat_manager.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/config_manager.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/config_migration.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/config_validator.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/conversation_manager.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/fallback_manager.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/health_monitor.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/history_manager.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/hooks.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/mcp_integration_layer.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/models.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/prompt_enhancer.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/provider_capabilities.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/provider_registry.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/provider_resolver.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/rate_limit_fallback.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/security/__init__.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/security/approval_workflow.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/security/audit_logger.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/security/permission_controller.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/security/permission_rules.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/security/sandbox_manager.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/security/security_manager.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/signal_handler.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/events/__init__.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/events/emitter.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/events/jsonl_writer.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/events/schema.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/__init__.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/judge.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/loop.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/models.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/planner.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/prompts.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/refs.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/render.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/mcp/__init__.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/mcp/client.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/mcp/manager.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/__init__.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/azure.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/base.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/cache_tokens.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/claude.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/claude_code.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/claude_credentials.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/cohere.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/digitalocean.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/factory.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/gemini.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/mistral.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/ollama.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/openai.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/openai_compatible.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/openrouter.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/perplexity.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/vertex.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/xai.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/tui/__init__.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/tui/fleet/__init__.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/tui/fleet/app.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/tui/fleet/cli.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/tui/fleet/models.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/tui/fleet/sources.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/tui/fleet/widgets.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/ui/__init__.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/ui/cancellation_handler.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/ui/progress_indicator.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/ui/streaming_display.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/ui/turn_activity.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/utils/__init__.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/utils/error_handler.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/utils/errors.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/utils/retry.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer_cli.egg-info/SOURCES.txt +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer_cli.egg-info/dependency_links.txt +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer_cli.egg-info/entry_points.txt +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer_cli.egg-info/requires.txt +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer_cli.egg-info/top_level.txt +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/setup.cfg +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_approval_integration_compatibility.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_approval_preview_format.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_approval_remember.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_approval_system.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_azure_provider.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_claude_code_provider.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_claude_credentials.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_claude_prompt_cache.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_claude_provider.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_cohere_provider.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_command_approval_security.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_config_comprehensive.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_config_migration_passthrough.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_conversation_history.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_conversation_manager.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_core_engine.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_digitalocean_provider.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_enhanced_model_info.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_env_loader.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_error_handling_robustness.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_execution_timeout_errors.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_fallback_manager.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_fixtures.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_gemini_provider.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_hooks.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_httpx_compatibility.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_import_validation.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_interface_smoke_tests.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_mcp_integration.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_mcp_integration_layer.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_mcp_management_commands.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_mistral_provider.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_models.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_ollama_provider.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_openai_compatible_provider.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_openai_context_fit.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_openai_provider_tools.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_openai_request_timeout.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_openai_streaming.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_openrouter_provider.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_permission_rules.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_perplexity_provider.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_program_executor.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_provider_aliases.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_provider_base.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_provider_cache_tokens.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_provider_capability_consistency.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_provider_discovery.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_provider_integration.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_provider_registry.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_rate_limit_fallback.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_retry.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_security_validation.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_system_integration.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_system_prompts.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_tool_calling_integration.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_tool_handler.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_vertex_provider.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_web_request_markers.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_workflows_comprehensive.py +0 -0
- {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_xai_provider.py +0 -0
|
@@ -251,6 +251,12 @@ class TUIPresenter:
|
|
|
251
251
|
f"{verdict.judge}): {failures}"
|
|
252
252
|
)
|
|
253
253
|
|
|
254
|
+
async def on_model_dropped(self, ref: H2LModelRef, error: str) -> None:
|
|
255
|
+
self.console.print(
|
|
256
|
+
f" [yellow]Dropped {ref.label}: it cannot call tools, so the "
|
|
257
|
+
f"remaining low models take its stories[/yellow] [dim]({error})[/dim]"
|
|
258
|
+
)
|
|
259
|
+
|
|
254
260
|
async def on_escalation(self, story: Story, outcome: StoryOutcome) -> str:
|
|
255
261
|
while True:
|
|
256
262
|
answer = (
|
|
@@ -232,6 +232,16 @@ class HeadlessPresenter:
|
|
|
232
232
|
+ (f" ({'; '.join(verdict.failures)})" if verdict.failures else "")
|
|
233
233
|
)
|
|
234
234
|
|
|
235
|
+
async def on_model_dropped(self, ref: H2LModelRef, error: str) -> None:
|
|
236
|
+
self.emitter.emit_h2l(
|
|
237
|
+
"model_dropped",
|
|
238
|
+
{"provider": ref.provider, "model": ref.model, "error": error},
|
|
239
|
+
)
|
|
240
|
+
self._progress(
|
|
241
|
+
f"dropped {ref.label}: it cannot call tools, so the remaining low "
|
|
242
|
+
f"models take its stories ({error})"
|
|
243
|
+
)
|
|
244
|
+
|
|
235
245
|
async def on_escalation(self, story: Story, outcome: StoryOutcome) -> str:
|
|
236
246
|
return "high"
|
|
237
247
|
|
|
@@ -182,11 +182,12 @@ class CoreEngine:
|
|
|
182
182
|
# DigitalOcean request). Leave it unset so the user gets a
|
|
183
183
|
# clear error naming the provider.
|
|
184
184
|
self._unavailable_default_provider = config.default_provider
|
|
185
|
+
# One accurate line instead of a guess; the full
|
|
186
|
+
# explanation is built on demand by _no_provider_error.
|
|
185
187
|
logger.error(
|
|
186
|
-
"Default provider '%s' is
|
|
187
|
-
"initialize (missing or invalid API key?). Not falling "
|
|
188
|
-
"back to another provider.",
|
|
188
|
+
"Default provider '%s' is not available: %s",
|
|
189
189
|
config.default_provider,
|
|
190
|
+
self._no_provider_error(),
|
|
190
191
|
)
|
|
191
192
|
elif self.providers:
|
|
192
193
|
# No default configured — use first available provider.
|
|
@@ -306,14 +307,44 @@ class CoreEngine:
|
|
|
306
307
|
missing, rather than a generic "no provider" message.
|
|
307
308
|
"""
|
|
308
309
|
unavailable = getattr(self, "_unavailable_default_provider", None)
|
|
309
|
-
if unavailable:
|
|
310
|
+
if not unavailable:
|
|
311
|
+
return "No provider available. Please configure a provider first."
|
|
312
|
+
|
|
313
|
+
env_prefix = "OMNIMANCER_" + unavailable.upper().replace("-", "_")
|
|
314
|
+
try:
|
|
315
|
+
from .provider_resolver import effective_providers
|
|
316
|
+
|
|
317
|
+
entry = effective_providers(self.config_manager).get(unavailable)
|
|
318
|
+
except Exception as exc: # diagnostics must not raise
|
|
319
|
+
logger.debug(f"Could not inspect entry for {unavailable}: {exc}")
|
|
320
|
+
entry = None
|
|
321
|
+
|
|
322
|
+
if entry is None:
|
|
310
323
|
return (
|
|
311
|
-
f"Provider '{unavailable}' is selected
|
|
312
|
-
f"
|
|
313
|
-
f"
|
|
314
|
-
f"
|
|
324
|
+
f"Provider '{unavailable}' is selected as the default but is "
|
|
325
|
+
f"not configured: there is no entry for it and no "
|
|
326
|
+
f"{env_prefix}_API_KEY in the environment. Add it with "
|
|
327
|
+
f"'/config set-provider {unavailable}', or pick a different "
|
|
328
|
+
f"default with '/switch'."
|
|
315
329
|
)
|
|
316
|
-
|
|
330
|
+
|
|
331
|
+
if not (getattr(entry, "model", "") or "").strip():
|
|
332
|
+
return (
|
|
333
|
+
f"Provider '{unavailable}' has no model configured, so it was "
|
|
334
|
+
f"not started. Set {env_prefix}_MODEL, or run '/model' to pick "
|
|
335
|
+
f"one."
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
from .provider_initializer import ProviderInitializer
|
|
339
|
+
|
|
340
|
+
reason = ProviderInitializer.get_last_init_errors().get(unavailable)
|
|
341
|
+
if reason:
|
|
342
|
+
return f"Provider '{unavailable}' did not start: {reason}"
|
|
343
|
+
return (
|
|
344
|
+
f"Provider '{unavailable}' is configured but did not start. Check "
|
|
345
|
+
f"its credentials ('/config set-provider {unavailable}' or "
|
|
346
|
+
f"{env_prefix}_API_KEY) and try again."
|
|
347
|
+
)
|
|
317
348
|
|
|
318
349
|
# ------------------------------------------------------------------
|
|
319
350
|
# Fallback wiring
|
|
@@ -230,7 +230,7 @@ def apply_env_overrides(config: "Config") -> "Config":
|
|
|
230
230
|
model=overrides.get("model", ""),
|
|
231
231
|
)
|
|
232
232
|
effective.providers[name] = provider_config
|
|
233
|
-
logger.
|
|
233
|
+
logger.debug(f"Created provider '{name}' from environment overrides")
|
|
234
234
|
|
|
235
235
|
if "api_key" in overrides:
|
|
236
236
|
provider_config.api_key = overrides["api_key"]
|
|
@@ -241,6 +241,8 @@ def apply_env_overrides(config: "Config") -> "Config":
|
|
|
241
241
|
|
|
242
242
|
if default_override:
|
|
243
243
|
effective.default_provider = default_override
|
|
244
|
-
logger.
|
|
244
|
+
logger.debug(
|
|
245
|
+
f"Default provider overridden from environment: {default_override}"
|
|
246
|
+
)
|
|
245
247
|
|
|
246
248
|
return effective
|
|
@@ -44,6 +44,8 @@ class ProviderInitializer:
|
|
|
44
44
|
# Configuration
|
|
45
45
|
_lazy_loading_enabled: bool = True
|
|
46
46
|
_cache_enabled: bool = True
|
|
47
|
+
# provider name -> why it failed, from the most recent initialization
|
|
48
|
+
_last_init_errors: Dict[str, str] = {}
|
|
47
49
|
|
|
48
50
|
@classmethod
|
|
49
51
|
def get_provider_class(cls, provider_name: str) -> Type[BaseProvider]:
|
|
@@ -447,14 +449,35 @@ class ProviderInitializer:
|
|
|
447
449
|
provider_configs: Dictionary of provider configurations
|
|
448
450
|
config_manager: Optional config manager for API key decryption
|
|
449
451
|
|
|
452
|
+
An entry with no model id is *incomplete*, not broken: environment
|
|
453
|
+
materialized entries (``OMNIMANCER_<PROVIDER>_API_KEY`` with no
|
|
454
|
+
``_MODEL``) are legitimate when the model comes from somewhere else,
|
|
455
|
+
such as an H2L ``--high <entry>:<model>`` ref. Those are skipped
|
|
456
|
+
without an error; real failures are recorded so callers can report
|
|
457
|
+
the actual cause instead of guessing at one.
|
|
458
|
+
|
|
450
459
|
Returns:
|
|
451
460
|
Dictionary of initialized provider instances
|
|
452
461
|
"""
|
|
453
462
|
from ..providers.factory import ProviderFactory
|
|
454
463
|
|
|
455
464
|
providers = {}
|
|
465
|
+
errors: Dict[str, str] = {}
|
|
456
466
|
|
|
457
467
|
for provider_name, config in provider_configs.items():
|
|
468
|
+
if not (getattr(config, "model", "") or "").strip():
|
|
469
|
+
env_var = (
|
|
470
|
+
"OMNIMANCER_" + provider_name.upper().replace("-", "_") + "_MODEL"
|
|
471
|
+
)
|
|
472
|
+
logger.info(
|
|
473
|
+
"Provider '%s' has no model configured; not started. "
|
|
474
|
+
"Set %s or '/config set-provider %s'. Model refs that "
|
|
475
|
+
"carry their own model (H2L tiers) still work.",
|
|
476
|
+
provider_name,
|
|
477
|
+
env_var,
|
|
478
|
+
provider_name,
|
|
479
|
+
)
|
|
480
|
+
continue
|
|
458
481
|
try:
|
|
459
482
|
# Use ProviderFactory instead of direct class loading
|
|
460
483
|
provider = ProviderFactory.create_provider(
|
|
@@ -463,12 +486,19 @@ class ProviderInitializer:
|
|
|
463
486
|
providers[provider_name] = provider
|
|
464
487
|
logger.info(f"Initialized provider: {provider_name}")
|
|
465
488
|
except Exception as e:
|
|
466
|
-
|
|
489
|
+
errors[provider_name] = str(e)
|
|
490
|
+
logger.error(f"Provider '{provider_name}' did not start: {e}")
|
|
467
491
|
# Continue with other providers instead of failing completely
|
|
468
492
|
continue
|
|
469
493
|
|
|
494
|
+
cls._last_init_errors = errors
|
|
470
495
|
return providers
|
|
471
496
|
|
|
497
|
+
@classmethod
|
|
498
|
+
def get_last_init_errors(cls) -> Dict[str, str]:
|
|
499
|
+
"""Why each provider failed during the most recent initialization."""
|
|
500
|
+
return dict(getattr(cls, "_last_init_errors", {}) or {})
|
|
501
|
+
|
|
472
502
|
@classmethod
|
|
473
503
|
def preload_model_info(cls, provider_names: List[str]) -> None:
|
|
474
504
|
"""
|
|
@@ -18,7 +18,17 @@ import logging
|
|
|
18
18
|
import uuid
|
|
19
19
|
from dataclasses import dataclass
|
|
20
20
|
from pathlib import Path
|
|
21
|
-
from typing import
|
|
21
|
+
from typing import (
|
|
22
|
+
Any,
|
|
23
|
+
Callable,
|
|
24
|
+
Dict,
|
|
25
|
+
Iterator,
|
|
26
|
+
List,
|
|
27
|
+
Optional,
|
|
28
|
+
Protocol,
|
|
29
|
+
Set,
|
|
30
|
+
Tuple,
|
|
31
|
+
)
|
|
22
32
|
|
|
23
33
|
from ..core.agent.tool_definitions import CODING_AGENT_TOOLS
|
|
24
34
|
from ..core.rate_limit_fallback import matches_rate_limit
|
|
@@ -50,6 +60,32 @@ _current_story: contextvars.ContextVar[str] = contextvars.ContextVar(
|
|
|
50
60
|
_TERMINAL_BAD = (StoryState.BLOCKED, StoryState.SKIPPED, StoryState.CANCELLED)
|
|
51
61
|
|
|
52
62
|
|
|
63
|
+
# Vendor wording for "this model cannot call tools" (Bedrock, Ollama,
|
|
64
|
+
# OpenRouter, OpenAI-compatible gateways).
|
|
65
|
+
_NO_TOOL_PATTERNS: Tuple[str, ...] = (
|
|
66
|
+
"support tool use",
|
|
67
|
+
"does not support tools",
|
|
68
|
+
"tool calling is not supported",
|
|
69
|
+
"tool use is not supported",
|
|
70
|
+
"does not support function calling",
|
|
71
|
+
"function calling is not supported",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def matches_no_tool_support(error: str) -> bool:
|
|
76
|
+
"""True when *error* says the model itself cannot call tools."""
|
|
77
|
+
lowered = error.lower()
|
|
78
|
+
return any(p in lowered for p in _NO_TOOL_PATTERNS)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Returned by ``_attempt`` when the model, not the story, failed: the attempt
|
|
82
|
+
# is retried on another model without spending a retry or consulting the judge.
|
|
83
|
+
_MODEL_DROPPED = Verdict(
|
|
84
|
+
story_id="", passed=False, failures=["model cannot call tools"]
|
|
85
|
+
)
|
|
86
|
+
_NO_LOW_MODEL = "no_tool_capable_low_model"
|
|
87
|
+
|
|
88
|
+
|
|
53
89
|
@dataclass
|
|
54
90
|
class PlanDecision:
|
|
55
91
|
accepted: bool
|
|
@@ -100,6 +136,8 @@ class H2LRun:
|
|
|
100
136
|
self._stop_cause = "done"
|
|
101
137
|
self._stopped = False
|
|
102
138
|
self._pool_index = 0
|
|
139
|
+
# Labels of low models that reported they cannot call tools.
|
|
140
|
+
self._dropped: Set[str] = set()
|
|
103
141
|
# Surfaces turn this off when Bash would only be denied (headless
|
|
104
142
|
# without --dangerously-skip-permissions).
|
|
105
143
|
self.planner_bash = True
|
|
@@ -299,30 +337,39 @@ class H2LRun:
|
|
|
299
337
|
feedback: Optional[str] = None
|
|
300
338
|
attempt = 0
|
|
301
339
|
while True:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
340
|
+
# None once every low model has been dropped for lacking tools;
|
|
341
|
+
# only the escalation policy is left then.
|
|
342
|
+
picked = self._next_ref()
|
|
343
|
+
if picked is not None:
|
|
344
|
+
attempt += 1
|
|
345
|
+
ref, provider = picked
|
|
346
|
+
verdict = await self._attempt(story, ref, provider, attempt, feedback)
|
|
347
|
+
if verdict is None:
|
|
348
|
+
return # cancelled or the run stopped
|
|
349
|
+
if verdict is _MODEL_DROPPED:
|
|
350
|
+
attempt -= 1 # the model failed, not the story
|
|
351
|
+
continue
|
|
352
|
+
if verdict.passed:
|
|
353
|
+
outcome.state = StoryState.PASSED
|
|
354
|
+
outcome.score = verdict.score
|
|
355
|
+
outcome.model = ref.label
|
|
356
|
+
return
|
|
357
|
+
feedback = verdict.feedback
|
|
358
|
+
if attempt < max_attempts:
|
|
359
|
+
continue
|
|
315
360
|
policy: str = self.config.escalation
|
|
316
361
|
if policy == "ask":
|
|
317
362
|
policy = await self.presenter.on_escalation(story, outcome)
|
|
318
|
-
if policy == "retry":
|
|
363
|
+
if policy == "retry" and picked is not None:
|
|
319
364
|
max_attempts += 1
|
|
320
365
|
continue
|
|
321
366
|
if policy == "high":
|
|
322
367
|
await self._escalate_high(story, outcome, attempt + 1, feedback)
|
|
323
368
|
return
|
|
324
369
|
outcome.state = StoryState.BLOCKED
|
|
325
|
-
outcome.reason =
|
|
370
|
+
outcome.reason = (
|
|
371
|
+
"retries_exhausted" if picked is not None else _NO_LOW_MODEL
|
|
372
|
+
)
|
|
326
373
|
return
|
|
327
374
|
|
|
328
375
|
async def _attempt(
|
|
@@ -372,6 +419,10 @@ class H2LRun:
|
|
|
372
419
|
outcome.state = StoryState.PENDING
|
|
373
420
|
outcome.attempts = attempt - 1
|
|
374
421
|
return None
|
|
422
|
+
if not report.success and matches_no_tool_support(report.error or ""):
|
|
423
|
+
outcome.attempts = attempt - 1
|
|
424
|
+
await self._drop_model(ref, report.error or "")
|
|
425
|
+
return _MODEL_DROPPED
|
|
375
426
|
|
|
376
427
|
with self._high_context():
|
|
377
428
|
verdict: Verdict = await judge.check(story, report)
|
|
@@ -451,13 +502,33 @@ class H2LRun:
|
|
|
451
502
|
|
|
452
503
|
return observe
|
|
453
504
|
|
|
454
|
-
def _next_ref(self) -> Tuple[H2LModelRef, Any]:
|
|
505
|
+
def _next_ref(self) -> Optional[Tuple[H2LModelRef, Any]]:
|
|
455
506
|
tiers = self._ensure_tiers()
|
|
456
|
-
pool =
|
|
507
|
+
pool = [
|
|
508
|
+
(ref, provider)
|
|
509
|
+
for ref, provider in zip(self.config.low, tiers.low)
|
|
510
|
+
if ref.label not in self._dropped
|
|
511
|
+
]
|
|
512
|
+
if not pool:
|
|
513
|
+
return None
|
|
457
514
|
ref, provider = pool[self._pool_index % len(pool)]
|
|
458
515
|
self._pool_index += 1
|
|
459
516
|
return ref, provider
|
|
460
517
|
|
|
518
|
+
async def _drop_model(self, ref: H2LModelRef, error: str) -> None:
|
|
519
|
+
"""Take a tool-less low model out of rotation; tell the surface once."""
|
|
520
|
+
if ref.label in self._dropped:
|
|
521
|
+
return
|
|
522
|
+
self._dropped.add(ref.label)
|
|
523
|
+
logger.info("H2L dropped %s, which cannot call tools: %s", ref.label, error)
|
|
524
|
+
hook = getattr(self.presenter, "on_model_dropped", None)
|
|
525
|
+
if hook is None:
|
|
526
|
+
return
|
|
527
|
+
try:
|
|
528
|
+
await hook(ref, error)
|
|
529
|
+
except Exception as exc: # presentation must never break the run
|
|
530
|
+
logger.debug("H2L on_model_dropped presenter hook failed: %s", exc)
|
|
531
|
+
|
|
461
532
|
def _add_usage(self, tier: str, usage: UsageTotals) -> None:
|
|
462
533
|
self.usage_by_tier[tier] = self.usage_by_tier[tier].add(usage)
|
|
463
534
|
|
|
@@ -9,6 +9,7 @@ carries the diff, verify output, and out-of-scope files the judge needs.
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
import difflib
|
|
12
13
|
import logging
|
|
13
14
|
import re
|
|
14
15
|
import subprocess
|
|
@@ -41,6 +42,8 @@ class WorkerRunner:
|
|
|
41
42
|
self.cwd = Path(cwd)
|
|
42
43
|
# Overridable for tests; the real handler routes through the gate.
|
|
43
44
|
self._tool_handler_factory: Callable[[Any], Any] = ToolHandler
|
|
45
|
+
# Story id -> {path: content or None}, taken before its first attempt.
|
|
46
|
+
self._baselines: Dict[str, Dict[str, Optional[str]]] = {}
|
|
44
47
|
|
|
45
48
|
async def run(
|
|
46
49
|
self,
|
|
@@ -102,6 +105,8 @@ class WorkerRunner:
|
|
|
102
105
|
)
|
|
103
106
|
|
|
104
107
|
agent_id = agent_id or f"h2l-{story.id}-{uuid.uuid4().hex[:8]}"
|
|
108
|
+
if story.id not in self._baselines:
|
|
109
|
+
self._baselines[story.id] = {p: _read_text(p) for p in story.files}
|
|
105
110
|
before = self._dirty_files()
|
|
106
111
|
status = 1
|
|
107
112
|
with fleet_events.agent_context(agent_id, run_id):
|
|
@@ -247,6 +252,9 @@ class WorkerRunner:
|
|
|
247
252
|
def _capture_changes(
|
|
248
253
|
self, report: WorkerReport, story: Story, before: Dict[str, str]
|
|
249
254
|
) -> None:
|
|
255
|
+
if self._repo_root() is None:
|
|
256
|
+
self._capture_from_baseline(report, story)
|
|
257
|
+
return
|
|
250
258
|
after = self._dirty_files()
|
|
251
259
|
scope = set(story.files)
|
|
252
260
|
new_or_changed = {p for p in after if p not in before or p in scope}
|
|
@@ -268,6 +276,48 @@ class WorkerRunner:
|
|
|
268
276
|
return f"[git diff unavailable for {path}]"
|
|
269
277
|
return proc.stdout
|
|
270
278
|
|
|
279
|
+
def _capture_from_baseline(self, report: WorkerReport, story: Story) -> None:
|
|
280
|
+
"""Without git, diff the story's files against its first-attempt snapshot.
|
|
281
|
+
|
|
282
|
+
The snapshot stands in for HEAD, so retries accumulate as they do under
|
|
283
|
+
git. Out-of-scope writes cannot be seen this way; the tool-level scope
|
|
284
|
+
check is the guard there.
|
|
285
|
+
"""
|
|
286
|
+
baseline = self._baselines.get(story.id, {})
|
|
287
|
+
changed: List[str] = []
|
|
288
|
+
chunks: List[str] = []
|
|
289
|
+
for path in story.files:
|
|
290
|
+
old, new = baseline.get(path), _read_text(path)
|
|
291
|
+
if old != new:
|
|
292
|
+
changed.append(path)
|
|
293
|
+
chunks.append(_unified_diff(path, old, new))
|
|
294
|
+
report.files_changed = changed
|
|
295
|
+
report.diff = "\n".join(chunks)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _read_text(path: str) -> Optional[str]:
|
|
299
|
+
try:
|
|
300
|
+
return Path(path).read_text(encoding="utf-8", errors="replace")
|
|
301
|
+
except OSError:
|
|
302
|
+
return None
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _unified_diff(path: str, old: Optional[str], new: Optional[str]) -> str:
|
|
306
|
+
fromfile = path if old is not None else "/dev/null"
|
|
307
|
+
tofile = path if new is not None else "/dev/null"
|
|
308
|
+
lines = list(
|
|
309
|
+
difflib.unified_diff(
|
|
310
|
+
(old or "").splitlines(),
|
|
311
|
+
(new or "").splitlines(),
|
|
312
|
+
fromfile=fromfile,
|
|
313
|
+
tofile=tofile,
|
|
314
|
+
lineterm="",
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
# An empty file created or removed has no hunks; keep the header so the
|
|
318
|
+
# change is still visible.
|
|
319
|
+
return "\n".join(lines or [f"--- {fromfile}", f"+++ {tofile}"])
|
|
320
|
+
|
|
271
321
|
|
|
272
322
|
def worker_tool_names(tools: List[ToolDefinition]) -> List[str]:
|
|
273
323
|
return [t.name for t in tools]
|
|
@@ -70,6 +70,14 @@ class BedrockProvider(BaseProvider):
|
|
|
70
70
|
# AWS-specific configuration
|
|
71
71
|
self.aws_region = kwargs.get("aws_region", "us-east-1")
|
|
72
72
|
|
|
73
|
+
# Chat-completion timeout. ProviderConfig's field is `timeout`;
|
|
74
|
+
# `request_timeout` is accepted as an alias, then
|
|
75
|
+
# OMNIMANCER_REQUEST_TIMEOUT, then DEFAULT_REQUEST_TIMEOUT. A long
|
|
76
|
+
# agent turn on a large model does not fit in the old hardcoded 30s.
|
|
77
|
+
self.request_timeout = self._resolve_request_timeout(
|
|
78
|
+
kwargs.get("request_timeout") or kwargs.get("timeout")
|
|
79
|
+
)
|
|
80
|
+
|
|
73
81
|
# Standard parameters. Bedrock fronts third-party models that reject
|
|
74
82
|
# temperature/topP in inferenceConfig outright, and nothing in
|
|
75
83
|
# Omnimancer lets a user pick these values, so an unset param stays
|
|
@@ -82,6 +90,21 @@ class BedrockProvider(BaseProvider):
|
|
|
82
90
|
# Build base URL for Bedrock API key authentication
|
|
83
91
|
self.base_url = f"https://bedrock-runtime.{self.aws_region}.amazonaws.com"
|
|
84
92
|
|
|
93
|
+
def _timeout_network_error(self) -> NetworkError:
|
|
94
|
+
"""The NetworkError raised when a Converse request times out."""
|
|
95
|
+
return NetworkError(
|
|
96
|
+
f"Request to AWS Bedrock API timed out after "
|
|
97
|
+
f"{self.request_timeout:.0f}s. Raise it with this provider's "
|
|
98
|
+
f"timeout (providers.<name>.timeout in config.json) or the "
|
|
99
|
+
f"OMNIMANCER_REQUEST_TIMEOUT environment variable."
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def _model_for_url(self) -> str:
|
|
103
|
+
"""The path segment for this model: ARNs carry an unusable prefix."""
|
|
104
|
+
if self._is_arn(self.model):
|
|
105
|
+
return self._extract_model_id_from_arn(self.model)
|
|
106
|
+
return self.model
|
|
107
|
+
|
|
85
108
|
def _is_arn(self, model_id: str) -> bool:
|
|
86
109
|
"""Check if the model ID is in ARN format."""
|
|
87
110
|
return model_id.startswith("arn:aws:bedrock:")
|
|
@@ -144,7 +167,7 @@ class BedrockProvider(BaseProvider):
|
|
|
144
167
|
request_body = self._prepare_bedrock_request(message, context)
|
|
145
168
|
|
|
146
169
|
# Build URL for the specific model
|
|
147
|
-
url = f"{self.base_url}/model/{self.
|
|
170
|
+
url = f"{self.base_url}/model/{self._model_for_url()}/converse"
|
|
148
171
|
|
|
149
172
|
# Create headers with Bearer token authentication
|
|
150
173
|
headers = {
|
|
@@ -155,14 +178,17 @@ class BedrockProvider(BaseProvider):
|
|
|
155
178
|
# Make API request
|
|
156
179
|
async with httpx.AsyncClient() as client:
|
|
157
180
|
response = await client.post(
|
|
158
|
-
url,
|
|
181
|
+
url,
|
|
182
|
+
headers=headers,
|
|
183
|
+
content=request_body,
|
|
184
|
+
timeout=self.request_timeout,
|
|
159
185
|
)
|
|
160
186
|
|
|
161
187
|
# Handle response
|
|
162
188
|
return self._handle_response(response)
|
|
163
189
|
|
|
164
190
|
except httpx.TimeoutException:
|
|
165
|
-
raise
|
|
191
|
+
raise self._timeout_network_error()
|
|
166
192
|
except httpx.RequestError as e:
|
|
167
193
|
raise NetworkError(f"Network error: {e}")
|
|
168
194
|
except Exception as e:
|
|
@@ -191,13 +217,8 @@ class BedrockProvider(BaseProvider):
|
|
|
191
217
|
message, context, available_tools
|
|
192
218
|
)
|
|
193
219
|
|
|
194
|
-
# Build URL -
|
|
195
|
-
|
|
196
|
-
# For ARN format, extract the actual model ID for the URL
|
|
197
|
-
model_for_url = self._extract_model_id_from_arn(self.model)
|
|
198
|
-
url = f"{self.base_url}/model" f"/{model_for_url}/converse"
|
|
199
|
-
else:
|
|
200
|
-
url = f"{self.base_url}/model" f"/{self.model}/converse"
|
|
220
|
+
# Build URL - an ARN model id is not usable as a path segment
|
|
221
|
+
url = f"{self.base_url}/model/{self._model_for_url()}/converse"
|
|
201
222
|
|
|
202
223
|
# Create headers with Bearer token authentication
|
|
203
224
|
headers = {
|
|
@@ -211,14 +232,14 @@ class BedrockProvider(BaseProvider):
|
|
|
211
232
|
url,
|
|
212
233
|
headers=headers,
|
|
213
234
|
content=request_body,
|
|
214
|
-
timeout=
|
|
235
|
+
timeout=self.request_timeout,
|
|
215
236
|
)
|
|
216
237
|
|
|
217
238
|
# Handle response with tool calls
|
|
218
239
|
return self._handle_response_with_tools(response)
|
|
219
240
|
|
|
220
241
|
except httpx.TimeoutException:
|
|
221
|
-
raise
|
|
242
|
+
raise self._timeout_network_error()
|
|
222
243
|
except httpx.RequestError as e:
|
|
223
244
|
raise NetworkError(f"Network error: {e}")
|
|
224
245
|
except Exception as e:
|