omnimancer-cli 0.3.4__tar.gz → 0.3.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/PKG-INFO +1 -1
  2. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/h2l_headless.py +17 -1
  3. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/subagent.py +73 -22
  4. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/engine.py +16 -16
  5. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/env_loader.py +36 -3
  6. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/prompt_enhancer.py +12 -11
  7. omnimancer_cli-0.3.5/omnimancer/core/provider_resolver.py +226 -0
  8. omnimancer_cli-0.3.5/omnimancer/h2l/refs.py +86 -0
  9. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer_cli.egg-info/PKG-INFO +1 -1
  10. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer_cli.egg-info/SOURCES.txt +1 -0
  11. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/pyproject.toml +1 -1
  12. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_env_loader.py +38 -1
  13. omnimancer_cli-0.3.4/omnimancer/h2l/refs.py +0 -135
  14. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/LICENSE +0 -0
  15. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/README.md +0 -0
  16. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omn_fleet_hook.py +0 -0
  17. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/__init__.py +0 -0
  18. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/__main__.py +0 -0
  19. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/__init__.py +0 -0
  20. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/agent_loop.py +0 -0
  21. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/approval_formatter.py +0 -0
  22. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/approval_integration.py +0 -0
  23. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/approval_prompt.py +0 -0
  24. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/command_dispatch.py +0 -0
  25. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/commands.py +0 -0
  26. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/completion.py +0 -0
  27. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/display.py +0 -0
  28. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/dynamic_commands.py +0 -0
  29. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/file_mentions.py +0 -0
  30. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/h2l_command.py +0 -0
  31. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/handlers/__init__.py +0 -0
  32. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/headless.py +0 -0
  33. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/headless_checkpoint.py +0 -0
  34. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/interface.py +0 -0
  35. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/prompt_input.py +0 -0
  36. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/pt_completion.py +0 -0
  37. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/session.py +0 -0
  38. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/system_prompts.py +0 -0
  39. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/tool_handler.py +0 -0
  40. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/turn_notify.py +0 -0
  41. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/cli/usage.py +0 -0
  42. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/__init__.py +0 -0
  43. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent/__init__.py +0 -0
  44. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent/approval_context.py +0 -0
  45. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent/approval_interface.py +0 -0
  46. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent/approval_manager.py +0 -0
  47. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent/config.py +0 -0
  48. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent/file_system_manager.py +0 -0
  49. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent/program_executor.py +0 -0
  50. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent/read_before_write_errors.py +0 -0
  51. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent/status_core.py +0 -0
  52. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent/status_manager.py +0 -0
  53. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent/tool_definitions.py +0 -0
  54. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent/types.py +0 -0
  55. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent/web_client.py +0 -0
  56. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent/workflow_orchestrator.py +0 -0
  57. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent_engine.py +0 -0
  58. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent_managers.py +0 -0
  59. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/agent_mode_manager.py +0 -0
  60. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/chat_manager.py +0 -0
  61. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/config_manager.py +0 -0
  62. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/config_migration.py +0 -0
  63. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/config_validator.py +0 -0
  64. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/conversation_manager.py +0 -0
  65. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/fallback_manager.py +0 -0
  66. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/health_monitor.py +0 -0
  67. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/history_manager.py +0 -0
  68. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/hooks.py +0 -0
  69. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/mcp_integration_layer.py +0 -0
  70. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/models.py +0 -0
  71. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/provider_capabilities.py +0 -0
  72. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/provider_initializer.py +0 -0
  73. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/provider_registry.py +0 -0
  74. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/rate_limit_fallback.py +0 -0
  75. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/security/__init__.py +0 -0
  76. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/security/approval_workflow.py +0 -0
  77. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/security/audit_logger.py +0 -0
  78. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/security/permission_controller.py +0 -0
  79. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/security/permission_rules.py +0 -0
  80. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/security/sandbox_manager.py +0 -0
  81. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/security/security_manager.py +0 -0
  82. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/core/signal_handler.py +0 -0
  83. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/events/__init__.py +0 -0
  84. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/events/emitter.py +0 -0
  85. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/events/jsonl_writer.py +0 -0
  86. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/events/schema.py +0 -0
  87. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/h2l/__init__.py +0 -0
  88. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/h2l/judge.py +0 -0
  89. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/h2l/loop.py +0 -0
  90. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/h2l/models.py +0 -0
  91. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/h2l/orchestrator.py +0 -0
  92. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/h2l/planner.py +0 -0
  93. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/h2l/prompts.py +0 -0
  94. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/h2l/render.py +0 -0
  95. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/h2l/worker.py +0 -0
  96. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/mcp/__init__.py +0 -0
  97. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/mcp/client.py +0 -0
  98. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/mcp/manager.py +0 -0
  99. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/__init__.py +0 -0
  100. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/azure.py +0 -0
  101. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/base.py +0 -0
  102. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/bedrock.py +0 -0
  103. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/cache_tokens.py +0 -0
  104. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/claude.py +0 -0
  105. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/claude_code.py +0 -0
  106. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/claude_credentials.py +0 -0
  107. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/cohere.py +0 -0
  108. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/digitalocean.py +0 -0
  109. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/factory.py +0 -0
  110. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/gemini.py +0 -0
  111. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/mistral.py +0 -0
  112. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/ollama.py +0 -0
  113. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/openai.py +0 -0
  114. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/openai_compatible.py +0 -0
  115. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/openrouter.py +0 -0
  116. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/perplexity.py +0 -0
  117. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/vertex.py +0 -0
  118. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/providers/xai.py +0 -0
  119. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/tui/__init__.py +0 -0
  120. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/tui/fleet/__init__.py +0 -0
  121. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/tui/fleet/app.py +0 -0
  122. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/tui/fleet/cli.py +0 -0
  123. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/tui/fleet/models.py +0 -0
  124. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/tui/fleet/sources.py +0 -0
  125. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/tui/fleet/widgets.py +0 -0
  126. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/ui/__init__.py +0 -0
  127. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/ui/cancellation_handler.py +0 -0
  128. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/ui/progress_indicator.py +0 -0
  129. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/ui/streaming_display.py +0 -0
  130. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/ui/turn_activity.py +0 -0
  131. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/utils/__init__.py +0 -0
  132. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/utils/error_handler.py +0 -0
  133. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/utils/errors.py +0 -0
  134. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer/utils/retry.py +0 -0
  135. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer_cli.egg-info/dependency_links.txt +0 -0
  136. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer_cli.egg-info/entry_points.txt +0 -0
  137. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer_cli.egg-info/requires.txt +0 -0
  138. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/omnimancer_cli.egg-info/top_level.txt +0 -0
  139. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/setup.cfg +0 -0
  140. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_approval_integration_compatibility.py +0 -0
  141. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_approval_preview_format.py +0 -0
  142. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_approval_remember.py +0 -0
  143. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_approval_system.py +0 -0
  144. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_azure_provider.py +0 -0
  145. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_bedrock_provider.py +0 -0
  146. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_claude_code_provider.py +0 -0
  147. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_claude_credentials.py +0 -0
  148. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_claude_prompt_cache.py +0 -0
  149. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_claude_provider.py +0 -0
  150. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_cohere_provider.py +0 -0
  151. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_command_approval_security.py +0 -0
  152. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_config_comprehensive.py +0 -0
  153. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_config_migration_passthrough.py +0 -0
  154. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_conversation_history.py +0 -0
  155. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_conversation_manager.py +0 -0
  156. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_core_engine.py +0 -0
  157. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_digitalocean_provider.py +0 -0
  158. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_enhanced_model_info.py +0 -0
  159. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_error_handling_robustness.py +0 -0
  160. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_execution_timeout_errors.py +0 -0
  161. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_fallback_manager.py +0 -0
  162. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_fixtures.py +0 -0
  163. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_gemini_provider.py +0 -0
  164. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_hooks.py +0 -0
  165. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_httpx_compatibility.py +0 -0
  166. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_import_validation.py +0 -0
  167. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_interface_smoke_tests.py +0 -0
  168. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_mcp_integration.py +0 -0
  169. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_mcp_integration_layer.py +0 -0
  170. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_mcp_management_commands.py +0 -0
  171. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_mistral_provider.py +0 -0
  172. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_models.py +0 -0
  173. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_ollama_provider.py +0 -0
  174. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_openai_compatible_provider.py +0 -0
  175. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_openai_context_fit.py +0 -0
  176. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_openai_provider_tools.py +0 -0
  177. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_openai_request_timeout.py +0 -0
  178. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_openai_streaming.py +0 -0
  179. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_openrouter_provider.py +0 -0
  180. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_permission_rules.py +0 -0
  181. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_perplexity_provider.py +0 -0
  182. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_program_executor.py +0 -0
  183. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_provider_aliases.py +0 -0
  184. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_provider_base.py +0 -0
  185. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_provider_cache_tokens.py +0 -0
  186. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_provider_capability_consistency.py +0 -0
  187. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_provider_discovery.py +0 -0
  188. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_provider_integration.py +0 -0
  189. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_provider_registry.py +0 -0
  190. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_rate_limit_fallback.py +0 -0
  191. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_retry.py +0 -0
  192. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_security_validation.py +0 -0
  193. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_system_integration.py +0 -0
  194. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_system_prompts.py +0 -0
  195. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_tool_calling_integration.py +0 -0
  196. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_tool_handler.py +0 -0
  197. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_vertex_provider.py +0 -0
  198. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_web_request_markers.py +0 -0
  199. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_workflows_comprehensive.py +0 -0
  200. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.5}/tests/test_xai_provider.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omnimancer-cli
3
- Version: 0.3.4
3
+ Version: 0.3.5
4
4
  Summary: A unified command-line interface for multiple AI language models
5
5
  Author-email: Kellan Strong <kellan@jite.ai>
6
6
  License: MIT
@@ -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 = getattr(config, "default_provider", "") or ""
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 against the current provider
6
- using its *own* conversation context, so the parent conversation is never
7
- touched. This is the lightweight counterpart to Claude Code's AgentTool.
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
- provider.model = definition.model
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
- if definition.model and original_model is not None:
169
- provider.model = original_model
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 = self.config_manager.get_config()
140
-
141
- # Apply ephemeral environment-variable overrides (API keys,
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
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
146
144
 
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
@@ -232,11 +228,13 @@ class CoreEngine:
232
228
  break
233
229
  if not provider_name or not model:
234
230
  try:
235
- config = self.config_manager.get_config()
231
+ from .provider_resolver import effective_config
232
+
233
+ config = effective_config(self.config_manager)
236
234
  if not provider_name:
237
235
  provider_name = config.default_provider or "unknown"
238
236
  if not model:
239
- entry = config.providers.get(config.default_provider)
237
+ entry = (config.providers or {}).get(config.default_provider)
240
238
  raw = getattr(entry, "model", "")
241
239
  model = raw if isinstance(raw, str) else ""
242
240
  except Exception as e: # identity is best-effort diagnostics
@@ -945,11 +943,13 @@ class CoreEngine:
945
943
  Dictionary with health status information
946
944
  """
947
945
  try:
948
- config = self.config_manager.get_config()
946
+ from .provider_resolver import effective_providers
947
+
948
+ providers = effective_providers(self.config_manager)
949
949
 
950
950
  if provider_name:
951
951
  # Check specific provider
952
- if provider_name not in config.providers:
952
+ if provider_name not in providers:
953
953
  return {
954
954
  provider_name: {
955
955
  "status": "error",
@@ -959,7 +959,7 @@ class CoreEngine:
959
959
  }
960
960
  }
961
961
 
962
- provider_config = config.providers[provider_name]
962
+ provider_config = providers[provider_name]
963
963
  status = await self.health_monitor.check_provider_health(
964
964
  provider_name, provider_config, force=force
965
965
  )
@@ -967,7 +967,7 @@ class CoreEngine:
967
967
  else:
968
968
  # Check all providers
969
969
  return await self.health_monitor.check_all_providers_health(
970
- config.providers, force=force
970
+ providers, force=force
971
971
  )
972
972
 
973
973
  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 named by OMNIMANCER_DEFAULT_PROVIDER.
180
- candidates = set(effective.providers.keys()) | set(ENV_VAR_MAPPING.keys())
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)
@@ -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 ..providers.factory import ProviderFactory
267
+ from .provider_resolver import ProviderResolutionError, resolve_provider
268
268
 
269
269
  for provider_name, model in candidates:
270
- provider_config = config.providers.get(provider_name)
271
- if provider_config is None:
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 is not configured", provider_name
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.
@@ -0,0 +1,226 @@
1
+ """The single answer to "which provider entry is in force, and give me one".
2
+
3
+ Every surface needs the same two things: the *effective* configuration
4
+ (stored config with environment overrides applied, in that precedence) and an
5
+ isolated provider instance built from one entry of it. Before this module each
6
+ caller re-derived that independently: ``CoreEngine.initialize_providers``
7
+ applied the overrides to a local variable and threw it away, H2L re-applied
8
+ them per model ref, the prompt enhancer read the stored config and so never
9
+ saw them, and subagents skipped resolution entirely and mutated the shared
10
+ session provider's model. A provider configured purely through
11
+ ``OMNIMANCER_<PROVIDER>_API_KEY`` therefore existed for the engine and for
12
+ nobody else.
13
+
14
+ Two rules hold for everything here:
15
+
16
+ * **Nothing is ever persisted.** :func:`effective_config` returns a deep copy
17
+ carrying ephemeral environment values; saving it would write env secrets
18
+ into ``~/.omnimancer/config.json``. Writers keep using
19
+ ``ConfigManager.get_config()``.
20
+ * **Never hand out an object from ``engine.providers``.** Each resolution
21
+ builds its own instance from a ``model_copy`` of the entry, so parallel
22
+ work (H2L tiers, subagents) cannot interleave through a shared provider.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import logging
28
+ from typing import Any, Dict, Optional, Union
29
+
30
+ from ..providers.factory import ProviderFactory
31
+ from .env_loader import apply_env_overrides
32
+ from .models import Config, ProviderConfig
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+ # Either a ConfigManager or an already-loaded Config is accepted, because the
37
+ # callers have one or the other at hand and converting is this module's job.
38
+ ConfigSource = Union[Config, Any]
39
+
40
+
41
+ class ProviderResolutionError(ValueError):
42
+ """Base class: a provider entry could not be turned into an instance."""
43
+
44
+
45
+ class EntryNotConfigured(ProviderResolutionError):
46
+ """No config entry, no environment entry, and no live instance."""
47
+
48
+ def __init__(self, provider: str) -> None:
49
+ self.provider = provider
50
+ self.env_var = "OMNIMANCER_" + provider.upper().replace("-", "_") + "_API_KEY"
51
+ super().__init__(
52
+ f"provider entry '{provider}' is not configured "
53
+ f"(see /providers, or set {self.env_var})"
54
+ )
55
+
56
+
57
+ class ProviderInitFailed(ProviderResolutionError):
58
+ """The factory could not build the provider (bad credentials, bad model)."""
59
+
60
+ def __init__(self, provider: str, model: str, cause: Exception) -> None:
61
+ self.provider = provider
62
+ self.model = model
63
+ self.cause = cause
64
+ super().__init__(f"could not initialize {provider}:{model}: {cause}")
65
+
66
+
67
+ class ToolsUnsupported(ProviderResolutionError):
68
+ """The caller required native tool calling and this provider has none."""
69
+
70
+ def __init__(self, provider: str) -> None:
71
+ self.provider = provider
72
+ super().__init__(f"provider '{provider}' does not support tool calling")
73
+
74
+
75
+ def _as_config(source: ConfigSource) -> Config:
76
+ """Accept a ``Config``, a ``ConfigManager``, or any config-like object.
77
+
78
+ Config-like (anything exposing ``providers``) is allowed because callers
79
+ and tests pass lightweight stand-ins; environment overrides need a real
80
+ pydantic ``Config`` and degrade to a no-op for the rest.
81
+ """
82
+ if isinstance(source, Config):
83
+ return source
84
+ getter = getattr(source, "get_config", None)
85
+ if callable(getter):
86
+ config = getter()
87
+ if config is not None:
88
+ return config # type: ignore[no-any-return,return-value]
89
+ if hasattr(source, "providers"):
90
+ return source # type: ignore[no-any-return,return-value]
91
+ raise ProviderResolutionError(
92
+ "provider resolution needs a Config or a ConfigManager"
93
+ )
94
+
95
+
96
+ def effective_config(source: ConfigSource) -> Config:
97
+ """Stored configuration with environment overrides applied.
98
+
99
+ This is the configuration the engine actually runs with. Never save it:
100
+ it carries ephemeral environment values, including API keys.
101
+ """
102
+ config = _as_config(source)
103
+ try:
104
+ return apply_env_overrides(config)
105
+ except Exception as exc: # overrides must never block a run
106
+ logger.debug(f"Environment overrides unavailable: {exc}")
107
+ return config
108
+
109
+
110
+ def effective_providers(source: ConfigSource) -> Dict[str, ProviderConfig]:
111
+ """The provider entries in force, including env-materialized ones."""
112
+ return effective_config(source).providers or {}
113
+
114
+
115
+ def _entry_from_live_instance(
116
+ provider_name: str, engine: Optional[Any], model: str
117
+ ) -> Optional[ProviderConfig]:
118
+ """Rebuild an entry from a running instance (session flags, earlier init)."""
119
+ live = (getattr(engine, "providers", None) or {}) if engine is not None else {}
120
+ instance = live.get(provider_name) if isinstance(live, dict) else None
121
+ if instance is None:
122
+ return None
123
+
124
+ api_key = getattr(instance, "api_key", "") or ""
125
+ kwargs = {
126
+ k: v
127
+ for k, v in (getattr(instance, "config", None) or {}).items()
128
+ if v is not None and k not in ("api_key", "model")
129
+ }
130
+ for candidate in (kwargs, {}):
131
+ try:
132
+ return ProviderConfig(api_key=api_key, model=model, **candidate)
133
+ except Exception:
134
+ continue
135
+ return None
136
+
137
+
138
+ def resolve_entry(
139
+ provider_name: str,
140
+ source: ConfigSource,
141
+ engine: Optional[Any] = None,
142
+ model: str = "",
143
+ ) -> ProviderConfig:
144
+ """The entry for ``provider_name``, from the first source that has it.
145
+
146
+ Environment-effective config first (what the engine initialized from),
147
+ then the stored config, then a live instance in ``engine.providers``.
148
+
149
+ Raises:
150
+ EntryNotConfigured: when no source knows this name.
151
+ """
152
+ config = _as_config(source)
153
+ stored = getattr(config, "providers", None) or {}
154
+ entry = effective_providers(config).get(provider_name) or stored.get(provider_name)
155
+ if entry is not None:
156
+ return entry
157
+
158
+ from_live = _entry_from_live_instance(provider_name, engine, model)
159
+ if from_live is not None:
160
+ return from_live
161
+
162
+ raise EntryNotConfigured(provider_name)
163
+
164
+
165
+ def resolve_provider(
166
+ provider_name: str,
167
+ source: ConfigSource,
168
+ model: Optional[str] = None,
169
+ engine: Optional[Any] = None,
170
+ *,
171
+ min_output_tokens: Optional[int] = None,
172
+ require_tools: bool = False,
173
+ overrides: Optional[Dict[str, Any]] = None,
174
+ ) -> Any:
175
+ """Build a fresh provider instance for one entry.
176
+
177
+ Args:
178
+ provider_name: Config-entry name (may be an alias with provider_type).
179
+ source: ConfigManager or Config.
180
+ model: Model id to run; the entry's own model when omitted.
181
+ engine: Live engine, for the last-resort instance lookup.
182
+ min_output_tokens: Floor on ``max_tokens``; a larger configured value
183
+ wins. Providers default to 4096, which truncates long output.
184
+ require_tools: Reject a provider without native tool calling.
185
+ overrides: Extra entry fields to set (e.g. the enhancer's temperature).
186
+
187
+ Raises:
188
+ EntryNotConfigured, ProviderInitFailed, ToolsUnsupported
189
+ """
190
+ entry = resolve_entry(provider_name, source, engine, model or "")
191
+
192
+ update: Dict[str, Any] = dict(overrides or {})
193
+ if model:
194
+ update["model"] = model
195
+ if min_output_tokens is not None:
196
+ current = getattr(entry, "max_tokens", None) or 0
197
+ update["max_tokens"] = max(int(current), int(min_output_tokens))
198
+ effective = entry.model_copy(update=update) if update else entry
199
+
200
+ config_manager = None if isinstance(source, Config) else source
201
+ try:
202
+ provider = ProviderFactory.create_provider(
203
+ provider_name, effective, config_manager
204
+ )
205
+ except Exception as exc:
206
+ raise ProviderInitFailed(provider_name, effective.model, exc) from exc
207
+
208
+ if require_tools:
209
+ try:
210
+ supports_tools = bool(provider.supports_tools())
211
+ except Exception: # a provider that cannot even answer is not usable
212
+ supports_tools = False
213
+ if not supports_tools:
214
+ raise ToolsUnsupported(provider_name)
215
+
216
+ # Belt and braces: the factory caches on (name, model), so an unchanged
217
+ # model can legitimately return the session's own object. Callers that
218
+ # need isolation pass a distinct model; warn when they do not get it.
219
+ shared = getattr(engine, "providers", None) if engine is not None else None
220
+ if isinstance(shared, dict) and any(provider is p for p in shared.values()):
221
+ logger.debug(
222
+ "Resolved %s to the session's own provider instance; work may "
223
+ "interleave with the main conversation",
224
+ provider_name,
225
+ )
226
+ return provider
@@ -0,0 +1,86 @@
1
+ """Resolve H2L model refs into isolated provider instances (PRD §1).
2
+
3
+ Entry lookup and instance building live in
4
+ :mod:`omnimancer.core.provider_resolver`, which every surface shares; this
5
+ module is the H2L-flavoured wrapper that turns a resolution failure into an
6
+ :class:`H2LConfigError` naming the tier. The rule that makes parallel tiers
7
+ safe is the resolver's: never hand out an object from ``engine.providers``.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from dataclasses import dataclass, field
14
+ from typing import Any, List, Optional
15
+
16
+ from ..core.provider_resolver import (
17
+ EntryNotConfigured,
18
+ ProviderInitFailed,
19
+ ProviderResolutionError,
20
+ ToolsUnsupported,
21
+ resolve_provider,
22
+ )
23
+ from .models import H2LConfig, H2LModelRef
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class H2LConfigError(ValueError):
29
+ """A tier cannot run; the message says which one and why."""
30
+
31
+
32
+ @dataclass
33
+ class ResolvedTiers:
34
+ high: Any
35
+ low: List[Any] = field(default_factory=list)
36
+
37
+
38
+ def resolve(
39
+ ref: H2LModelRef,
40
+ config_manager: Any,
41
+ engine: Optional[Any],
42
+ min_output_tokens: Optional[int] = None,
43
+ ) -> Any:
44
+ """Build a fresh, tool-capable provider for ``ref``.
45
+
46
+ ``min_output_tokens`` is a floor on the instance's ``max_tokens``: the
47
+ providers default to 4096, which truncates plans and verdict feedback (the
48
+ first live runs lost whole plans to it). A larger configured value wins.
49
+
50
+ Raises :class:`H2LConfigError` when the entry is not configured, the
51
+ factory cannot build it, or the provider reports no tool support.
52
+ """
53
+ try:
54
+ return resolve_provider(
55
+ ref.provider,
56
+ config_manager,
57
+ ref.model,
58
+ engine,
59
+ min_output_tokens=min_output_tokens,
60
+ require_tools=True,
61
+ )
62
+ except EntryNotConfigured as exc:
63
+ raise H2LConfigError(f"H2L: {exc}") from exc
64
+ except ToolsUnsupported as exc:
65
+ raise H2LConfigError(
66
+ f"H2L: {exc}; use an openai-compatible alias pointed at its "
67
+ "OpenAI endpoint (see docs/provider-setup.md)"
68
+ ) from exc
69
+ except ProviderInitFailed as exc:
70
+ raise H2LConfigError(
71
+ f"H2L: could not initialize {ref.label}: {exc.cause}"
72
+ ) from exc
73
+ except ProviderResolutionError as exc: # future resolver failure modes
74
+ raise H2LConfigError(f"H2L: {exc}") from exc
75
+
76
+
77
+ def validate_tiers(
78
+ config: H2LConfig, config_manager: Any, engine: Optional[Any]
79
+ ) -> ResolvedTiers:
80
+ """Resolve the high ref and every low ref before any model call (PRD §1)."""
81
+ high = resolve(config.high, config_manager, engine, config.high_max_tokens)
82
+ low = [
83
+ resolve(ref, config_manager, engine, config.low_max_tokens)
84
+ for ref in config.low
85
+ ]
86
+ return ResolvedTiers(high=high, low=low)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omnimancer-cli
3
- Version: 0.3.4
3
+ Version: 0.3.5
4
4
  Summary: A unified command-line interface for multiple AI language models
5
5
  Author-email: Kellan Strong <kellan@jite.ai>
6
6
  License: MIT