omnimancer-cli 0.3.4__tar.gz → 0.3.6__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.4 → omnimancer_cli-0.3.6}/PKG-INFO +1 -1
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/h2l_headless.py +17 -1
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/subagent.py +73 -22
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/engine.py +56 -25
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/env_loader.py +40 -5
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/prompt_enhancer.py +12 -11
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/provider_initializer.py +31 -1
- omnimancer_cli-0.3.6/omnimancer/core/provider_resolver.py +226 -0
- omnimancer_cli-0.3.6/omnimancer/h2l/refs.py +86 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/bedrock.py +33 -12
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer_cli.egg-info/PKG-INFO +1 -1
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer_cli.egg-info/SOURCES.txt +1 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/pyproject.toml +1 -1
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_bedrock_provider.py +103 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_env_loader.py +38 -1
- omnimancer_cli-0.3.4/omnimancer/h2l/refs.py +0 -135
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/LICENSE +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/README.md +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omn_fleet_hook.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/__init__.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/__main__.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/__init__.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/agent_loop.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/approval_formatter.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/approval_integration.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/approval_prompt.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/command_dispatch.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/commands.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/completion.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/display.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/dynamic_commands.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/file_mentions.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/h2l_command.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/handlers/__init__.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/headless.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/headless_checkpoint.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/interface.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/prompt_input.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/pt_completion.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/session.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/system_prompts.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/tool_handler.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/turn_notify.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/usage.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/__init__.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/__init__.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/approval_context.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/approval_interface.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/approval_manager.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/config.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/file_system_manager.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/program_executor.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/read_before_write_errors.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/status_core.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/status_manager.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/tool_definitions.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/types.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/web_client.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/workflow_orchestrator.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent_engine.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent_managers.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent_mode_manager.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/chat_manager.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/config_manager.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/config_migration.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/config_validator.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/conversation_manager.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/fallback_manager.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/health_monitor.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/history_manager.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/hooks.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/mcp_integration_layer.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/models.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/provider_capabilities.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/provider_registry.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/rate_limit_fallback.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/security/__init__.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/security/approval_workflow.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/security/audit_logger.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/security/permission_controller.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/security/permission_rules.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/security/sandbox_manager.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/security/security_manager.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/signal_handler.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/events/__init__.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/events/emitter.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/events/jsonl_writer.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/events/schema.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/__init__.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/judge.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/loop.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/models.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/orchestrator.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/planner.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/prompts.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/render.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/worker.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/mcp/__init__.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/mcp/client.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/mcp/manager.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/__init__.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/azure.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/base.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/cache_tokens.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/claude.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/claude_code.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/claude_credentials.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/cohere.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/digitalocean.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/factory.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/gemini.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/mistral.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/ollama.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/openai.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/openai_compatible.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/openrouter.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/perplexity.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/vertex.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/xai.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/tui/__init__.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/tui/fleet/__init__.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/tui/fleet/app.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/tui/fleet/cli.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/tui/fleet/models.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/tui/fleet/sources.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/tui/fleet/widgets.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/ui/__init__.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/ui/cancellation_handler.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/ui/progress_indicator.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/ui/streaming_display.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/ui/turn_activity.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/utils/__init__.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/utils/error_handler.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/utils/errors.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/utils/retry.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer_cli.egg-info/dependency_links.txt +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer_cli.egg-info/entry_points.txt +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer_cli.egg-info/requires.txt +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer_cli.egg-info/top_level.txt +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/setup.cfg +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_approval_integration_compatibility.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_approval_preview_format.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_approval_remember.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_approval_system.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_azure_provider.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_claude_code_provider.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_claude_credentials.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_claude_prompt_cache.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_claude_provider.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_cohere_provider.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_command_approval_security.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_config_comprehensive.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_config_migration_passthrough.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_conversation_history.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_conversation_manager.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_core_engine.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_digitalocean_provider.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_enhanced_model_info.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_error_handling_robustness.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_execution_timeout_errors.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_fallback_manager.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_fixtures.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_gemini_provider.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_hooks.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_httpx_compatibility.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_import_validation.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_interface_smoke_tests.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_mcp_integration.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_mcp_integration_layer.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_mcp_management_commands.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_mistral_provider.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_models.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_ollama_provider.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_openai_compatible_provider.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_openai_context_fit.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_openai_provider_tools.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_openai_request_timeout.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_openai_streaming.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_openrouter_provider.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_permission_rules.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_perplexity_provider.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_program_executor.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_provider_aliases.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_provider_base.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_provider_cache_tokens.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_provider_capability_consistency.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_provider_discovery.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_provider_integration.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_provider_registry.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_rate_limit_fallback.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_retry.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_security_validation.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_system_integration.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_system_prompts.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_tool_calling_integration.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_tool_handler.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_vertex_provider.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_web_request_markers.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_workflows_comprehensive.py +0 -0
- {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_xai_provider.py +0 -0
|
@@ -13,6 +13,7 @@ from dataclasses import dataclass, field
|
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from typing import Any, Dict, List, Optional
|
|
15
15
|
|
|
16
|
+
from ..core.env_loader import apply_env_overrides
|
|
16
17
|
from ..core.models import ChatResponse, Config
|
|
17
18
|
from ..h2l.models import (
|
|
18
19
|
H2LConfig,
|
|
@@ -48,13 +49,28 @@ class H2LOptions:
|
|
|
48
49
|
plan_iterations: Optional[int] = None
|
|
49
50
|
|
|
50
51
|
|
|
52
|
+
def _effective_default_provider(config: Config) -> str:
|
|
53
|
+
"""The default provider a bare ref should use.
|
|
54
|
+
|
|
55
|
+
Environment beats stored config everywhere else, so a bare ``--high
|
|
56
|
+
kimi-k2`` under OMNIMANCER_DEFAULT_PROVIDER=bedrock must resolve to
|
|
57
|
+
bedrock rather than to the stale entry on disk.
|
|
58
|
+
"""
|
|
59
|
+
try:
|
|
60
|
+
effective = apply_env_overrides(config)
|
|
61
|
+
except Exception as exc: # overrides must never block a run
|
|
62
|
+
logger.debug("H2L: env overrides unavailable: %s", exc)
|
|
63
|
+
effective = config
|
|
64
|
+
return getattr(effective, "default_provider", "") or ""
|
|
65
|
+
|
|
66
|
+
|
|
51
67
|
def resolve_h2l_config(config: Config, options: H2LOptions) -> H2LConfig:
|
|
52
68
|
"""Flags override the config block field by field; never persisted."""
|
|
53
69
|
base: Dict[str, Any] = {}
|
|
54
70
|
block = getattr(config, "h2l", None)
|
|
55
71
|
if block is not None:
|
|
56
72
|
base = block.model_dump()
|
|
57
|
-
default_provider =
|
|
73
|
+
default_provider = _effective_default_provider(config)
|
|
58
74
|
|
|
59
75
|
if options.high:
|
|
60
76
|
base["high"] = parse_model_ref(options.high, default_provider)
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
A subagent (see :class:`~omnimancer.core.models.SubAgentDefinition`) handles a
|
|
4
4
|
focused task with its own system prompt, a restricted tool allowlist, and an
|
|
5
|
-
optional model override. It runs an isolated loop
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
optional model override. It runs an isolated loop using its *own* conversation
|
|
6
|
+
context, so the parent conversation is never touched. A model override also
|
|
7
|
+
gets its own provider instance from
|
|
8
|
+
:mod:`omnimancer.core.provider_resolver` rather than reassigning the shared
|
|
9
|
+
session provider's model. This is the lightweight counterpart to Claude
|
|
10
|
+
Code's AgentTool.
|
|
8
11
|
"""
|
|
9
12
|
|
|
10
13
|
import logging
|
|
@@ -13,7 +16,12 @@ from dataclasses import dataclass, field
|
|
|
13
16
|
from typing import Any, List, Optional
|
|
14
17
|
|
|
15
18
|
from ..core.agent.status_core import EventType
|
|
16
|
-
from ..core.models import ChatContext, SubAgentDefinition
|
|
19
|
+
from ..core.models import ChatContext, ProviderConfig, SubAgentDefinition
|
|
20
|
+
from ..core.provider_resolver import (
|
|
21
|
+
ProviderResolutionError,
|
|
22
|
+
resolve_entry,
|
|
23
|
+
resolve_provider,
|
|
24
|
+
)
|
|
17
25
|
from ..events import emitter as fleet_events
|
|
18
26
|
from .tool_handler import ToolHandler
|
|
19
27
|
|
|
@@ -48,6 +56,50 @@ class SubAgentRunner:
|
|
|
48
56
|
allowed = set(definition.tools)
|
|
49
57
|
return [t for t in all_tools if t.name in allowed]
|
|
50
58
|
|
|
59
|
+
def _provider_name(self, provider: Any) -> str:
|
|
60
|
+
"""The config-entry name for the running provider, if we can tell."""
|
|
61
|
+
providers = getattr(self.engine, "providers", None)
|
|
62
|
+
if isinstance(providers, dict):
|
|
63
|
+
for name, instance in providers.items():
|
|
64
|
+
if instance is provider:
|
|
65
|
+
return str(name)
|
|
66
|
+
getter = getattr(provider, "get_provider_name", None)
|
|
67
|
+
if callable(getter):
|
|
68
|
+
raw_name = getter()
|
|
69
|
+
if isinstance(raw_name, str):
|
|
70
|
+
return str(raw_name)
|
|
71
|
+
return ""
|
|
72
|
+
|
|
73
|
+
def _isolated_provider(self, provider_name: str, model: str) -> Optional[Any]:
|
|
74
|
+
"""A provider instance of our own for a model override.
|
|
75
|
+
|
|
76
|
+
Returns None when the entry cannot be resolved, so the caller falls
|
|
77
|
+
back to the legacy mutate-and-restore behaviour rather than failing
|
|
78
|
+
a subagent over a provider-resolution detail.
|
|
79
|
+
"""
|
|
80
|
+
config_manager = getattr(self.engine, "config_manager", None)
|
|
81
|
+
if not provider_name or config_manager is None:
|
|
82
|
+
return None
|
|
83
|
+
try:
|
|
84
|
+
# Only a real entry is safe to build from: anything else means we
|
|
85
|
+
# cannot tell what we would be constructing.
|
|
86
|
+
entry = resolve_entry(provider_name, config_manager, self.engine, model)
|
|
87
|
+
if not isinstance(entry, ProviderConfig):
|
|
88
|
+
return None
|
|
89
|
+
return resolve_provider(provider_name, config_manager, model, self.engine)
|
|
90
|
+
except ProviderResolutionError as exc:
|
|
91
|
+
logger.debug(
|
|
92
|
+
"Subagent model override falling back to the shared provider: %s",
|
|
93
|
+
exc,
|
|
94
|
+
)
|
|
95
|
+
return None
|
|
96
|
+
except Exception as exc:
|
|
97
|
+
logger.debug(
|
|
98
|
+
"Subagent model override falling back to the shared provider: %s",
|
|
99
|
+
exc,
|
|
100
|
+
)
|
|
101
|
+
return None
|
|
102
|
+
|
|
51
103
|
async def run(self, definition: SubAgentDefinition, task: str) -> SubAgentResult:
|
|
52
104
|
"""Execute the subagent's isolated tool-calling loop for ``task``."""
|
|
53
105
|
provider = getattr(self.engine, "current_provider", None)
|
|
@@ -62,26 +114,24 @@ class SubAgentRunner:
|
|
|
62
114
|
tool_handler = ToolHandler(agent_engine)
|
|
63
115
|
tools = self._scoped_tools(tool_handler, definition)
|
|
64
116
|
|
|
117
|
+
# Identity is read before any override so the entry name is the
|
|
118
|
+
# session's, not something derived from a swapped-in instance.
|
|
119
|
+
provider_name = self._provider_name(provider)
|
|
120
|
+
|
|
121
|
+
shared_provider = provider
|
|
65
122
|
original_model = getattr(provider, "model", None)
|
|
123
|
+
restore_model = False
|
|
66
124
|
if definition.model:
|
|
67
|
-
|
|
125
|
+
isolated = self._isolated_provider(provider_name, definition.model)
|
|
126
|
+
if isolated is not None:
|
|
127
|
+
provider = isolated
|
|
128
|
+
else:
|
|
129
|
+
# Last resort: the shared object, restored in the finally.
|
|
130
|
+
provider.model = definition.model
|
|
131
|
+
restore_model = True
|
|
68
132
|
|
|
69
|
-
# Compute the provider/model identity for fleet lifecycle events.
|
|
70
133
|
raw_model = getattr(provider, "model", "")
|
|
71
134
|
model_used: str = raw_model if isinstance(raw_model, str) else ""
|
|
72
|
-
provider_name = ""
|
|
73
|
-
providers = getattr(self.engine, "providers", None)
|
|
74
|
-
if isinstance(providers, dict):
|
|
75
|
-
for name, instance in providers.items():
|
|
76
|
-
if instance is provider:
|
|
77
|
-
provider_name = name
|
|
78
|
-
break
|
|
79
|
-
if not provider_name:
|
|
80
|
-
getter = getattr(provider, "get_provider_name", None)
|
|
81
|
-
if callable(getter):
|
|
82
|
-
raw_name = getter()
|
|
83
|
-
if isinstance(raw_name, str):
|
|
84
|
-
provider_name = raw_name
|
|
85
135
|
|
|
86
136
|
# Isolated context — the parent conversation is never modified. The
|
|
87
137
|
# run id is unique per invocation so two runs of the same subagent
|
|
@@ -164,9 +214,10 @@ class SubAgentRunner:
|
|
|
164
214
|
finally:
|
|
165
215
|
# Restore the shared provider BEFORE any await: a cancellation
|
|
166
216
|
# during the emit must never leave the parent conversation
|
|
167
|
-
# running on the subagent's model.
|
|
168
|
-
|
|
169
|
-
|
|
217
|
+
# running on the subagent's model. Only the fallback path
|
|
218
|
+
# touched it; a resolved instance leaves it alone entirely.
|
|
219
|
+
if restore_model and original_model is not None:
|
|
220
|
+
shared_provider.model = original_model
|
|
170
221
|
try:
|
|
171
222
|
await fleet_events.emit_event(
|
|
172
223
|
EventType.SESSION_END,
|
|
@@ -136,17 +136,13 @@ class CoreEngine:
|
|
|
136
136
|
async def initialize_providers(self) -> None:
|
|
137
137
|
"""Initialize all configured providers."""
|
|
138
138
|
try:
|
|
139
|
-
config
|
|
139
|
+
# The effective configuration: stored config plus ephemeral
|
|
140
|
+
# environment overrides (API keys, base_url, model, default
|
|
141
|
+
# provider), never persisted. Every other surface resolves
|
|
142
|
+
# against this same view via core.provider_resolver.
|
|
143
|
+
from .provider_resolver import effective_config
|
|
140
144
|
|
|
141
|
-
|
|
142
|
-
# base_url, model, default provider). Never persisted to disk.
|
|
143
|
-
# Overrides must never prevent startup, so fall back on any error.
|
|
144
|
-
try:
|
|
145
|
-
from .env_loader import apply_env_overrides
|
|
146
|
-
|
|
147
|
-
config = apply_env_overrides(config)
|
|
148
|
-
except Exception as e:
|
|
149
|
-
logger.warning(f"Skipping environment overrides: {e}")
|
|
145
|
+
config = effective_config(self.config_manager)
|
|
150
146
|
|
|
151
147
|
# Use the optimized provider initializer with
|
|
152
148
|
# config_manager for API key decryption
|
|
@@ -186,11 +182,12 @@ class CoreEngine:
|
|
|
186
182
|
# DigitalOcean request). Leave it unset so the user gets a
|
|
187
183
|
# clear error naming the provider.
|
|
188
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.
|
|
189
187
|
logger.error(
|
|
190
|
-
"Default provider '%s' is
|
|
191
|
-
"initialize (missing or invalid API key?). Not falling "
|
|
192
|
-
"back to another provider.",
|
|
188
|
+
"Default provider '%s' is not available: %s",
|
|
193
189
|
config.default_provider,
|
|
190
|
+
self._no_provider_error(),
|
|
194
191
|
)
|
|
195
192
|
elif self.providers:
|
|
196
193
|
# No default configured — use first available provider.
|
|
@@ -232,11 +229,13 @@ class CoreEngine:
|
|
|
232
229
|
break
|
|
233
230
|
if not provider_name or not model:
|
|
234
231
|
try:
|
|
235
|
-
|
|
232
|
+
from .provider_resolver import effective_config
|
|
233
|
+
|
|
234
|
+
config = effective_config(self.config_manager)
|
|
236
235
|
if not provider_name:
|
|
237
236
|
provider_name = config.default_provider or "unknown"
|
|
238
237
|
if not model:
|
|
239
|
-
entry = config.providers.get(config.default_provider)
|
|
238
|
+
entry = (config.providers or {}).get(config.default_provider)
|
|
240
239
|
raw = getattr(entry, "model", "")
|
|
241
240
|
model = raw if isinstance(raw, str) else ""
|
|
242
241
|
except Exception as e: # identity is best-effort diagnostics
|
|
@@ -308,14 +307,44 @@ class CoreEngine:
|
|
|
308
307
|
missing, rather than a generic "no provider" message.
|
|
309
308
|
"""
|
|
310
309
|
unavailable = getattr(self, "_unavailable_default_provider", None)
|
|
311
|
-
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:
|
|
312
323
|
return (
|
|
313
|
-
f"Provider '{unavailable}' is selected
|
|
314
|
-
f"
|
|
315
|
-
f"
|
|
316
|
-
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'."
|
|
317
329
|
)
|
|
318
|
-
|
|
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
|
+
)
|
|
319
348
|
|
|
320
349
|
# ------------------------------------------------------------------
|
|
321
350
|
# Fallback wiring
|
|
@@ -945,11 +974,13 @@ class CoreEngine:
|
|
|
945
974
|
Dictionary with health status information
|
|
946
975
|
"""
|
|
947
976
|
try:
|
|
948
|
-
|
|
977
|
+
from .provider_resolver import effective_providers
|
|
978
|
+
|
|
979
|
+
providers = effective_providers(self.config_manager)
|
|
949
980
|
|
|
950
981
|
if provider_name:
|
|
951
982
|
# Check specific provider
|
|
952
|
-
if provider_name not in
|
|
983
|
+
if provider_name not in providers:
|
|
953
984
|
return {
|
|
954
985
|
provider_name: {
|
|
955
986
|
"status": "error",
|
|
@@ -959,7 +990,7 @@ class CoreEngine:
|
|
|
959
990
|
}
|
|
960
991
|
}
|
|
961
992
|
|
|
962
|
-
provider_config =
|
|
993
|
+
provider_config = providers[provider_name]
|
|
963
994
|
status = await self.health_monitor.check_provider_health(
|
|
964
995
|
provider_name, provider_config, force=force
|
|
965
996
|
)
|
|
@@ -967,7 +998,7 @@ class CoreEngine:
|
|
|
967
998
|
else:
|
|
968
999
|
# Check all providers
|
|
969
1000
|
return await self.health_monitor.check_all_providers_health(
|
|
970
|
-
|
|
1001
|
+
providers, force=force
|
|
971
1002
|
)
|
|
972
1003
|
|
|
973
1004
|
except Exception as e:
|
|
@@ -7,13 +7,16 @@ and inject them into provider configurations.
|
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
9
|
import os
|
|
10
|
-
from typing import TYPE_CHECKING, Dict, Optional
|
|
10
|
+
from typing import TYPE_CHECKING, Dict, Optional, Set
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
from .models import Config
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
17
|
+
# Namespaced env-var suffixes recognized for every provider.
|
|
18
|
+
ENV_VAR_SUFFIXES = ("API_KEY", "BASE_URL", "MODEL")
|
|
19
|
+
|
|
17
20
|
# Mapping of provider names to (conventional) environment variable names for API keys.
|
|
18
21
|
ENV_VAR_MAPPING = {
|
|
19
22
|
"claude": "ANTHROPIC_API_KEY",
|
|
@@ -37,6 +40,31 @@ def _omnimancer_env_prefix(provider_name: str) -> str:
|
|
|
37
40
|
return "OMNIMANCER_" + provider_name.upper().replace("-", "_")
|
|
38
41
|
|
|
39
42
|
|
|
43
|
+
def env_configured_provider_names() -> Set[str]:
|
|
44
|
+
"""Registered provider names that have OMNIMANCER_<PROVIDER>_* vars set.
|
|
45
|
+
|
|
46
|
+
``ENV_VAR_MAPPING`` only lists providers that have a conventional
|
|
47
|
+
single-key variable, so without this a namespaced key for anything else
|
|
48
|
+
(bedrock, vertex, openai-compatible) was never even considered as a
|
|
49
|
+
candidate and was silently ignored. The registry is the source of names so
|
|
50
|
+
the underscore-to-hyphen mapping stays unambiguous.
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
from ..providers.factory import ProviderFactory
|
|
54
|
+
|
|
55
|
+
registered = set(ProviderFactory.get_available_providers())
|
|
56
|
+
except Exception as exc: # discovery must never block config loading
|
|
57
|
+
logger.debug(f"Provider registry unavailable for env discovery: {exc}")
|
|
58
|
+
return set()
|
|
59
|
+
|
|
60
|
+
names: Set[str] = set()
|
|
61
|
+
for name in registered:
|
|
62
|
+
prefix = _omnimancer_env_prefix(name)
|
|
63
|
+
if any(os.environ.get(f"{prefix}_{suffix}") for suffix in ENV_VAR_SUFFIXES):
|
|
64
|
+
names.add(name)
|
|
65
|
+
return names
|
|
66
|
+
|
|
67
|
+
|
|
40
68
|
def load_provider_env_overrides(provider_name: str) -> Dict[str, str]:
|
|
41
69
|
"""
|
|
42
70
|
Collect per-provider overrides from environment variables.
|
|
@@ -176,8 +204,13 @@ def apply_env_overrides(config: "Config") -> "Config":
|
|
|
176
204
|
effective = config.model_copy(deep=True)
|
|
177
205
|
|
|
178
206
|
# Consider providers already configured, those with a recognized API-key
|
|
179
|
-
# env var, plus any provider
|
|
180
|
-
|
|
207
|
+
# env var or a namespaced OMNIMANCER_<PROVIDER>_* var, plus any provider
|
|
208
|
+
# named by OMNIMANCER_DEFAULT_PROVIDER.
|
|
209
|
+
candidates = (
|
|
210
|
+
set(effective.providers.keys())
|
|
211
|
+
| set(ENV_VAR_MAPPING.keys())
|
|
212
|
+
| env_configured_provider_names()
|
|
213
|
+
)
|
|
181
214
|
default_override = os.environ.get(DEFAULT_PROVIDER_ENV_VAR)
|
|
182
215
|
if default_override:
|
|
183
216
|
candidates.add(default_override)
|
|
@@ -197,7 +230,7 @@ def apply_env_overrides(config: "Config") -> "Config":
|
|
|
197
230
|
model=overrides.get("model", ""),
|
|
198
231
|
)
|
|
199
232
|
effective.providers[name] = provider_config
|
|
200
|
-
logger.
|
|
233
|
+
logger.debug(f"Created provider '{name}' from environment overrides")
|
|
201
234
|
|
|
202
235
|
if "api_key" in overrides:
|
|
203
236
|
provider_config.api_key = overrides["api_key"]
|
|
@@ -208,6 +241,8 @@ def apply_env_overrides(config: "Config") -> "Config":
|
|
|
208
241
|
|
|
209
242
|
if default_override:
|
|
210
243
|
effective.default_provider = default_override
|
|
211
|
-
logger.
|
|
244
|
+
logger.debug(
|
|
245
|
+
f"Default provider overridden from environment: {default_override}"
|
|
246
|
+
)
|
|
212
247
|
|
|
213
248
|
return effective
|
|
@@ -264,23 +264,24 @@ async def enhance(
|
|
|
264
264
|
if fallback_model is not None and fallback_model not in candidates:
|
|
265
265
|
candidates.append(fallback_model)
|
|
266
266
|
|
|
267
|
-
from
|
|
267
|
+
from .provider_resolver import ProviderResolutionError, resolve_provider
|
|
268
268
|
|
|
269
269
|
for provider_name, model in candidates:
|
|
270
|
-
|
|
271
|
-
|
|
270
|
+
try:
|
|
271
|
+
# The shared resolver so an entry materialized from
|
|
272
|
+
# OMNIMANCER_<PROVIDER>_* is as usable here as in the engine.
|
|
273
|
+
provider = resolve_provider(
|
|
274
|
+
provider_name,
|
|
275
|
+
config_manager,
|
|
276
|
+
model,
|
|
277
|
+
overrides={"temperature": temperature},
|
|
278
|
+
)
|
|
279
|
+
except ProviderResolutionError as exc:
|
|
272
280
|
logger.warning(
|
|
273
|
-
"Enhancement provider %r
|
|
281
|
+
"Enhancement provider %r unusable: %s", provider_name, exc
|
|
274
282
|
)
|
|
275
283
|
continue
|
|
276
284
|
try:
|
|
277
|
-
effective = provider_config.model_copy(
|
|
278
|
-
update={"model": model, "temperature": temperature}
|
|
279
|
-
)
|
|
280
|
-
provider = ProviderFactory.create_provider(
|
|
281
|
-
provider_name, effective, config_manager
|
|
282
|
-
)
|
|
283
|
-
|
|
284
285
|
# Isolated context: the session conversation never leaks
|
|
285
286
|
# into the rewrite, and the rewrite never lands in session
|
|
286
287
|
# history.
|
|
@@ -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
|
"""
|