ayder-cli 2.1.1__tar.gz → 2.1.2__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.
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/PKG-INFO +1 -1
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/docs/config.toml.example +47 -7
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/pyproject.toml +1 -1
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/core/config.py +34 -3
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/core/default_context_manager.py +48 -12
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/ollama.py +4 -6
- ayder_cli-2.1.2/tests/core/test_config_provider_logging.py +46 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/core/test_default_context_manager.py +133 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/test_ollama_native.py +33 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/.github/workflows/ci.yml +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/.github/workflows/python-package.yml +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/.gitignore +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/.python-version +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/AGENTS.md +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/CHANGELOG.md +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/LICENSE +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/README.md +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/docs/PROJECT_STRUCTURE.md +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/docs/README_AGENT_WORKFLOW.md +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/docs/README_OLLAMA.md +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/docs/cc.png +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/__main__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/agents/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/agents/callbacks.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/agents/config.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/agents/registry.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/agents/run.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/agents/runner.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/agents/tool.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/application/README.md +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/application/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/application/execution_policy.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/application/message_contract.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/application/runtime_factory.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/application/validation.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/cli.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/cli_callbacks.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/cli_runner.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/console.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/core/cache_monitor.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/core/config_migration.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/core/context.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/core/context_manager.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/core/context_manager_factory.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/core/ollama_context_manager.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/core/result.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/logging_config.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/loops/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/loops/chat_loop.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/parser.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/process_manager.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/prompts.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/base.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/claude.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/deepseek.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/gemini.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/glm.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/ollama_drivers/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/ollama_drivers/_errors.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/ollama_drivers/base.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/ollama_drivers/deepseek.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/ollama_drivers/generic_native.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/ollama_drivers/generic_xml.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/ollama_drivers/matrix.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/ollama_drivers/minimax.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/ollama_drivers/qwen3.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/ollama_drivers/registry.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/ollama_inspector.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/openai.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/impl/qwen.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/orchestrator.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/providers/retry.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/services/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/services/interactions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/themes/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/themes/claude.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/themes/original.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/context.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/context_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/filesystem.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/filesystem_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/notes.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/notes_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/process_manager_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/search.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/search_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/shell.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/shell_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/tasks.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/tasks_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/utils_tools.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/utils_tools_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/web.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/builtins/web_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/definition.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/execution.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/hooks.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/normalization.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/plugin_api.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/plugin_github.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/plugin_manager.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/registry.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/schemas.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tools/utils.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tui/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tui/adapter.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tui/app.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tui/commands.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tui/helpers.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tui/keybindings.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tui/parser.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tui/screens.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tui/theme_manager.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tui/types.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/tui/widgets.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/ui.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/src/ayder_cli/version.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/COVERAGE.md +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/agents/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/agents/test_agent_tools.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/agents/test_callbacks.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/agents/test_config.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/agents/test_integration.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/agents/test_registry.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/agents/test_run.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/agents/test_runner.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/agents/test_runner_provider_unavailable.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/agents/test_tool.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/application/test_message_contract.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/application/test_runtime_factory.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/client/test_main.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/convergence/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/convergence/test_execution_policy_parity.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/convergence/test_runtime_wiring.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/convergence/test_validation_path.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/core/test_cache_monitor.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/core/test_config.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/core/test_config_coverage.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/core/test_config_migration.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/core/test_config_retry.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/core/test_config_v2.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/core/test_context_manager_factory.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/core/test_default_context_manager_should_compact.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/core/test_ollama_context_manager.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/core/test_ollama_context_manager_cache_monitor.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/core/test_ollama_context_manager_max_history.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/core/test_parameter_aliasing.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/core/test_parser.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/loops/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/loops/test_base.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/loops/test_chat_loop_hook.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/loops/test_chat_loop_tool_truncation.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/ollama_drivers/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/ollama_drivers/test_base.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/ollama_drivers/test_deepseek.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/ollama_drivers/test_errors.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/ollama_drivers/test_fallback.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/ollama_drivers/test_generic_native.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/ollama_drivers/test_generic_xml.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/ollama_drivers/test_matrix.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/ollama_drivers/test_minimax.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/ollama_drivers/test_provider_integration.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/ollama_drivers/test_qwen3.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/ollama_drivers/test_registry.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/test_glm_async.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/test_ollama_inspector.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/test_ollama_xml.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/test_ollama_xml_autoroute.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/test_ollama_xml_usage.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/test_orchestrator_capabilities.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/test_qwen_async.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/test_retry_classifier.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/test_retry_integration.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/providers/test_retrying_provider.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/services/test_boundary.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/services/test_interaction_sink.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/test_cli.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/test_cli_callbacks.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/test_context.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/test_env_manager.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/test_logging_config.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/test_notes.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/test_process_manager.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/test_tasks.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_agent_notes.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_definition_discovery.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_di_injection.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_impl.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_impl_coverage.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_path_security.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_plugin_api.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_plugin_github.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_plugin_loading.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_plugin_manager.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_registry.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_registry_coverage.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_result.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_schemas.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_search_codebase.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_utils.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tools/test_web.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tui/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tui/test_agent_list_screen.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tui/test_do_clear_coordination.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tui/test_follow_mode.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tui/test_help_screen.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tui/test_keybindings.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tui/test_multi_select_screen.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tui/test_multiline_input.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tui/test_paste.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tui/test_pending_compact_consumer.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tui/test_pull_delivery.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tui/test_turn_consumer.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/tui/test_widgets.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/ui/test_confirm_screen.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/ui/test_diff_preview.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/ui/test_provider_switch.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/ui/test_run_tui_startup.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/ui/test_tui_assistant_sanitization.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/ui/test_tui_chat_loop.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/ui/test_tui_commands_agent.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/ui/test_tui_commands_logging.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/ui/test_tui_commands_provider.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/ui/test_tui_helpers.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/ui/test_tui_temporal_command.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/ui/test_tui_widgets.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/ui/test_ui.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/ui/test_ui_coverage.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.2}/tests/ui/test_ui_verbose.py +0 -0
|
@@ -48,9 +48,29 @@
|
|
|
48
48
|
# `export OLLAMA_API_KEY="<key>"`. The api_key config field is INERT on the
|
|
49
49
|
# native path, so a missing env var ⇒ 401 Unauthorized.
|
|
50
50
|
#
|
|
51
|
-
# NOTE on `think`:
|
|
52
|
-
#
|
|
53
|
-
#
|
|
51
|
+
# NOTE on `think`: on the native `ollama` driver, ayder hardcodes thinking ON
|
|
52
|
+
# (think=True) and does NOT forward the configured level — so a
|
|
53
|
+
# `think = "high"` below documents intent, but the "low/medium/high"
|
|
54
|
+
# granularity is not applied. The `openai` driver sends no reasoning-effort
|
|
55
|
+
# parameter at all.
|
|
56
|
+
#
|
|
57
|
+
# NOTE on tuning knobs — which actually take effect depends on the DRIVER. These
|
|
58
|
+
# agents run on the native `ollama` driver, which ignores most of them:
|
|
59
|
+
# • temperature — forwarded by NO driver yet ⇒ no effect anywhere.
|
|
60
|
+
# • max_output_tokens — NOT sent on the ollama path (Ollama sizes
|
|
61
|
+
# generation itself); honored only by the
|
|
62
|
+
# openai-family drivers as max_tokens. Default 4096.
|
|
63
|
+
# • think — hardcoded ON for ollama (level ignored); openai
|
|
64
|
+
# sends none.
|
|
65
|
+
# • num_ctx — on ollama it is provisional only (the real window
|
|
66
|
+
# is auto-detected from /api/show); on the openai
|
|
67
|
+
# path it sizes the context-manager budget.
|
|
68
|
+
# Default 65536.
|
|
69
|
+
# The per-agent temperature / max_output_tokens / think values below are
|
|
70
|
+
# therefore largely INERT on this Ollama pipeline — they are kept to
|
|
71
|
+
# document intent and to carry over if you switch an agent to an `openai`
|
|
72
|
+
# profile. To actually tune these, use the `openai` driver — see
|
|
73
|
+
# [llm.openai] below.
|
|
54
74
|
# =============================================================================
|
|
55
75
|
|
|
56
76
|
config_version = "2.0"
|
|
@@ -77,10 +97,16 @@ retention = "7 days"
|
|
|
77
97
|
|
|
78
98
|
[context_manager]
|
|
79
99
|
enabled = true
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
|
|
100
|
+
# Destructive tool-result compression is OPT-IN and OFF by default. It summarizes
|
|
101
|
+
# old tool results in place (lossy) and thrashes the KV cache, so leave it off
|
|
102
|
+
# unless you specifically want it; eviction will replace it in a later release.
|
|
103
|
+
enable_compression = false
|
|
104
|
+
# max_context_tokens is OPTIONAL. When unset (recommended), the budget is derived
|
|
105
|
+
# from the active model's num_ctx. It is read only on the openai/non-ollama path
|
|
106
|
+
# (DefaultContextManager); the native Ollama path auto-detects the real window
|
|
107
|
+
# from /api/show and ignores it. reserve_ratio + compaction_threshold ARE read on
|
|
108
|
+
# both paths.
|
|
109
|
+
# max_context_tokens = 131072
|
|
84
110
|
reserve_ratio = 0.30
|
|
85
111
|
compaction_threshold = 0.80
|
|
86
112
|
|
|
@@ -118,6 +144,20 @@ num_ctx = 32768 # keep modest: local VRAM is the constraint
|
|
|
118
144
|
# model = "gemma4:31b-cloud"
|
|
119
145
|
# num_ctx = 131072
|
|
120
146
|
|
|
147
|
+
# OpenAI-compatible driver. Unlike the `ollama` driver above, this one HONORS the
|
|
148
|
+
# tuning knobs (defaults shown). Use it for api.openai.com, Azure, vLLM,
|
|
149
|
+
# llama.cpp, LM Studio, or Ollama's own `/v1` endpoint. Point an agent at it with
|
|
150
|
+
# `provider = "openai"`.
|
|
151
|
+
# [llm.openai]
|
|
152
|
+
# driver = "openai"
|
|
153
|
+
# base_url = "https://api.openai.com/v1" # OpenAI-compatible endpoint
|
|
154
|
+
# api_key = "sk-..." # your key (or "ollama"/"none" for local servers)
|
|
155
|
+
# model = "gpt-5.5"
|
|
156
|
+
# num_ctx = 65536 # sizes the context-manager budget (default 65536); NOT sent to the API
|
|
157
|
+
# max_output_tokens = 8192 # -> max_tokens / max_completion_tokens (default 4096); keep WELL below num_ctx
|
|
158
|
+
# stop_sequences = [] # -> stop (default [])
|
|
159
|
+
# # temperature / think are NOT applied by the openai driver today (no-ops) — omitted on purpose.
|
|
160
|
+
|
|
121
161
|
# =============================================================================
|
|
122
162
|
# Agents
|
|
123
163
|
# =============================================================================
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import tomllib
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
4
5
|
from typing import Any, Callable, Dict
|
|
5
6
|
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
6
9
|
CONFIG_DIR = Path("~/.ayder").expanduser()
|
|
7
10
|
CONFIG_PATH = CONFIG_DIR / "config.toml"
|
|
8
11
|
|
|
@@ -174,12 +177,19 @@ class ContextManagerConfigSection(BaseModel):
|
|
|
174
177
|
model_config = ConfigDict(frozen=True)
|
|
175
178
|
|
|
176
179
|
enabled: bool = Field(default=True)
|
|
177
|
-
|
|
180
|
+
# Optional: when unset, the context manager derives the budget from the
|
|
181
|
+
# model's num_ctx. The old fixed 8192 default predates large-context models.
|
|
182
|
+
max_context_tokens: int | None = Field(default=None)
|
|
178
183
|
reserve_ratio: float = Field(default=0.30)
|
|
179
184
|
compaction_threshold: float = Field(default=0.70)
|
|
180
185
|
tool_result_compress_age: int = Field(default=5)
|
|
181
186
|
max_tool_result_length: int = Field(default=2048)
|
|
182
187
|
compress_tool_results: bool = Field(default=True)
|
|
188
|
+
# Master switch for destructive tool-result compression. Compression is
|
|
189
|
+
# known to be too aggressive (it summarizes old tool results in place and
|
|
190
|
+
# thrashes the KV cache), so it is OPT-IN: disabled unless explicitly set.
|
|
191
|
+
# Eviction will replace it in a later phase.
|
|
192
|
+
enable_compression: bool = Field(default=False)
|
|
183
193
|
|
|
184
194
|
@field_validator("reserve_ratio", "compaction_threshold")
|
|
185
195
|
@classmethod
|
|
@@ -190,8 +200,8 @@ class ContextManagerConfigSection(BaseModel):
|
|
|
190
200
|
|
|
191
201
|
@field_validator("max_context_tokens")
|
|
192
202
|
@classmethod
|
|
193
|
-
def validate_positive(cls, v: int) -> int:
|
|
194
|
-
if v <= 0:
|
|
203
|
+
def validate_positive(cls, v: int | None) -> int | None:
|
|
204
|
+
if v is not None and v <= 0:
|
|
195
205
|
raise ValueError("max_context_tokens must be positive")
|
|
196
206
|
return v
|
|
197
207
|
|
|
@@ -309,9 +319,25 @@ class Config(BaseModel):
|
|
|
309
319
|
if isinstance(llm_section, dict):
|
|
310
320
|
profile = llm_section.get(provider)
|
|
311
321
|
if not isinstance(profile, dict):
|
|
322
|
+
available = ", ".join(sorted(llm_section)) or "<none>"
|
|
312
323
|
default_profile = llm_section.get(DEFAULTS["provider"])
|
|
313
324
|
if isinstance(default_profile, dict):
|
|
314
325
|
profile = default_profile
|
|
326
|
+
logger.error(
|
|
327
|
+
"Provider %r has no [llm.%s] profile; falling back to the "
|
|
328
|
+
"default [llm.%s] profile. Available profiles: %s. Fix the "
|
|
329
|
+
"provider name or add an [llm.%s] section.",
|
|
330
|
+
provider, provider, DEFAULTS["provider"], available, provider,
|
|
331
|
+
)
|
|
332
|
+
else:
|
|
333
|
+
logger.error(
|
|
334
|
+
"Provider %r has no [llm.%s] profile and no default "
|
|
335
|
+
"[llm.%s] profile exists; the driver will default to "
|
|
336
|
+
"'openai' with built-in endpoint defaults. Available "
|
|
337
|
+
"profiles: %s. Fix the provider name or add an [llm.%s] "
|
|
338
|
+
"section.",
|
|
339
|
+
provider, provider, DEFAULTS["provider"], available, provider,
|
|
340
|
+
)
|
|
315
341
|
if isinstance(profile, dict):
|
|
316
342
|
new_data.update(profile)
|
|
317
343
|
new_data.pop("llm", None)
|
|
@@ -359,6 +385,11 @@ class Config(BaseModel):
|
|
|
359
385
|
"qwen", "dashscope", "glm", "zhipu",
|
|
360
386
|
)
|
|
361
387
|
if v not in valid:
|
|
388
|
+
logger.error(
|
|
389
|
+
"Unsupported driver %r; must be one of: %s. Fix the driver in "
|
|
390
|
+
"your [llm.<provider>] profile.",
|
|
391
|
+
v, ", ".join(valid),
|
|
392
|
+
)
|
|
362
393
|
raise ValueError(f"driver must be one of: {', '.join(valid)}")
|
|
363
394
|
return v
|
|
364
395
|
|
|
@@ -19,6 +19,10 @@ if TYPE_CHECKING:
|
|
|
19
19
|
|
|
20
20
|
logger = logging.getLogger(__name__)
|
|
21
21
|
|
|
22
|
+
# Fallback context budget used only when neither an explicit max_context_tokens
|
|
23
|
+
# nor a model num_ctx is available (e.g. a bare config section in tests).
|
|
24
|
+
DEFAULT_MAX_CONTEXT_TOKENS = 65536
|
|
25
|
+
|
|
22
26
|
|
|
23
27
|
class MessageTier(Enum):
|
|
24
28
|
"""Importance tiers for messages."""
|
|
@@ -147,10 +151,34 @@ class DefaultContextManager:
|
|
|
147
151
|
elif isinstance(config, ContextManagerConfigSection):
|
|
148
152
|
self._config = config
|
|
149
153
|
else:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
+
self._config = ContextManagerConfigSection()
|
|
155
|
+
|
|
156
|
+
# Resolve the effective context budget. An explicit max_context_tokens
|
|
157
|
+
# wins; otherwise derive it from the model's num_ctx so the budget tracks
|
|
158
|
+
# the real window instead of the legacy fixed default.
|
|
159
|
+
configured = self._config.max_context_tokens
|
|
160
|
+
num_ctx = getattr(config, "num_ctx", None)
|
|
161
|
+
if isinstance(configured, int) and configured > 0:
|
|
162
|
+
self._max_context_tokens = configured
|
|
163
|
+
logger.info(
|
|
164
|
+
"Context budget: %d tokens (explicit max_context_tokens; num_ctx=%s)",
|
|
165
|
+
configured, num_ctx,
|
|
166
|
+
)
|
|
167
|
+
elif isinstance(num_ctx, int) and num_ctx > 0:
|
|
168
|
+
self._max_context_tokens = num_ctx
|
|
169
|
+
logger.info(
|
|
170
|
+
"Context budget: %d tokens (derived from num_ctx; "
|
|
171
|
+
"max_context_tokens unset)",
|
|
172
|
+
num_ctx,
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
self._max_context_tokens = DEFAULT_MAX_CONTEXT_TOKENS
|
|
176
|
+
logger.warning(
|
|
177
|
+
"Context budget: could not read num_ctx or max_context_tokens; "
|
|
178
|
+
"falling back to default %d tokens. Recommend setting num_ctx "
|
|
179
|
+
"(or [context_manager] max_context_tokens) in your config.",
|
|
180
|
+
DEFAULT_MAX_CONTEXT_TOKENS,
|
|
181
|
+
)
|
|
154
182
|
|
|
155
183
|
provider = getattr(config, "provider", "openai")
|
|
156
184
|
self._counter = TokenCounter(model=model, provider=provider)
|
|
@@ -214,7 +242,7 @@ class DefaultContextManager:
|
|
|
214
242
|
response arrives. See opus47.md finding #6.
|
|
215
243
|
"""
|
|
216
244
|
if self._last_prompt_tokens:
|
|
217
|
-
ceiling = self.
|
|
245
|
+
ceiling = self._max_context_tokens
|
|
218
246
|
return self._last_prompt_tokens >= ceiling * self._config.compaction_threshold
|
|
219
247
|
return self.should_trigger_summarization()
|
|
220
248
|
|
|
@@ -227,8 +255,8 @@ class DefaultContextManager:
|
|
|
227
255
|
return self._stats_cache
|
|
228
256
|
|
|
229
257
|
total = self._calculate_total_tokens()
|
|
230
|
-
available = max(0, self.
|
|
231
|
-
utilization = (total / self.
|
|
258
|
+
available = max(0, self._max_context_tokens - total)
|
|
259
|
+
utilization = (total / self._max_context_tokens) * 100
|
|
232
260
|
|
|
233
261
|
self._stats_cache = ContextStats(
|
|
234
262
|
total_tokens=total,
|
|
@@ -245,7 +273,7 @@ class DefaultContextManager:
|
|
|
245
273
|
|
|
246
274
|
@property
|
|
247
275
|
def reserve(self) -> int:
|
|
248
|
-
return int(self.
|
|
276
|
+
return int(self._max_context_tokens * self._config.reserve_ratio)
|
|
249
277
|
|
|
250
278
|
def count_tokens(self, text: str) -> int:
|
|
251
279
|
"""Legacy support."""
|
|
@@ -362,18 +390,24 @@ class DefaultContextManager:
|
|
|
362
390
|
return False
|
|
363
391
|
|
|
364
392
|
total = self._calculate_total_tokens()
|
|
365
|
-
utilization = total / self.
|
|
393
|
+
utilization = total / self._max_context_tokens
|
|
366
394
|
return utilization >= self._config.compaction_threshold
|
|
367
395
|
|
|
368
396
|
def should_trigger_compression(self) -> bool:
|
|
397
|
+
if not getattr(self._config, "enable_compression", False):
|
|
398
|
+
return False
|
|
369
399
|
if not self._config.enabled or not self._config.compress_tool_results:
|
|
370
400
|
return False
|
|
371
401
|
|
|
372
402
|
total = self._calculate_total_tokens()
|
|
373
|
-
utilization = total / self.
|
|
403
|
+
utilization = total / self._max_context_tokens
|
|
374
404
|
return utilization >= self._config.compaction_threshold
|
|
375
405
|
|
|
376
406
|
def compress_old_results(self, age_threshold: int | None = None) -> int:
|
|
407
|
+
# Compression is opt-in. Disabled by default because it destructively
|
|
408
|
+
# summarizes old tool results in place and thrashes the KV cache.
|
|
409
|
+
if not getattr(self._config, "enable_compression", False):
|
|
410
|
+
return 0
|
|
377
411
|
threshold = (
|
|
378
412
|
age_threshold
|
|
379
413
|
if age_threshold is not None
|
|
@@ -449,10 +483,12 @@ class DefaultContextManager:
|
|
|
449
483
|
return overhead
|
|
450
484
|
|
|
451
485
|
def _available_budget(self, overhead: int) -> int:
|
|
452
|
-
reserve = int(self.
|
|
453
|
-
return self.
|
|
486
|
+
reserve = int(self._max_context_tokens * self._config.reserve_ratio)
|
|
487
|
+
return self._max_context_tokens - overhead - reserve
|
|
454
488
|
|
|
455
489
|
def _auto_compress(self) -> None:
|
|
490
|
+
if not getattr(self._config, "enable_compression", False):
|
|
491
|
+
return
|
|
456
492
|
if not self._config.compress_tool_results:
|
|
457
493
|
return
|
|
458
494
|
if self.should_trigger_compression():
|
|
@@ -146,8 +146,8 @@ class OllamaProvider(AIProvider):
|
|
|
146
146
|
tools: list[dict[str, Any]] | None,
|
|
147
147
|
options: dict[str, Any] | None,
|
|
148
148
|
) -> AsyncGenerator[NormalizedStreamChunk, None]:
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
# Let Ollama size the context window from the model itself (recent
|
|
150
|
+
# versions do this automatically); do not force num_ctx.
|
|
151
151
|
ollama_tools = self._convert_tools(tools) if tools else None
|
|
152
152
|
ollama_messages = self._convert_messages(messages)
|
|
153
153
|
|
|
@@ -155,7 +155,6 @@ class OllamaProvider(AIProvider):
|
|
|
155
155
|
model=model,
|
|
156
156
|
messages=ollama_messages,
|
|
157
157
|
tools=ollama_tools,
|
|
158
|
-
options={"num_ctx": num_ctx},
|
|
159
158
|
keep_alive=-1,
|
|
160
159
|
think=True,
|
|
161
160
|
stream=True,
|
|
@@ -204,8 +203,8 @@ class OllamaProvider(AIProvider):
|
|
|
204
203
|
tools: list[dict[str, Any]] | None,
|
|
205
204
|
options: dict[str, Any] | None,
|
|
206
205
|
) -> AsyncGenerator[NormalizedStreamChunk, None]:
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
# Let Ollama size the context window from the model itself; do not
|
|
207
|
+
# force num_ctx.
|
|
209
208
|
formatted_messages = driver.render_tools_into_messages(messages, tools or [])
|
|
210
209
|
ollama_messages = self._convert_messages(formatted_messages)
|
|
211
210
|
|
|
@@ -213,7 +212,6 @@ class OllamaProvider(AIProvider):
|
|
|
213
212
|
model=model,
|
|
214
213
|
messages=ollama_messages,
|
|
215
214
|
tools=None,
|
|
216
|
-
options={"num_ctx": num_ctx},
|
|
217
215
|
keep_alive=-1,
|
|
218
216
|
think=True,
|
|
219
217
|
stream=True,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Config should log an actionable error when a provider/driver is misconfigured."""
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from pydantic import ValidationError
|
|
6
|
+
|
|
7
|
+
from ayder_cli.core.config import Config
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_missing_provider_profile_logs_error(caplog):
|
|
11
|
+
"""A provider with no matching [llm.<provider>] profile logs an error and
|
|
12
|
+
still falls back to the openai driver (so the run does not silently misroute
|
|
13
|
+
without any trace)."""
|
|
14
|
+
data = {
|
|
15
|
+
"app": {"provider": "ollama_cloud"},
|
|
16
|
+
"llm": {"ollama": {"driver": "ollama", "model": "m"}},
|
|
17
|
+
}
|
|
18
|
+
with caplog.at_level(logging.ERROR):
|
|
19
|
+
cfg = Config(**data)
|
|
20
|
+
|
|
21
|
+
assert cfg.driver == "openai" # silent fallback — now logged
|
|
22
|
+
text = caplog.text
|
|
23
|
+
assert "ollama_cloud" in text
|
|
24
|
+
assert "ollama" in text # names the available profile / section to add
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_valid_provider_profile_logs_no_error(caplog):
|
|
28
|
+
"""A provider that has a matching profile must not log an error."""
|
|
29
|
+
data = {
|
|
30
|
+
"app": {"provider": "ollama"},
|
|
31
|
+
"llm": {"ollama": {"driver": "ollama", "model": "m"}},
|
|
32
|
+
}
|
|
33
|
+
with caplog.at_level(logging.ERROR):
|
|
34
|
+
cfg = Config(**data)
|
|
35
|
+
|
|
36
|
+
assert cfg.driver == "ollama"
|
|
37
|
+
assert caplog.text == ""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_invalid_driver_logs_error(caplog):
|
|
41
|
+
"""An unsupported driver name logs an error before raising."""
|
|
42
|
+
with caplog.at_level(logging.ERROR):
|
|
43
|
+
with pytest.raises(ValidationError):
|
|
44
|
+
Config(driver="not_a_real_driver", provider="openai")
|
|
45
|
+
|
|
46
|
+
assert "not_a_real_driver" in caplog.text
|
|
@@ -178,6 +178,139 @@ def test_group_into_units():
|
|
|
178
178
|
assert units[2] == [messages[3]]
|
|
179
179
|
|
|
180
180
|
|
|
181
|
+
def _make_old_long_tool_result_manager(config):
|
|
182
|
+
"""Manager holding one old, long tool result eligible for compression."""
|
|
183
|
+
mgr = DefaultContextManager(config)
|
|
184
|
+
long_content = "x" * 5000 # exceeds max_tool_result_length (2048)
|
|
185
|
+
mgr.add_message({"role": "user", "content": "go"})
|
|
186
|
+
mgr.add_message(
|
|
187
|
+
{
|
|
188
|
+
"role": "assistant",
|
|
189
|
+
"content": "",
|
|
190
|
+
"tool_calls": [
|
|
191
|
+
{"id": "1", "function": {"name": "read_file", "arguments": "{}"}}
|
|
192
|
+
],
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
mgr.add_message(
|
|
196
|
+
{"role": "tool", "tool_call_id": "1", "name": "read_file", "content": long_content}
|
|
197
|
+
)
|
|
198
|
+
# Filler so the tool result is "old" (age > tool_result_compress_age=5).
|
|
199
|
+
for i in range(10):
|
|
200
|
+
mgr.add_message({"role": "user", "content": f"m{i}"})
|
|
201
|
+
return mgr, long_content
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def test_max_context_tokens_defaults_to_none():
|
|
205
|
+
"""max_context_tokens is now optional — unset means 'derive from num_ctx'."""
|
|
206
|
+
from ayder_cli.core.config import ContextManagerConfigSection
|
|
207
|
+
|
|
208
|
+
assert ContextManagerConfigSection().max_context_tokens is None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def test_budget_syncs_from_num_ctx_when_max_context_tokens_unset():
|
|
212
|
+
"""When max_context_tokens is unset, the budget tracks the model's num_ctx."""
|
|
213
|
+
from types import SimpleNamespace
|
|
214
|
+
from ayder_cli.core.config import ContextManagerConfigSection
|
|
215
|
+
|
|
216
|
+
cfg = SimpleNamespace(
|
|
217
|
+
context_manager=ContextManagerConfigSection(), # max_context_tokens=None
|
|
218
|
+
num_ctx=131072,
|
|
219
|
+
provider="openai",
|
|
220
|
+
model="x",
|
|
221
|
+
)
|
|
222
|
+
mgr = DefaultContextManager(cfg)
|
|
223
|
+
|
|
224
|
+
assert mgr._max_context_tokens == 131072
|
|
225
|
+
assert mgr.reserve == int(131072 * 0.30)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def test_budget_fallback_to_default_logs_warning(caplog):
|
|
229
|
+
"""When neither max_context_tokens nor num_ctx is readable, warn + use default."""
|
|
230
|
+
import logging
|
|
231
|
+
from ayder_cli.core.config import ContextManagerConfigSection
|
|
232
|
+
from ayder_cli.core.default_context_manager import DEFAULT_MAX_CONTEXT_TOKENS
|
|
233
|
+
|
|
234
|
+
config = ContextManagerConfigSection() # max_context_tokens=None, no num_ctx attr
|
|
235
|
+
with caplog.at_level(logging.WARNING):
|
|
236
|
+
mgr = DefaultContextManager(config)
|
|
237
|
+
|
|
238
|
+
assert mgr._max_context_tokens == DEFAULT_MAX_CONTEXT_TOKENS
|
|
239
|
+
assert "num_ctx" in caplog.text
|
|
240
|
+
assert str(DEFAULT_MAX_CONTEXT_TOKENS) in caplog.text
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def test_budget_derived_from_num_ctx_logs_info(caplog):
|
|
244
|
+
"""Deriving the budget from num_ctx is reported at INFO with both values."""
|
|
245
|
+
import logging
|
|
246
|
+
from types import SimpleNamespace
|
|
247
|
+
from ayder_cli.core.config import ContextManagerConfigSection
|
|
248
|
+
|
|
249
|
+
cfg = SimpleNamespace(
|
|
250
|
+
context_manager=ContextManagerConfigSection(),
|
|
251
|
+
num_ctx=131072,
|
|
252
|
+
provider="openai",
|
|
253
|
+
model="x",
|
|
254
|
+
)
|
|
255
|
+
with caplog.at_level(logging.INFO):
|
|
256
|
+
DefaultContextManager(cfg)
|
|
257
|
+
|
|
258
|
+
assert "131072" in caplog.text
|
|
259
|
+
assert "num_ctx" in caplog.text
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def test_explicit_max_context_tokens_overrides_num_ctx():
|
|
263
|
+
"""An explicit max_context_tokens wins over num_ctx."""
|
|
264
|
+
from types import SimpleNamespace
|
|
265
|
+
from ayder_cli.core.config import ContextManagerConfigSection
|
|
266
|
+
|
|
267
|
+
cfg = SimpleNamespace(
|
|
268
|
+
context_manager=ContextManagerConfigSection(max_context_tokens=4096),
|
|
269
|
+
num_ctx=131072,
|
|
270
|
+
provider="openai",
|
|
271
|
+
model="x",
|
|
272
|
+
)
|
|
273
|
+
mgr = DefaultContextManager(cfg)
|
|
274
|
+
|
|
275
|
+
assert mgr._max_context_tokens == 4096
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def test_enable_compression_defaults_off():
|
|
279
|
+
"""Compression must be opt-in: the global flag defaults to False."""
|
|
280
|
+
from ayder_cli.core.config import ContextManagerConfigSection
|
|
281
|
+
|
|
282
|
+
assert ContextManagerConfigSection().enable_compression is False
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def test_compress_old_results_noop_when_compression_disabled():
|
|
286
|
+
"""With the default (disabled) flag, old long tool results are NOT compressed."""
|
|
287
|
+
from ayder_cli.core.config import ContextManagerConfigSection
|
|
288
|
+
|
|
289
|
+
config = ContextManagerConfigSection(max_context_tokens=1000)
|
|
290
|
+
mgr, original = _make_old_long_tool_result_manager(config)
|
|
291
|
+
|
|
292
|
+
compressed = mgr.compress_old_results()
|
|
293
|
+
|
|
294
|
+
assert compressed == 0
|
|
295
|
+
assert mgr._messages[2]["content"] == original # untouched
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def test_compress_old_results_runs_when_compression_enabled():
|
|
299
|
+
"""When enable_compression=True, eligible old long tool results are compressed."""
|
|
300
|
+
from ayder_cli.core.config import ContextManagerConfigSection
|
|
301
|
+
|
|
302
|
+
config = ContextManagerConfigSection(
|
|
303
|
+
max_context_tokens=1000, enable_compression=True
|
|
304
|
+
)
|
|
305
|
+
mgr, original = _make_old_long_tool_result_manager(config)
|
|
306
|
+
|
|
307
|
+
compressed = mgr.compress_old_results()
|
|
308
|
+
|
|
309
|
+
assert compressed == 1
|
|
310
|
+
assert mgr._messages[2]["content"] != original
|
|
311
|
+
assert "truncated" in mgr._messages[2]["content"].lower()
|
|
312
|
+
|
|
313
|
+
|
|
181
314
|
def test_assign_tier_user_messages_never_old_assistant():
|
|
182
315
|
"""_assign_tier must not assign OLD_ASSISTANT to old user messages."""
|
|
183
316
|
from ayder_cli.core.config import ContextManagerConfigSection
|
|
@@ -204,6 +204,39 @@ async def test_keep_alive_set_to_infinite():
|
|
|
204
204
|
assert call_kwargs.kwargs.get("keep_alive") == -1 or call_kwargs[1].get("keep_alive") == -1
|
|
205
205
|
|
|
206
206
|
|
|
207
|
+
@pytest.mark.asyncio
|
|
208
|
+
async def test_native_stream_does_not_force_num_ctx():
|
|
209
|
+
"""Provider must let Ollama derive context from the model, not force num_ctx.
|
|
210
|
+
|
|
211
|
+
Recent Ollama versions size the context window from the model itself, so the
|
|
212
|
+
provider should not override it via options.
|
|
213
|
+
"""
|
|
214
|
+
cfg = make_config()
|
|
215
|
+
|
|
216
|
+
async def mock_stream(*args, **kwargs):
|
|
217
|
+
yield make_chat_response(content="ok", done=True,
|
|
218
|
+
prompt_eval_count=1, eval_count=1,
|
|
219
|
+
prompt_eval_duration=10, eval_duration=10,
|
|
220
|
+
load_duration=0)
|
|
221
|
+
|
|
222
|
+
with patch("ayder_cli.providers.impl.ollama.AsyncClient") as MockClient:
|
|
223
|
+
instance = AsyncMock()
|
|
224
|
+
instance.chat.return_value = mock_stream()
|
|
225
|
+
MockClient.return_value = instance
|
|
226
|
+
|
|
227
|
+
provider = OllamaProvider(cfg)
|
|
228
|
+
async for _ in provider.stream_with_tools(
|
|
229
|
+
messages=[{"role": "user", "content": "test"}],
|
|
230
|
+
model="test:latest",
|
|
231
|
+
tools=None,
|
|
232
|
+
options={"num_ctx": 4096},
|
|
233
|
+
):
|
|
234
|
+
pass
|
|
235
|
+
|
|
236
|
+
sent_options = instance.chat.call_args.kwargs.get("options")
|
|
237
|
+
assert not (sent_options and "num_ctx" in sent_options)
|
|
238
|
+
|
|
239
|
+
|
|
207
240
|
# =============================================================================
|
|
208
241
|
# SDK Compliance Regression Tests
|
|
209
242
|
# =============================================================================
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|