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.
Files changed (199) hide show
  1. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/PKG-INFO +1 -1
  2. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/h2l_command.py +6 -0
  3. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/h2l_headless.py +10 -0
  4. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/engine.py +40 -9
  5. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/env_loader.py +4 -2
  6. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/provider_initializer.py +31 -1
  7. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/orchestrator.py +89 -18
  8. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/worker.py +50 -0
  9. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/bedrock.py +33 -12
  10. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer_cli.egg-info/PKG-INFO +1 -1
  11. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/pyproject.toml +1 -1
  12. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_bedrock_provider.py +103 -0
  13. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/LICENSE +0 -0
  14. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/README.md +0 -0
  15. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omn_fleet_hook.py +0 -0
  16. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/__init__.py +0 -0
  17. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/__main__.py +0 -0
  18. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/__init__.py +0 -0
  19. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/agent_loop.py +0 -0
  20. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/approval_formatter.py +0 -0
  21. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/approval_integration.py +0 -0
  22. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/approval_prompt.py +0 -0
  23. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/command_dispatch.py +0 -0
  24. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/commands.py +0 -0
  25. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/completion.py +0 -0
  26. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/display.py +0 -0
  27. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/dynamic_commands.py +0 -0
  28. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/file_mentions.py +0 -0
  29. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/handlers/__init__.py +0 -0
  30. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/headless.py +0 -0
  31. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/headless_checkpoint.py +0 -0
  32. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/interface.py +0 -0
  33. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/prompt_input.py +0 -0
  34. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/pt_completion.py +0 -0
  35. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/session.py +0 -0
  36. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/subagent.py +0 -0
  37. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/system_prompts.py +0 -0
  38. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/tool_handler.py +0 -0
  39. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/turn_notify.py +0 -0
  40. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/cli/usage.py +0 -0
  41. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/__init__.py +0 -0
  42. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/__init__.py +0 -0
  43. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/approval_context.py +0 -0
  44. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/approval_interface.py +0 -0
  45. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/approval_manager.py +0 -0
  46. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/config.py +0 -0
  47. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/file_system_manager.py +0 -0
  48. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/program_executor.py +0 -0
  49. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/read_before_write_errors.py +0 -0
  50. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/status_core.py +0 -0
  51. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/status_manager.py +0 -0
  52. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/tool_definitions.py +0 -0
  53. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/types.py +0 -0
  54. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/web_client.py +0 -0
  55. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent/workflow_orchestrator.py +0 -0
  56. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent_engine.py +0 -0
  57. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent_managers.py +0 -0
  58. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/agent_mode_manager.py +0 -0
  59. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/chat_manager.py +0 -0
  60. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/config_manager.py +0 -0
  61. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/config_migration.py +0 -0
  62. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/config_validator.py +0 -0
  63. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/conversation_manager.py +0 -0
  64. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/fallback_manager.py +0 -0
  65. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/health_monitor.py +0 -0
  66. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/history_manager.py +0 -0
  67. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/hooks.py +0 -0
  68. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/mcp_integration_layer.py +0 -0
  69. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/models.py +0 -0
  70. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/prompt_enhancer.py +0 -0
  71. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/provider_capabilities.py +0 -0
  72. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/provider_registry.py +0 -0
  73. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/provider_resolver.py +0 -0
  74. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/rate_limit_fallback.py +0 -0
  75. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/security/__init__.py +0 -0
  76. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/security/approval_workflow.py +0 -0
  77. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/security/audit_logger.py +0 -0
  78. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/security/permission_controller.py +0 -0
  79. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/security/permission_rules.py +0 -0
  80. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/security/sandbox_manager.py +0 -0
  81. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/security/security_manager.py +0 -0
  82. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/core/signal_handler.py +0 -0
  83. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/events/__init__.py +0 -0
  84. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/events/emitter.py +0 -0
  85. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/events/jsonl_writer.py +0 -0
  86. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/events/schema.py +0 -0
  87. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/__init__.py +0 -0
  88. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/judge.py +0 -0
  89. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/loop.py +0 -0
  90. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/models.py +0 -0
  91. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/planner.py +0 -0
  92. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/prompts.py +0 -0
  93. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/refs.py +0 -0
  94. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/h2l/render.py +0 -0
  95. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/mcp/__init__.py +0 -0
  96. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/mcp/client.py +0 -0
  97. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/mcp/manager.py +0 -0
  98. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/__init__.py +0 -0
  99. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/azure.py +0 -0
  100. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/base.py +0 -0
  101. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/cache_tokens.py +0 -0
  102. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/claude.py +0 -0
  103. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/claude_code.py +0 -0
  104. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/claude_credentials.py +0 -0
  105. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/cohere.py +0 -0
  106. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/digitalocean.py +0 -0
  107. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/factory.py +0 -0
  108. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/gemini.py +0 -0
  109. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/mistral.py +0 -0
  110. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/ollama.py +0 -0
  111. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/openai.py +0 -0
  112. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/openai_compatible.py +0 -0
  113. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/openrouter.py +0 -0
  114. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/perplexity.py +0 -0
  115. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/vertex.py +0 -0
  116. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/providers/xai.py +0 -0
  117. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/tui/__init__.py +0 -0
  118. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/tui/fleet/__init__.py +0 -0
  119. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/tui/fleet/app.py +0 -0
  120. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/tui/fleet/cli.py +0 -0
  121. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/tui/fleet/models.py +0 -0
  122. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/tui/fleet/sources.py +0 -0
  123. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/tui/fleet/widgets.py +0 -0
  124. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/ui/__init__.py +0 -0
  125. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/ui/cancellation_handler.py +0 -0
  126. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/ui/progress_indicator.py +0 -0
  127. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/ui/streaming_display.py +0 -0
  128. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/ui/turn_activity.py +0 -0
  129. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/utils/__init__.py +0 -0
  130. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/utils/error_handler.py +0 -0
  131. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/utils/errors.py +0 -0
  132. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer/utils/retry.py +0 -0
  133. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer_cli.egg-info/SOURCES.txt +0 -0
  134. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer_cli.egg-info/dependency_links.txt +0 -0
  135. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer_cli.egg-info/entry_points.txt +0 -0
  136. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer_cli.egg-info/requires.txt +0 -0
  137. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/omnimancer_cli.egg-info/top_level.txt +0 -0
  138. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/setup.cfg +0 -0
  139. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_approval_integration_compatibility.py +0 -0
  140. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_approval_preview_format.py +0 -0
  141. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_approval_remember.py +0 -0
  142. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_approval_system.py +0 -0
  143. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_azure_provider.py +0 -0
  144. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_claude_code_provider.py +0 -0
  145. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_claude_credentials.py +0 -0
  146. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_claude_prompt_cache.py +0 -0
  147. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_claude_provider.py +0 -0
  148. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_cohere_provider.py +0 -0
  149. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_command_approval_security.py +0 -0
  150. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_config_comprehensive.py +0 -0
  151. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_config_migration_passthrough.py +0 -0
  152. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_conversation_history.py +0 -0
  153. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_conversation_manager.py +0 -0
  154. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_core_engine.py +0 -0
  155. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_digitalocean_provider.py +0 -0
  156. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_enhanced_model_info.py +0 -0
  157. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_env_loader.py +0 -0
  158. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_error_handling_robustness.py +0 -0
  159. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_execution_timeout_errors.py +0 -0
  160. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_fallback_manager.py +0 -0
  161. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_fixtures.py +0 -0
  162. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_gemini_provider.py +0 -0
  163. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_hooks.py +0 -0
  164. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_httpx_compatibility.py +0 -0
  165. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_import_validation.py +0 -0
  166. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_interface_smoke_tests.py +0 -0
  167. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_mcp_integration.py +0 -0
  168. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_mcp_integration_layer.py +0 -0
  169. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_mcp_management_commands.py +0 -0
  170. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_mistral_provider.py +0 -0
  171. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_models.py +0 -0
  172. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_ollama_provider.py +0 -0
  173. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_openai_compatible_provider.py +0 -0
  174. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_openai_context_fit.py +0 -0
  175. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_openai_provider_tools.py +0 -0
  176. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_openai_request_timeout.py +0 -0
  177. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_openai_streaming.py +0 -0
  178. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_openrouter_provider.py +0 -0
  179. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_permission_rules.py +0 -0
  180. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_perplexity_provider.py +0 -0
  181. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_program_executor.py +0 -0
  182. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_provider_aliases.py +0 -0
  183. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_provider_base.py +0 -0
  184. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_provider_cache_tokens.py +0 -0
  185. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_provider_capability_consistency.py +0 -0
  186. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_provider_discovery.py +0 -0
  187. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_provider_integration.py +0 -0
  188. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_provider_registry.py +0 -0
  189. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_rate_limit_fallback.py +0 -0
  190. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_retry.py +0 -0
  191. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_security_validation.py +0 -0
  192. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_system_integration.py +0 -0
  193. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_system_prompts.py +0 -0
  194. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_tool_calling_integration.py +0 -0
  195. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_tool_handler.py +0 -0
  196. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_vertex_provider.py +0 -0
  197. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_web_request_markers.py +0 -0
  198. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/tests/test_workflows_comprehensive.py +0 -0
  199. {omnimancer_cli-0.3.5 → omnimancer_cli-0.3.7}/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.5
3
+ Version: 0.3.7
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
@@ -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 configured but failed to "
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 but failed to initialize "
312
- f"— most likely a missing or invalid API key. Configure it (e.g. "
313
- f"'/config set-provider {unavailable}' or the provider's API-key "
314
- f"environment variable) and try again."
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
- return "No provider available. Please configure a provider first."
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.info(f"Created provider '{name}' from environment overrides")
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.info(f"Default provider overridden from environment: {default_override}")
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
- logger.error(f"Failed to initialize provider {provider_name}: {e}")
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 Any, Callable, Dict, Iterator, List, Optional, Protocol, Tuple
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
- attempt += 1
303
- ref, provider = self._next_ref()
304
- verdict = await self._attempt(story, ref, provider, attempt, feedback)
305
- if verdict is None:
306
- return # cancelled or the run stopped
307
- if verdict.passed:
308
- outcome.state = StoryState.PASSED
309
- outcome.score = verdict.score
310
- outcome.model = ref.label
311
- return
312
- feedback = verdict.feedback
313
- if attempt < max_attempts:
314
- continue
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 = "retries_exhausted"
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 = list(zip(self.config.low, tiers.low))
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.model}/converse"
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, headers=headers, content=request_body, timeout=30.0
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 NetworkError("Request to AWS Bedrock API timed out")
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 - use appropriate endpoint based on model format
195
- if self._is_arn(self.model):
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=30.0,
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 NetworkError("Request to AWS Bedrock API timed out")
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omnimancer-cli
3
- Version: 0.3.5
3
+ Version: 0.3.7
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "omnimancer-cli"
7
- version = "0.3.5"
7
+ version = "0.3.7"
8
8
  description = "A unified command-line interface for multiple AI language models"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"