ayder-cli 2.1.1__tar.gz → 2.1.4__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.4}/PKG-INFO +3 -1
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/docs/config.toml.example +53 -8
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/pyproject.toml +2 -1
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/config.py +38 -4
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/default_context_manager.py +48 -12
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/ollama_context_manager.py +2 -2
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama.py +4 -6
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/themes/claude.py +27 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/themes/original.py +21 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/definition.py +10 -4
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/plugin_manager.py +9 -4
- ayder_cli-2.1.4/src/ayder_cli/tools/plugin_status.py +33 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/app.py +81 -27
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/commands.py +38 -8
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/keybindings.py +3 -3
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/widgets.py +147 -14
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_config_coverage.py +13 -9
- ayder_cli-2.1.4/tests/core/test_config_provider_logging.py +46 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_default_context_manager.py +133 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_ollama_context_manager.py +15 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_ollama_native.py +33 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_plugin_manager.py +82 -0
- ayder_cli-2.1.4/tests/tools/test_plugin_status.py +36 -0
- ayder_cli-2.1.4/tests/tui/test_input_box.py +114 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_multiline_input.py +37 -40
- ayder_cli-2.1.4/tests/tui/test_status_bar_badges.py +70 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_widgets.py +77 -1
- ayder_cli-2.1.4/tests/ui/test_plugin_autoenable.py +44 -0
- ayder_cli-2.1.4/tests/ui/test_tui_commands_plugin.py +72 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/.github/workflows/ci.yml +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/.github/workflows/python-package.yml +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/.gitignore +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/.python-version +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/AGENTS.md +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/CHANGELOG.md +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/LICENSE +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/README.md +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/docs/PROJECT_STRUCTURE.md +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/docs/README_AGENT_WORKFLOW.md +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/docs/README_OLLAMA.md +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/docs/cc.png +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/__main__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/agents/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/agents/callbacks.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/agents/config.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/agents/registry.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/agents/run.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/agents/runner.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/agents/tool.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/application/README.md +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/application/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/application/execution_policy.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/application/message_contract.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/application/runtime_factory.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/application/validation.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/cli.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/cli_callbacks.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/cli_runner.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/console.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/cache_monitor.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/config_migration.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/context.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/context_manager.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/context_manager_factory.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/result.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/logging_config.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/loops/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/loops/chat_loop.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/parser.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/process_manager.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/prompts.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/base.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/claude.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/deepseek.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/gemini.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/glm.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/_errors.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/base.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/deepseek.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/generic_native.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/generic_xml.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/matrix.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/minimax.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/qwen3.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/registry.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_inspector.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/openai.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/qwen.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/orchestrator.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/retry.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/services/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/services/interactions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/themes/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/context.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/context_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/filesystem.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/filesystem_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/notes.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/notes_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/process_manager_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/search.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/search_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/shell.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/shell_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/tasks.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/tasks_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/utils_tools.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/utils_tools_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/web.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/web_definitions.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/execution.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/hooks.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/normalization.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/plugin_api.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/plugin_github.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/registry.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/schemas.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/utils.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/adapter.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/helpers.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/parser.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/screens.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/theme_manager.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/types.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/ui.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/version.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/COVERAGE.md +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_agent_tools.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_callbacks.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_config.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_integration.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_registry.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_run.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_runner.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_runner_provider_unavailable.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_tool.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/application/test_message_contract.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/application/test_runtime_factory.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/client/test_main.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/convergence/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/convergence/test_execution_policy_parity.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/convergence/test_runtime_wiring.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/convergence/test_validation_path.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_cache_monitor.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_config.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_config_migration.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_config_retry.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_config_v2.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_context_manager_factory.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_default_context_manager_should_compact.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_ollama_context_manager_cache_monitor.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_ollama_context_manager_max_history.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_parameter_aliasing.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_parser.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/loops/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/loops/test_base.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/loops/test_chat_loop_hook.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/loops/test_chat_loop_tool_truncation.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_base.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_deepseek.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_errors.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_fallback.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_generic_native.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_generic_xml.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_matrix.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_minimax.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_provider_integration.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_qwen3.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_registry.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_glm_async.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_ollama_inspector.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_ollama_xml.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_ollama_xml_autoroute.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_ollama_xml_usage.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_orchestrator_capabilities.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_qwen_async.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_retry_classifier.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_retry_integration.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_retrying_provider.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/services/test_boundary.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/services/test_interaction_sink.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/test_cli.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/test_cli_callbacks.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/test_context.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/test_env_manager.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/test_logging_config.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/test_notes.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/test_process_manager.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/test_tasks.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_agent_notes.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_definition_discovery.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_di_injection.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_impl.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_impl_coverage.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_path_security.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_plugin_api.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_plugin_github.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_plugin_loading.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_registry.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_registry_coverage.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_result.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_schemas.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_search_codebase.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_utils.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_web.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/__init__.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_agent_list_screen.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_do_clear_coordination.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_follow_mode.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_help_screen.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_keybindings.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_multi_select_screen.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_paste.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_pending_compact_consumer.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_pull_delivery.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_turn_consumer.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_confirm_screen.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_diff_preview.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_provider_switch.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_run_tui_startup.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_tui_assistant_sanitization.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_tui_chat_loop.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_tui_commands_agent.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_tui_commands_logging.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_tui_commands_provider.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_tui_helpers.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_tui_temporal_command.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_tui_widgets.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_ui.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_ui_coverage.py +0 -0
- {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_ui_verbose.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ayder-cli
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.4
|
|
4
4
|
Summary: AI agent for any LLMs
|
|
5
5
|
Project-URL: Homepage, https://github.com/ayder/ayder-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/ayder/ayder-cli.git
|
|
@@ -34,6 +34,8 @@ Provides-Extra: glm
|
|
|
34
34
|
Requires-Dist: zhipuai; extra == 'glm'
|
|
35
35
|
Provides-Extra: google
|
|
36
36
|
Requires-Dist: google-genai; extra == 'google'
|
|
37
|
+
Provides-Extra: mcp
|
|
38
|
+
Requires-Dist: mcp; extra == 'mcp'
|
|
37
39
|
Provides-Extra: qwen
|
|
38
40
|
Requires-Dist: dashscope; extra == 'qwen'
|
|
39
41
|
Description-Content-Type: text/markdown
|
|
@@ -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"
|
|
@@ -62,7 +82,12 @@ verbose = false
|
|
|
62
82
|
prompt = "STANDARD" # MINIMAL | STANDARD | EXTENDED
|
|
63
83
|
max_background_processes = 5 # cap on concurrent background processes (e.g. test runs)
|
|
64
84
|
max_output_tokens = 8192 # orchestrator response cap
|
|
65
|
-
|
|
85
|
+
# Max conversation messages kept per turn. 0 = unlimited (default): the context
|
|
86
|
+
# manager's token budget + compaction are the bound, so the model uses its real
|
|
87
|
+
# window. A small fixed cap (the old default was 30) thrashes a tiny history
|
|
88
|
+
# window on a large-context model and wastes most of it. Set a finite number
|
|
89
|
+
# only if you want a hard message cap regardless of window size.
|
|
90
|
+
max_history_messages = 0
|
|
66
91
|
agent_timeout = 1800 # seconds before a background agent is cancelled (30 min; reasoning+coding can be slow)
|
|
67
92
|
# Tools the agents inherit. A coding harness needs file/shell/search (core),
|
|
68
93
|
# tasks/notes (metadata), background process control for test suites
|
|
@@ -77,10 +102,16 @@ retention = "7 days"
|
|
|
77
102
|
|
|
78
103
|
[context_manager]
|
|
79
104
|
enabled = true
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
|
|
105
|
+
# Destructive tool-result compression is OPT-IN and OFF by default. It summarizes
|
|
106
|
+
# old tool results in place (lossy) and thrashes the KV cache, so leave it off
|
|
107
|
+
# unless you specifically want it; eviction will replace it in a later release.
|
|
108
|
+
enable_compression = false
|
|
109
|
+
# max_context_tokens is OPTIONAL. When unset (recommended), the budget is derived
|
|
110
|
+
# from the active model's num_ctx. It is read only on the openai/non-ollama path
|
|
111
|
+
# (DefaultContextManager); the native Ollama path auto-detects the real window
|
|
112
|
+
# from /api/show and ignores it. reserve_ratio + compaction_threshold ARE read on
|
|
113
|
+
# both paths.
|
|
114
|
+
# max_context_tokens = 131072
|
|
84
115
|
reserve_ratio = 0.30
|
|
85
116
|
compaction_threshold = 0.80
|
|
86
117
|
|
|
@@ -118,6 +149,20 @@ num_ctx = 32768 # keep modest: local VRAM is the constraint
|
|
|
118
149
|
# model = "gemma4:31b-cloud"
|
|
119
150
|
# num_ctx = 131072
|
|
120
151
|
|
|
152
|
+
# OpenAI-compatible driver. Unlike the `ollama` driver above, this one HONORS the
|
|
153
|
+
# tuning knobs (defaults shown). Use it for api.openai.com, Azure, vLLM,
|
|
154
|
+
# llama.cpp, LM Studio, or Ollama's own `/v1` endpoint. Point an agent at it with
|
|
155
|
+
# `provider = "openai"`.
|
|
156
|
+
# [llm.openai]
|
|
157
|
+
# driver = "openai"
|
|
158
|
+
# base_url = "https://api.openai.com/v1" # OpenAI-compatible endpoint
|
|
159
|
+
# api_key = "sk-..." # your key (or "ollama"/"none" for local servers)
|
|
160
|
+
# model = "gpt-5.5"
|
|
161
|
+
# num_ctx = 65536 # sizes the context-manager budget (default 65536); NOT sent to the API
|
|
162
|
+
# max_output_tokens = 8192 # -> max_tokens / max_completion_tokens (default 4096); keep WELL below num_ctx
|
|
163
|
+
# stop_sequences = [] # -> stop (default [])
|
|
164
|
+
# # temperature / think are NOT applied by the openai driver today (no-ops) — omitted on purpose.
|
|
165
|
+
|
|
121
166
|
# =============================================================================
|
|
122
167
|
# Agents
|
|
123
168
|
# =============================================================================
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ayder-cli"
|
|
7
|
-
version = "2.1.
|
|
7
|
+
version = "2.1.4"
|
|
8
8
|
description = "AI agent for any LLMs"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -37,6 +37,7 @@ google = ["google-genai"]
|
|
|
37
37
|
qwen = ["dashscope"]
|
|
38
38
|
glm = ["zhipuai"]
|
|
39
39
|
all = ["ayder-cli[anthropic,google,qwen,glm]"]
|
|
40
|
+
mcp = ["mcp"]
|
|
40
41
|
|
|
41
42
|
[project.urls]
|
|
42
43
|
Homepage = "https://github.com/ayder/ayder-cli"
|
|
@@ -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
|
|
|
@@ -278,7 +288,10 @@ class Config(BaseModel):
|
|
|
278
288
|
logging_retention: str = Field(default="7 days")
|
|
279
289
|
max_background_processes: int = Field(default=5)
|
|
280
290
|
max_output_tokens: int = Field(default=4096)
|
|
281
|
-
|
|
291
|
+
# 0 = unlimited: let the context manager's token budget + compaction be the
|
|
292
|
+
# bound. The old fixed 30 predates large-context models and thrashes a tiny
|
|
293
|
+
# history window on a big context.
|
|
294
|
+
max_history_messages: int = Field(default=0)
|
|
282
295
|
prompt: str = Field(default="STANDARD")
|
|
283
296
|
chat_protocol: str = Field(default="ollama")
|
|
284
297
|
stop_sequences: list[str] = Field(default_factory=list)
|
|
@@ -309,9 +322,25 @@ class Config(BaseModel):
|
|
|
309
322
|
if isinstance(llm_section, dict):
|
|
310
323
|
profile = llm_section.get(provider)
|
|
311
324
|
if not isinstance(profile, dict):
|
|
325
|
+
available = ", ".join(sorted(llm_section)) or "<none>"
|
|
312
326
|
default_profile = llm_section.get(DEFAULTS["provider"])
|
|
313
327
|
if isinstance(default_profile, dict):
|
|
314
328
|
profile = default_profile
|
|
329
|
+
logger.error(
|
|
330
|
+
"Provider %r has no [llm.%s] profile; falling back to the "
|
|
331
|
+
"default [llm.%s] profile. Available profiles: %s. Fix the "
|
|
332
|
+
"provider name or add an [llm.%s] section.",
|
|
333
|
+
provider, provider, DEFAULTS["provider"], available, provider,
|
|
334
|
+
)
|
|
335
|
+
else:
|
|
336
|
+
logger.error(
|
|
337
|
+
"Provider %r has no [llm.%s] profile and no default "
|
|
338
|
+
"[llm.%s] profile exists; the driver will default to "
|
|
339
|
+
"'openai' with built-in endpoint defaults. Available "
|
|
340
|
+
"profiles: %s. Fix the provider name or add an [llm.%s] "
|
|
341
|
+
"section.",
|
|
342
|
+
provider, provider, DEFAULTS["provider"], available, provider,
|
|
343
|
+
)
|
|
315
344
|
if isinstance(profile, dict):
|
|
316
345
|
new_data.update(profile)
|
|
317
346
|
new_data.pop("llm", None)
|
|
@@ -359,6 +388,11 @@ class Config(BaseModel):
|
|
|
359
388
|
"qwen", "dashscope", "glm", "zhipu",
|
|
360
389
|
)
|
|
361
390
|
if v not in valid:
|
|
391
|
+
logger.error(
|
|
392
|
+
"Unsupported driver %r; must be one of: %s. Fix the driver in "
|
|
393
|
+
"your [llm.<provider>] profile.",
|
|
394
|
+
v, ", ".join(valid),
|
|
395
|
+
)
|
|
362
396
|
raise ValueError(f"driver must be one of: {', '.join(valid)}")
|
|
363
397
|
return v
|
|
364
398
|
|
|
@@ -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():
|
|
@@ -237,8 +237,8 @@ class OllamaContextManager:
|
|
|
237
237
|
cache_state = f", cache={status.state}({status.hit_ratio:.0%})"
|
|
238
238
|
|
|
239
239
|
logger.info(
|
|
240
|
-
"Context: %d/%d tokens (%.0f%%), completion=%d, speed=%s%s",
|
|
241
|
-
self._real_prompt_tokens, ceiling, utilization,
|
|
240
|
+
"Context[%s]: %d/%d tokens (%.0f%%), completion=%d, speed=%s%s",
|
|
241
|
+
self._model or "?", self._real_prompt_tokens, ceiling, utilization,
|
|
242
242
|
self._real_completion_tokens, prompt_speed, cache_state,
|
|
243
243
|
)
|
|
244
244
|
|
|
@@ -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,
|
|
@@ -633,6 +633,27 @@ AgentPanel .agent-detail {
|
|
|
633
633
|
padding: 0 0 0 4;
|
|
634
634
|
}
|
|
635
635
|
|
|
636
|
+
/* ThinkingPanel - Scrollable panel showing the model's reasoning stream.
|
|
637
|
+
Toggled with Ctrl+T. Taller than the tool/agent panels because reasoning is
|
|
638
|
+
long-form. Targets 'ThinkingPanel' in widgets.py. */
|
|
639
|
+
ThinkingPanel {
|
|
640
|
+
layout: vertical;
|
|
641
|
+
height: auto;
|
|
642
|
+
max-height: 60%;
|
|
643
|
+
background: #0a0a18;
|
|
644
|
+
border-top: solid #333350;
|
|
645
|
+
padding: 1 1;
|
|
646
|
+
overflow-y: scroll;
|
|
647
|
+
display: none;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
ThinkingPanel .thinking-content {
|
|
651
|
+
height: auto;
|
|
652
|
+
color: #8888aa;
|
|
653
|
+
text-style: italic;
|
|
654
|
+
padding: 0 1;
|
|
655
|
+
}
|
|
656
|
+
|
|
636
657
|
/* AyderApp - The root application screen. */
|
|
637
658
|
Screen {
|
|
638
659
|
layout: vertical;
|
|
@@ -669,6 +690,12 @@ Screen:inline {
|
|
|
669
690
|
width: 100%;
|
|
670
691
|
}
|
|
671
692
|
|
|
693
|
+
#thinking-panel {
|
|
694
|
+
min-height: 0;
|
|
695
|
+
max-height: 60%;
|
|
696
|
+
width: 100%;
|
|
697
|
+
}
|
|
698
|
+
|
|
672
699
|
#input-bar {
|
|
673
700
|
height: auto;
|
|
674
701
|
min-height: 1;
|
|
@@ -514,6 +514,27 @@ ToolPanel .tool-item.completed {
|
|
|
514
514
|
color: $success;
|
|
515
515
|
}
|
|
516
516
|
|
|
517
|
+
/* ThinkingPanel - Scrollable reasoning stream, toggled with Ctrl+T.
|
|
518
|
+
Taller than the tool/agent panels because reasoning is long-form. */
|
|
519
|
+
ThinkingPanel {
|
|
520
|
+
layout: vertical;
|
|
521
|
+
height: auto;
|
|
522
|
+
min-height: 6;
|
|
523
|
+
max-height: 24;
|
|
524
|
+
background: $surface-darken-1;
|
|
525
|
+
border-top: heavy $primary;
|
|
526
|
+
padding: 1 1;
|
|
527
|
+
overflow-y: scroll;
|
|
528
|
+
display: none;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
ThinkingPanel .thinking-content {
|
|
532
|
+
height: auto;
|
|
533
|
+
color: $text-muted;
|
|
534
|
+
text-style: italic;
|
|
535
|
+
padding: 0 1;
|
|
536
|
+
}
|
|
537
|
+
|
|
517
538
|
/* AyderApp - Main application */
|
|
518
539
|
Screen {
|
|
519
540
|
layout: vertical;
|
|
@@ -168,7 +168,15 @@ def _discover_definitions() -> Tuple["ToolDefinition", ...]:
|
|
|
168
168
|
|
|
169
169
|
_BUILTIN_DEFINITIONS: Tuple[ToolDefinition, ...] = _discover_definitions()
|
|
170
170
|
|
|
171
|
-
# Phase 1: Merge global plugins at import time
|
|
171
|
+
# Phase 1: Merge global plugins at import time.
|
|
172
|
+
#
|
|
173
|
+
# Discovery runs *inside this module's own body*, so a plugin whose definitions
|
|
174
|
+
# module reads ``TOOL_DEFINITIONS`` at import time (e.g. to avoid tool-name
|
|
175
|
+
# collisions, as mcp-tool does) would otherwise hit a circular import — the name
|
|
176
|
+
# isn't bound yet. Bind it provisionally to the builtins first; those are the
|
|
177
|
+
# only definitions that exist during discovery anyway. It is reassigned to the
|
|
178
|
+
# full set (builtins + plugins) immediately after discovery completes.
|
|
179
|
+
TOOL_DEFINITIONS: Tuple[ToolDefinition, ...] = _BUILTIN_DEFINITIONS
|
|
172
180
|
_GLOBAL_PLUGIN_DEFS: Tuple[ToolDefinition, ...] = ()
|
|
173
181
|
_PLUGIN_HANDLERS: Dict[str, Any] = {}
|
|
174
182
|
|
|
@@ -179,9 +187,7 @@ try:
|
|
|
179
187
|
except Exception as e:
|
|
180
188
|
logger.warning(f"Failed to load global plugins: {e}")
|
|
181
189
|
|
|
182
|
-
TOOL_DEFINITIONS
|
|
183
|
-
_BUILTIN_DEFINITIONS + _GLOBAL_PLUGIN_DEFS
|
|
184
|
-
)
|
|
190
|
+
TOOL_DEFINITIONS = _BUILTIN_DEFINITIONS + _GLOBAL_PLUGIN_DEFS
|
|
185
191
|
TOOL_DEFINITIONS_BY_NAME: Dict[str, ToolDefinition] = {
|
|
186
192
|
td.name: td for td in TOOL_DEFINITIONS
|
|
187
193
|
}
|
|
@@ -147,10 +147,15 @@ def install_plugin_from_local(
|
|
|
147
147
|
"Use --force to overwrite."
|
|
148
148
|
)
|
|
149
149
|
|
|
150
|
-
# Copy plugin files
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
150
|
+
# Copy plugin files. When the source IS the install target — e.g. reinstalling
|
|
151
|
+
# an already-installed plugin from its own directory with --force, which is
|
|
152
|
+
# exactly what the missing-dependency warning recommends — the files are
|
|
153
|
+
# already in place. Skip the copy so we don't rmtree the source out from under
|
|
154
|
+
# ourselves (the dependency reinstall + registry refresh below still run).
|
|
155
|
+
if source_dir.resolve() != target_dir.resolve():
|
|
156
|
+
if target_dir.exists():
|
|
157
|
+
shutil.rmtree(target_dir)
|
|
158
|
+
shutil.copytree(source_dir, target_dir)
|
|
154
159
|
|
|
155
160
|
# Update plugins.json
|
|
156
161
|
data = read_plugins_json(plugins_dir)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Plugin status-badge registry.
|
|
2
|
+
|
|
3
|
+
A lightweight, process-global registry that plugins use to publish a status
|
|
4
|
+
badge (a short label plus a colour) for display in the TUI status bar. The
|
|
5
|
+
mcp-tool plugin, for example, calls ``set_status("mcp", "MCP: news-digest",
|
|
6
|
+
"green")`` once it connects to its MCP servers, and ``set_status("mcp", "MCP",
|
|
7
|
+
"red")`` when configured servers are unreachable.
|
|
8
|
+
|
|
9
|
+
The TUI ``StatusBar`` reads :func:`get_all` to render the badges. Keeping this
|
|
10
|
+
in ayder core (rather than inside a plugin) means the badge survives even when
|
|
11
|
+
the plugin's own modules are loaded under throwaway names during discovery, and
|
|
12
|
+
gives the plugin a stable import target instead of a no-op fallback.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
# plugin name -> (label, colour). Colour is a Rich colour/style name ("green").
|
|
18
|
+
_status: dict[str, tuple[str, str]] = {}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def set_status(name: str, label: str, color: str) -> None:
|
|
22
|
+
"""Register or update a plugin's status badge."""
|
|
23
|
+
_status[name] = (label, color)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_all() -> dict[str, tuple[str, str]]:
|
|
27
|
+
"""Return a copy of all registered status badges (name -> (label, color))."""
|
|
28
|
+
return dict(_status)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def clear() -> None:
|
|
32
|
+
"""Remove all registered badges (primarily for tests)."""
|
|
33
|
+
_status.clear()
|