ayder-cli 2.3.1__tar.gz → 2.4.0__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.3.1 → ayder_cli-2.4.0}/AGENTS.md +17 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/PKG-INFO +7 -1
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/README.md +6 -0
- ayder_cli-2.4.0/docs/GATE.md +28 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/docs/PROJECT_STRUCTURE.md +2 -1
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/docs/config.toml.example +9 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/pyproject.toml +1 -1
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/scripts/check_exception_handlers.py +1 -1
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/scripts/check_log_arguments.py +4 -4
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/core/config.py +10 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/core/config_migration.py +2 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/base.py +2 -1
- ayder_cli-2.4.0/src/ayder_cli/providers/evren.py +61 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/openai.py +4 -9
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/themes/ayder.py +37 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/themes/claude.py +37 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tui/commands.py +41 -2
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tui/screens.py +38 -3
- ayder_cli-2.4.0/tests/core/test_default_config_evren.py +44 -0
- ayder_cli-2.4.0/tests/providers/test_evren.py +79 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/test_ollama_native.py +16 -0
- ayder_cli-2.4.0/tests/providers/test_openai_list_models.py +47 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/scripts/test_exception_gate.py +7 -4
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/scripts/test_log_argument_gate.py +6 -6
- ayder_cli-2.4.0/tests/tui/test_evren_terms.py +230 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/uv.lock +1 -1
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/.github/workflows/ci.yml +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/.github/workflows/python-package.yml +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/.gitignore +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/.python-version +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/LICENSE +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/docs/README_AGENT_WORKFLOW.md +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/docs/README_OLLAMA.md +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/docs/cc.png +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/docs/superpowers/plans/2026-06-26-file-editor-improvements.md +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/docs/superpowers/plans/2026-06-26-session-save-resume.md +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/docs/superpowers/specs/2026-06-26-file-editor-improvements-design.md +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/docs/superpowers/specs/2026-06-26-session-save-resume-design.md +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/scripts/check_logging_imports.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/scripts/exception_baseline.txt +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/scripts/find_percent_logging.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/scripts/log_argument_baseline.txt +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/scripts/logging_gates.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/__main__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/agents/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/agents/callbacks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/agents/config.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/agents/registry.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/agents/run.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/agents/runner.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/agents/tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/agents/worktree.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/application/README.md +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/application/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/application/execution_policy.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/application/message_contract.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/application/runtime_factory.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/application/validation.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/cli.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/cli_callbacks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/cli_runner.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/console.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/core/cache_monitor.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/core/context.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/core/context_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/core/context_manager_factory.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/core/default_context_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/core/ollama_context_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/core/reasoning.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/core/result.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/core/session.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/diagnostics.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/log.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/logging_config.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/loops/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/loops/chat_loop.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/masking.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/parser.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/process_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/prompts.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/claude.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/deepseek.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/gemini.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/glm.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/ollama.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/ollama_drivers/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/ollama_drivers/_errors.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/ollama_drivers/base.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/ollama_drivers/deepseek.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/ollama_drivers/generic_native.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/ollama_drivers/generic_xml.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/ollama_drivers/matrix.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/ollama_drivers/minimax.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/ollama_drivers/qwen3.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/ollama_drivers/registry.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/ollama_inspector.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/impl/qwen.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/orchestrator.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/providers/retry.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/services/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/services/interactions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/services/messaging_inbox.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/themes/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/background_process_tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/context.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/context_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/filesystem.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/filesystem_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/note_tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/notes.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/notes_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/process_manager_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/search.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/search_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/shell.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/shell_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/skill.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/skill_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/task_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/task_tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/tasks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/utils_tools.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/utils_tools_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/web.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/builtins/web_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/definition.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/execution.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/hooks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/mcp.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/mcp_client.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/normalization.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/plugin_api.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/plugin_github.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/plugin_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/plugin_status.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/registry.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/schemas.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tools/utils.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tui/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tui/adapter.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tui/app.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tui/helpers.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tui/keybindings.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tui/parser.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tui/rendering.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tui/theme_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tui/types.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/tui/widgets.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/ui.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/src/ayder_cli/version.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/COVERAGE.md +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/agents/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/agents/test_agent_consolidated.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/agents/test_agent_severity.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/agents/test_agent_tools.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/agents/test_callbacks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/agents/test_config.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/agents/test_integration.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/agents/test_queue.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/agents/test_registry.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/agents/test_run.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/agents/test_runner.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/agents/test_runner_provider_unavailable.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/agents/test_tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/agents/test_worktree_helpers.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/agents/test_worktree_isolation.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/application/test_execution_result_truthfulness.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/application/test_message_contract.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/application/test_runtime_factory.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/client/test_main.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/conftest.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/convergence/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/convergence/test_execution_policy_parity.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/convergence/test_runtime_wiring.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/convergence/test_validation_path.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_asyncio_bridge.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_cache_monitor.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_config.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_config_coverage.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_config_migration.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_config_provider_logging.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_config_retry.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_config_v2.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_context_manager_factory.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_default_context_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_default_context_manager_should_compact.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_event_emission.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_event_stream.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_exception_guarantee.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_exception_hooks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_log_facade.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_logging_config_keys.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_logging_sinks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_loguru_caplog.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_ollama_context_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_ollama_context_manager_cache_monitor.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_ollama_context_manager_max_history.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_parameter_aliasing.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_parser.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_reasoning_config.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_session.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_session_id_threading.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/core/test_sink_masking.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/loops/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/loops/test_base.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/loops/test_chat_loop_hook.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/loops/test_chat_loop_tool_truncation.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/loops/test_loop_severity.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/loops/test_required_args_aliases.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/ollama_drivers/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/ollama_drivers/test_base.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/ollama_drivers/test_deepseek.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/ollama_drivers/test_errors.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/ollama_drivers/test_fallback.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/ollama_drivers/test_generic_native.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/ollama_drivers/test_generic_xml.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/ollama_drivers/test_matrix.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/ollama_drivers/test_minimax.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/ollama_drivers/test_provider_integration.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/ollama_drivers/test_qwen3.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/ollama_drivers/test_registry.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/test_glm_async.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/test_ollama_inspector.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/test_ollama_parallel_tool_call_ids.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/test_ollama_xml.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/test_ollama_xml_autoroute.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/test_ollama_xml_usage.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/test_orchestrator_capabilities.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/test_provider_channel_binding.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/test_provider_severity.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/test_qwen_async.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/test_reasoning_effort.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/test_retry_classifier.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/test_retry_integration.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/providers/test_retrying_provider.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/scripts/test_logging_policy_gates.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/scripts/test_loguru_private_shape.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/services/test_boundary.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/services/test_interaction_sink.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/services/test_messaging_inbox.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/test_cli.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/test_cli_callbacks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/test_context.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/test_env_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/test_logging_config.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/test_notes.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/test_process_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/test_tasks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_agent_notes.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_background_process_tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_bash_tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_definition_discovery.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_di_injection.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_file_editor_hardening.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_file_editor_size_limit.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_impl.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_impl_coverage.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_mcp.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_mcp_client.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_note_tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_notes_api.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_path_security.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_plugin_api.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_plugin_github.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_plugin_loading.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_plugin_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_plugin_status.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_registry.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_registry_coverage.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_result.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_schemas.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_search_codebase.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_search_hardening.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_skill_tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_task_tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_tool_severity.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_unexpected_args.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_utils.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tools/test_web.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_agent_list_screen.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_do_clear_coordination.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_follow_mode.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_help_screen.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_input_box.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_keybindings.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_multi_select_screen.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_multiline_input.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_paste.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_peer_message_turn.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_pending_compact_consumer.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_pull_delivery.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_resume.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_status_bar_badges.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_themes.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_turn_consumer.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/tui/test_widgets.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_confirm_screen.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_diff_preview.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_plugin_autoenable.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_provider_switch.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_run_tui_startup.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_tui_assistant_sanitization.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_tui_chat_loop.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_tui_commands_agent.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_tui_commands_effort.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_tui_commands_logging.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_tui_commands_plugin.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_tui_commands_provider.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_tui_commands_skill.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_tui_helpers.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_tui_temporal_command.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_tui_widgets.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_ui.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_ui_coverage.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.0}/tests/ui/test_ui_verbose.py +0 -0
|
@@ -87,6 +87,7 @@ ayder-cli/
|
|
|
87
87
|
│ ├── providers/ # LLM provider implementations
|
|
88
88
|
│ │ ├── base.py # AIProvider protocol, NormalizedStreamChunk
|
|
89
89
|
│ │ ├── orchestrator.py # Factory: create(config) → provider
|
|
90
|
+
│ │ ├── evren.py # evren gateway terms-of-use acceptance (/model)
|
|
90
91
|
│ │ └── impl/ # Individual provider drivers
|
|
91
92
|
│ │ ├── openai.py # OpenAIProvider (base for most drivers)
|
|
92
93
|
│ │ ├── ollama.py # OllamaProvider (native + XML fallback)
|
|
@@ -385,6 +386,22 @@ uv run poe test-cov # With coverage report
|
|
|
385
386
|
- **Coverage**: Maintain existing coverage levels
|
|
386
387
|
- **Timeout**: Use `--timeout=30` to prevent hanging tests
|
|
387
388
|
|
|
389
|
+
### Test Definitions
|
|
390
|
+
|
|
391
|
+
Plans and reviews use these values.
|
|
392
|
+
|
|
393
|
+
- **Kinds**
|
|
394
|
+
- `baseline regression` — fails on the parent commit for a named reason, passes on the fix.
|
|
395
|
+
- `new interface` — fails at its assertion once a minimal callable version exists (never on an `ImportError`).
|
|
396
|
+
- `preservation` — passes before and after the change; its assertion depends on the behaviour it preserves.
|
|
397
|
+
- **Layers**
|
|
398
|
+
- `unit` — a function or class, without a Textual app.
|
|
399
|
+
- `tui` — a Textual app driven under `App.run_test()`.
|
|
400
|
+
- **Environment**
|
|
401
|
+
- `local-faked` — the only one. No network: HTTP is faked with `httpx.MockTransport` passed to the SDK client; other I/O uses `unittest.mock`. No test reaches a real provider.
|
|
402
|
+
|
|
403
|
+
The gate procedure lives in [docs/GATE.md](docs/GATE.md).
|
|
404
|
+
|
|
388
405
|
### Writing Tests
|
|
389
406
|
|
|
390
407
|
```python
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ayder-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
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
|
|
@@ -155,6 +155,8 @@ in the core install.
|
|
|
155
155
|
|
|
156
156
|
ayder-cli uses a flexible profile-based configuration system. On the first run, it creates a config file at `~/.ayder/config.toml`.
|
|
157
157
|
|
|
158
|
+
The generated file includes an `[llm.evren]` profile for the evren gateway (`https://evren-llmapi.ssyz.org.tr/v1`) with an empty `api_key`. Fill in your key and switch to it with `/provider evren`.
|
|
159
|
+
|
|
158
160
|
**Key Concepts:**
|
|
159
161
|
- **Profile Name:** A custom named section (e.g., `[llm.my_ollama]`). You can define as many profiles as you want.
|
|
160
162
|
- **Driver:** The underlying native SDK or adapter used by the profile (`ollama`, `openai`, `anthropic`, `google`, `deepseek`, `qwen`, or `glm`). Each driver guarantees full support for native tool calling and streaming.
|
|
@@ -319,6 +321,10 @@ Once a profile is active, use `/model` in the TUI to swap models on the fly:
|
|
|
319
321
|
- **Interactive Picker:** `/model` with no arguments queries the active driver for available models and opens a picker.
|
|
320
322
|
- **Direct Switch:** `/model <model-name>` immediately switches to that model.
|
|
321
323
|
|
|
324
|
+
If the provider rejects the listing request, `/model` shows the server's error.
|
|
325
|
+
|
|
326
|
+
For the evren gateway (`evren-llmapi.ssyz.org.tr`), if your account has not accepted the current terms of use, `/model` shows them and asks you to Approve or Deny; Approve accepts them on the server.
|
|
327
|
+
|
|
322
328
|
Changes made with `/model` apply to the current session only. To make a model the permanent default, update `model = "..."` in your `config.toml`.
|
|
323
329
|
|
|
324
330
|
## Usage
|
|
@@ -112,6 +112,8 @@ in the core install.
|
|
|
112
112
|
|
|
113
113
|
ayder-cli uses a flexible profile-based configuration system. On the first run, it creates a config file at `~/.ayder/config.toml`.
|
|
114
114
|
|
|
115
|
+
The generated file includes an `[llm.evren]` profile for the evren gateway (`https://evren-llmapi.ssyz.org.tr/v1`) with an empty `api_key`. Fill in your key and switch to it with `/provider evren`.
|
|
116
|
+
|
|
115
117
|
**Key Concepts:**
|
|
116
118
|
- **Profile Name:** A custom named section (e.g., `[llm.my_ollama]`). You can define as many profiles as you want.
|
|
117
119
|
- **Driver:** The underlying native SDK or adapter used by the profile (`ollama`, `openai`, `anthropic`, `google`, `deepseek`, `qwen`, or `glm`). Each driver guarantees full support for native tool calling and streaming.
|
|
@@ -276,6 +278,10 @@ Once a profile is active, use `/model` in the TUI to swap models on the fly:
|
|
|
276
278
|
- **Interactive Picker:** `/model` with no arguments queries the active driver for available models and opens a picker.
|
|
277
279
|
- **Direct Switch:** `/model <model-name>` immediately switches to that model.
|
|
278
280
|
|
|
281
|
+
If the provider rejects the listing request, `/model` shows the server's error.
|
|
282
|
+
|
|
283
|
+
For the evren gateway (`evren-llmapi.ssyz.org.tr`), if your account has not accepted the current terms of use, `/model` shows them and asks you to Approve or Deny; Approve accepts them on the server.
|
|
284
|
+
|
|
279
285
|
Changes made with `/model` apply to the current session only. To make a model the permanent default, update `model = "..."` in your `config.toml`.
|
|
280
286
|
|
|
281
287
|
## Usage
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Gate
|
|
2
|
+
|
|
3
|
+
The procedure an agent follows to gate a candidate commit. Follow the steps in order and do
|
|
4
|
+
not interpret the results. Never edit a file, fix a failure, skip a step or create a worktree.
|
|
5
|
+
Test definitions live in [AGENTS.md](../AGENTS.md#test-definitions).
|
|
6
|
+
|
|
7
|
+
Input: the candidate SHA.
|
|
8
|
+
|
|
9
|
+
1. `git rev-parse HEAD` → expected: exactly the candidate SHA.
|
|
10
|
+
2. `git status --porcelain` → expected: empty output.
|
|
11
|
+
3. `uv run poe check-all` → expected: exit code 0. This runs lint, typecheck, the logging
|
|
12
|
+
gates and the test suite, and stops at the first failing task.
|
|
13
|
+
4. Copy the final pytest summary line (`=== N passed, M skipped in …s ===`) verbatim.
|
|
14
|
+
|
|
15
|
+
Last measured: `2494 passed, 2 skipped` on `feat/evren-terms` with the evren default profile (2026-09-29).
|
|
16
|
+
|
|
17
|
+
## Report format
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
SHA: <candidate SHA>
|
|
21
|
+
1 git rev-parse HEAD: <output> (match|MISMATCH)
|
|
22
|
+
2 git status --porcelain: <empty|output>
|
|
23
|
+
3 uv run poe check-all: exit <code>
|
|
24
|
+
4 summary: <pytest summary line verbatim>
|
|
25
|
+
GATE COMPLETE | GATE FAILED at step <n>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`GATE COMPLETE` only when steps 1–3 meet their expected results.
|
|
@@ -220,7 +220,7 @@ cli.py:main()
|
|
|
220
220
|
| `tui/adapter.py` | Adapter glue between `AyderApp` and `ChatLoop` | callback wiring helpers |
|
|
221
221
|
| `tui/commands.py` | Slash command handlers | `COMMAND_MAP`, `handle_*()` |
|
|
222
222
|
| `tui/widgets.py` | Custom widgets | `ChatView`, `ToolPanel`, `CLIInputBar`, `StatusBar`, `AutoCompleteInput` |
|
|
223
|
-
| `tui/screens.py` | Modal screens | `AgentListScreen`, `CLIConfirmScreen`, `CLIHelpScreen`, `CLIMultiSelectScreen`, `CLIPermissionScreen`, `CLISafeModeScreen`, `CLISelectScreen`, `TaskEditScreen` |
|
|
223
|
+
| `tui/screens.py` | Modal screens | `AgentListScreen`, `CLIConfirmScreen`, `CLIHelpScreen`, `CLIMultiSelectScreen`, `CLIPermissionScreen`, `CLISafeModeScreen`, `CLISelectScreen`, `EvrenTermsScreen`, `TaskEditScreen` |
|
|
224
224
|
| `tui/parser.py` | TUI-specific parsing | `content_processor()` |
|
|
225
225
|
| `tui/helpers.py` | UI helpers | `create_tui_banner()` |
|
|
226
226
|
| `tui/keybindings.py` | Keybinding declarations | `BINDINGS` |
|
|
@@ -242,6 +242,7 @@ All LLM provider implementations live here. Previously some sat under `services/
|
|
|
242
242
|
| `providers/__init__.py` | Re-exports | `AIProvider`, `NormalizedStreamChunk`, `ToolCallDef`, `provider_orchestrator` |
|
|
243
243
|
| `providers/base.py` | Provider protocol + shared DTOs | `AIProvider`, `NormalizedStreamChunk`, `ToolCallDef`, `_ToolCall`, `_FunctionCall` |
|
|
244
244
|
| `providers/orchestrator.py` | Driver-keyed provider factory | `ProviderOrchestrator`, `provider_orchestrator` |
|
|
245
|
+
| `providers/evren.py` | evren gateway terms-of-use acceptance (used by `/model`) | `EVREN_HOST`, `EvrenTerms`, `is_evren_terms_error`, `fetch_terms`, `accept_terms` |
|
|
245
246
|
| `providers/impl/ollama.py` | Native Ollama provider (ollama SDK) | `OllamaProvider` |
|
|
246
247
|
| `providers/impl/ollama_inspector.py` | Ollama model introspection | `OllamaInspector`, `ModelInfo`, `RuntimeState` |
|
|
247
248
|
| `providers/impl/openai.py` | OpenAI / OpenAI-compatible backend | `OpenAIProvider` |
|
|
@@ -206,6 +206,15 @@ think = true # default; set false if the selected model r
|
|
|
206
206
|
# reasoning_effort = "medium" # choose a value supported by the selected model
|
|
207
207
|
# # temperature / think are NOT applied by the openai driver.
|
|
208
208
|
|
|
209
|
+
# evren gateway (OpenAI-compatible). A fresh config.toml ships this profile with
|
|
210
|
+
# an empty api_key. The first `/model` asks you to accept evren's terms of use.
|
|
211
|
+
# [llm.evren]
|
|
212
|
+
# driver = "openai"
|
|
213
|
+
# base_url = "https://evren-llmapi.ssyz.org.tr/v1"
|
|
214
|
+
# api_key = "" # your evren key
|
|
215
|
+
# model = "deepseek-v4-flash"
|
|
216
|
+
# num_ctx = 131072
|
|
217
|
+
|
|
209
218
|
# =============================================================================
|
|
210
219
|
# Agents
|
|
211
220
|
# =============================================================================
|
|
@@ -24,7 +24,7 @@ DEFAULT_BASELINE = REPO / "scripts" / "exception_baseline.txt"
|
|
|
24
24
|
|
|
25
25
|
# Frozen census (§C10). A different number means the gate is looking at the
|
|
26
26
|
# wrong tree, or the census moved without the plan being updated.
|
|
27
|
-
EXPECTED_BROAD =
|
|
27
|
+
EXPECTED_BROAD = 137
|
|
28
28
|
BRANCHING = (ast.If, ast.Try, ast.For, ast.While, ast.With, ast.Match)
|
|
29
29
|
TERMINAL = (ast.Return, ast.Break, ast.Continue, ast.Pass)
|
|
30
30
|
|
|
@@ -203,13 +203,13 @@ FROZEN_CHAIN_SITES: dict[tuple[str, str, str, str], tuple[str, int]] = {
|
|
|
203
203
|
("log.py", "emit_event", "bind", "**"): ("Name(id='fields', ctx=Load())", 1),
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
# The
|
|
207
|
-
# package-wide shape/count rule rather than
|
|
206
|
+
# The 101 `exception=True` inputs are homogeneous, so they are governed as one
|
|
207
|
+
# package-wide shape/count rule rather than 101 site identities - their per-site
|
|
208
208
|
# discipline is C10's.
|
|
209
|
-
FROZEN_EXCEPTION_TRUE =
|
|
209
|
+
FROZEN_EXCEPTION_TRUE = 101
|
|
210
210
|
|
|
211
211
|
# Live chain inputs, counted per method. Printed on every run.
|
|
212
|
-
CHAIN_TALLY: dict[str, int] = {"bind": 6, "opt":
|
|
212
|
+
CHAIN_TALLY: dict[str, int] = {"bind": 6, "opt": 105, "patch": 0}
|
|
213
213
|
|
|
214
214
|
# The reviewed composition of THE COMMITTED baseline, frozen by the Phase 5
|
|
215
215
|
# census: 252 retained non-path + 22 paths + 2 deferred interpolated rows, plus
|
|
@@ -45,6 +45,16 @@ DEFAULTS: Dict[str, Any] = {
|
|
|
45
45
|
"model": "gemini-3-flash",
|
|
46
46
|
"num_ctx": 65536,
|
|
47
47
|
},
|
|
48
|
+
# Extra [llm.<name>] profiles written into a freshly created config.toml.
|
|
49
|
+
"extra_llm_profiles": {
|
|
50
|
+
"evren": {
|
|
51
|
+
"driver": "openai",
|
|
52
|
+
"base_url": "https://evren-llmapi.ssyz.org.tr/v1",
|
|
53
|
+
"api_key": "",
|
|
54
|
+
"model": "deepseek-v4-flash",
|
|
55
|
+
"num_ctx": 131072,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
48
58
|
"editor": "vim",
|
|
49
59
|
"verbose": False,
|
|
50
60
|
"logging": {
|
|
@@ -74,6 +74,8 @@ def render_v2_config(
|
|
|
74
74
|
section = dict(defaults.get(provider, {}))
|
|
75
75
|
section.setdefault("driver", _DRIVER_BY_PROVIDER[provider])
|
|
76
76
|
llm_profiles[provider] = section
|
|
77
|
+
for name, section in defaults.get("extra_llm_profiles", {}).items():
|
|
78
|
+
llm_profiles[name] = dict(section)
|
|
77
79
|
if llm_overrides:
|
|
78
80
|
for name, section in llm_overrides.items():
|
|
79
81
|
merged = dict(llm_profiles.get(name, {}))
|
|
@@ -56,7 +56,8 @@ class AIProvider(ABC):
|
|
|
56
56
|
List available models from the provider.
|
|
57
57
|
|
|
58
58
|
Returns:
|
|
59
|
-
List of model name strings. Empty list if
|
|
59
|
+
List of model name strings. Empty list if the driver does not
|
|
60
|
+
support listing; drivers may raise on server errors.
|
|
60
61
|
"""
|
|
61
62
|
return []
|
|
62
63
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""evren LLM gateway: terms-of-use acceptance.
|
|
2
|
+
|
|
3
|
+
The gateway (https://evren-llmapi.ssyz.org.tr) is OpenAI-compatible but refuses every
|
|
4
|
+
call with HTTP 403 ``terms_not_accepted`` until the account accepts the current terms.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Final
|
|
9
|
+
from urllib.parse import urlsplit
|
|
10
|
+
|
|
11
|
+
from openai import AsyncOpenAI, PermissionDeniedError
|
|
12
|
+
|
|
13
|
+
from ayder_cli.core.config import Config
|
|
14
|
+
|
|
15
|
+
EVREN_HOST: Final[str] = "evren-llmapi.ssyz.org.tr"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class EvrenTerms:
|
|
20
|
+
"""One published version of the evren terms of use."""
|
|
21
|
+
|
|
22
|
+
version: int
|
|
23
|
+
content: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def is_evren_terms_error(base_url: str | None, exc: BaseException) -> bool:
|
|
27
|
+
"""True when ``exc`` is evren refusing a call until its terms are accepted."""
|
|
28
|
+
if not base_url or urlsplit(base_url).hostname != EVREN_HOST:
|
|
29
|
+
return False
|
|
30
|
+
return isinstance(exc, PermissionDeniedError) and exc.code == "terms_not_accepted"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def make_client(config: Config) -> AsyncOpenAI:
|
|
34
|
+
"""Build a client for the provider's configured base URL and key."""
|
|
35
|
+
return AsyncOpenAI(base_url=config.base_url, api_key=config.api_key)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def fetch_terms(client: AsyncOpenAI) -> EvrenTerms:
|
|
39
|
+
"""Fetch the current terms text.
|
|
40
|
+
|
|
41
|
+
Raises the SDK's ``APIError`` on a failed request and ``ValueError`` on a
|
|
42
|
+
response without an integer ``version`` and a string ``content``.
|
|
43
|
+
"""
|
|
44
|
+
data = await client.get("terms/text", cast_to=object)
|
|
45
|
+
if not isinstance(data, dict):
|
|
46
|
+
raise ValueError(f"unexpected evren terms response: {data!r}")
|
|
47
|
+
version, content = data.get("version"), data.get("content")
|
|
48
|
+
if not isinstance(version, int) or not isinstance(content, str):
|
|
49
|
+
raise ValueError(f"unexpected evren terms response: {data!r}")
|
|
50
|
+
return EvrenTerms(version, content)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def accept_terms(client: AsyncOpenAI, version: int) -> int:
|
|
54
|
+
"""Accept terms ``version``; return the version the server recorded.
|
|
55
|
+
|
|
56
|
+
Falls back to ``version`` when the response does not name one. Raises the
|
|
57
|
+
SDK's ``APIError`` on a failed request.
|
|
58
|
+
"""
|
|
59
|
+
data = await client.post("terms/accept", body={"version": version}, cast_to=object)
|
|
60
|
+
accepted = data.get("accepted_version") if isinstance(data, dict) else None
|
|
61
|
+
return accepted if isinstance(accepted, int) else version
|
|
@@ -36,16 +36,11 @@ class OpenAIProvider(AIProvider):
|
|
|
36
36
|
async def list_models(self) -> List[str]:
|
|
37
37
|
"""
|
|
38
38
|
List available models from the OpenAI-compatible API.
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
|
|
40
|
+
Server and transport errors propagate so the caller can report them.
|
|
41
41
|
"""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
# Extract model IDs from the response
|
|
45
|
-
return [model.id for model in response.data if hasattr(model, "id")]
|
|
46
|
-
except Exception: # noqa: BLE001 - OpenAI SDK boundary: servers that omit model listing raise arbitrary client errors
|
|
47
|
-
logger.opt(exception=True).warning("Failed to list models")
|
|
48
|
-
return []
|
|
42
|
+
response = await self.client.models.list()
|
|
43
|
+
return [model.id for model in response.data if hasattr(model, "id")]
|
|
49
44
|
|
|
50
45
|
async def chat(
|
|
51
46
|
self,
|
|
@@ -358,6 +358,43 @@ CLIHelpScreen #help-content {
|
|
|
358
358
|
height: auto;
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
+
/* EvrenTermsScreen - evren terms-of-use modal with Approve / Deny.
|
|
362
|
+
Targets 'EvrenTermsScreen' in screens.py. */
|
|
363
|
+
EvrenTermsScreen {
|
|
364
|
+
align: center middle;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
EvrenTermsScreen > Vertical {
|
|
368
|
+
width: 90%;
|
|
369
|
+
height: 85%;
|
|
370
|
+
background: ansi_black;
|
|
371
|
+
border: solid ansi_bright_blue;
|
|
372
|
+
padding: 1 2;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
EvrenTermsScreen .prompt {
|
|
376
|
+
text-style: bold;
|
|
377
|
+
color: ansi_bright_blue;
|
|
378
|
+
text-align: center;
|
|
379
|
+
width: 100%;
|
|
380
|
+
margin-bottom: 1;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
EvrenTermsScreen #terms-scroll {
|
|
384
|
+
height: 1fr;
|
|
385
|
+
color: ansi_white;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
EvrenTermsScreen #terms-buttons {
|
|
389
|
+
height: auto;
|
|
390
|
+
align: center middle;
|
|
391
|
+
margin-top: 1;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
EvrenTermsScreen Button {
|
|
395
|
+
margin: 0 2;
|
|
396
|
+
}
|
|
397
|
+
|
|
361
398
|
/* Generic text selection highlight color. */
|
|
362
399
|
.selection {
|
|
363
400
|
background: ansi_blue;
|
|
@@ -314,6 +314,43 @@ CLIHelpScreen #help-content {
|
|
|
314
314
|
height: auto;
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
+
/* EvrenTermsScreen - evren terms-of-use modal with Approve / Deny.
|
|
318
|
+
Targets 'EvrenTermsScreen' in screens.py. */
|
|
319
|
+
EvrenTermsScreen {
|
|
320
|
+
align: center middle;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
EvrenTermsScreen > Vertical {
|
|
324
|
+
width: 90%;
|
|
325
|
+
height: 85%;
|
|
326
|
+
background: #12122a;
|
|
327
|
+
border: solid #5eaff5;
|
|
328
|
+
padding: 1 2;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
EvrenTermsScreen .prompt {
|
|
332
|
+
text-style: bold;
|
|
333
|
+
color: #5eaff5;
|
|
334
|
+
text-align: center;
|
|
335
|
+
width: 100%;
|
|
336
|
+
margin-bottom: 1;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
EvrenTermsScreen #terms-scroll {
|
|
340
|
+
height: 1fr;
|
|
341
|
+
color: #b8b8c8;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
EvrenTermsScreen #terms-buttons {
|
|
345
|
+
height: auto;
|
|
346
|
+
align: center middle;
|
|
347
|
+
margin-top: 1;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
EvrenTermsScreen Button {
|
|
351
|
+
margin: 0 2;
|
|
352
|
+
}
|
|
353
|
+
|
|
317
354
|
/* Generic text selection highlight color. */
|
|
318
355
|
.selection {
|
|
319
356
|
background: #264f78;
|
|
@@ -15,11 +15,14 @@ from ayder_cli.core.context import ProjectContext
|
|
|
15
15
|
from ayder_cli.core.reasoning import get_effort_label
|
|
16
16
|
from ayder_cli.log import LOG_LEVELS, get_logger
|
|
17
17
|
from ayder_cli.logging_config import setup_logging
|
|
18
|
-
|
|
18
|
+
import openai
|
|
19
|
+
|
|
20
|
+
from ayder_cli.providers import evren, provider_orchestrator, ProviderUnavailableError
|
|
19
21
|
from ayder_cli.tools.builtins.skill import SkillInfo, discover_skills, skill
|
|
20
22
|
from ayder_cli.tui.screens import (
|
|
21
23
|
AgentListScreen,
|
|
22
24
|
CLISelectScreen,
|
|
25
|
+
EvrenTermsScreen,
|
|
23
26
|
CLIMultiSelectScreen,
|
|
24
27
|
CLIPermissionScreen,
|
|
25
28
|
TaskEditScreen,
|
|
@@ -136,7 +139,9 @@ def _apply_provider_switch(
|
|
|
136
139
|
app.request_turn(prepare=_prepare, run_loop=False)
|
|
137
140
|
|
|
138
141
|
|
|
139
|
-
async def _list_and_show_models(
|
|
142
|
+
async def _list_and_show_models(
|
|
143
|
+
app: AyderApp, chat_view: ChatView, *, terms_prompted: bool = False
|
|
144
|
+
) -> None:
|
|
140
145
|
"""Async helper to list models and show selector."""
|
|
141
146
|
try:
|
|
142
147
|
models = await app.llm.list_models()
|
|
@@ -173,9 +178,43 @@ async def _list_and_show_models(app: AyderApp, chat_view: ChatView) -> None:
|
|
|
173
178
|
)
|
|
174
179
|
except Exception as e: # noqa: BLE001 - slash-command guard: /model reports listing failures in chat rather than killing the app
|
|
175
180
|
logger.opt(exception=True).error("Model listing failed")
|
|
181
|
+
if not terms_prompted and evren.is_evren_terms_error(app.config.base_url, e):
|
|
182
|
+
await _prompt_evren_terms(app, chat_view)
|
|
183
|
+
return
|
|
176
184
|
chat_view.add_system_message(f"Error listing models: {e}")
|
|
177
185
|
|
|
178
186
|
|
|
187
|
+
async def _prompt_evren_terms(app: AyderApp, chat_view: ChatView) -> None:
|
|
188
|
+
"""Show the evren terms; on Approve accept them and list models again."""
|
|
189
|
+
client = evren.make_client(app.config)
|
|
190
|
+
try:
|
|
191
|
+
terms = await evren.fetch_terms(client)
|
|
192
|
+
except (openai.APIError, ValueError) as e:
|
|
193
|
+
logger.opt(exception=True).warning("Fetching evren terms failed")
|
|
194
|
+
chat_view.add_system_message(f"Could not load evren terms: {e}")
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
async def _accept_and_relist() -> None:
|
|
198
|
+
try:
|
|
199
|
+
accepted = await evren.accept_terms(client, terms.version)
|
|
200
|
+
except openai.APIError as e:
|
|
201
|
+
logger.opt(exception=True).warning("Accepting evren terms failed")
|
|
202
|
+
chat_view.add_system_message(f"Could not accept evren terms: {e}")
|
|
203
|
+
return
|
|
204
|
+
chat_view.add_system_message(f"Accepted evren terms v{accepted}.")
|
|
205
|
+
await _list_and_show_models(app, chat_view, terms_prompted=True)
|
|
206
|
+
|
|
207
|
+
def on_decision(approved: bool | None) -> None:
|
|
208
|
+
if approved:
|
|
209
|
+
app.run_worker(_accept_and_relist(), exclusive=False)
|
|
210
|
+
else:
|
|
211
|
+
chat_view.add_system_message(
|
|
212
|
+
"evren terms not accepted; models cannot be listed until they are."
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
app.push_screen(EvrenTermsScreen(terms), on_decision)
|
|
216
|
+
|
|
217
|
+
|
|
179
218
|
def handle_model(app: AyderApp, args: str, chat_view: ChatView) -> None:
|
|
180
219
|
"""Handle /model command."""
|
|
181
220
|
if not args.strip():
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
"""Modal screens for the TUI: confirm, permission, safe mode, select, task edit, help."""
|
|
1
|
+
"""Modal screens for the TUI: confirm, permission, safe mode, select, task edit, help, evren terms."""
|
|
2
2
|
|
|
3
3
|
from textual.app import ComposeResult
|
|
4
|
-
from textual.containers import Vertical, VerticalScroll
|
|
4
|
+
from textual.containers import Horizontal, Vertical, VerticalScroll
|
|
5
5
|
from textual.css.query import NoMatches
|
|
6
|
-
from textual.widgets import
|
|
6
|
+
from textual.widgets import Button, Input, Label, Markdown, Static, TextArea
|
|
7
7
|
from textual.screen import ModalScreen
|
|
8
8
|
from rich.text import Text
|
|
9
9
|
|
|
10
|
+
from ayder_cli.providers.evren import EvrenTerms
|
|
10
11
|
from ayder_cli.tui.keybindings import get_keybindings_by_category
|
|
11
12
|
from ayder_cli.tui.types import ConfirmResult
|
|
12
13
|
from ayder_cli.log import get_logger
|
|
@@ -767,3 +768,37 @@ class CLIHelpScreen(ModalScreen[None]):
|
|
|
767
768
|
|
|
768
769
|
def action_dismiss_help(self) -> None:
|
|
769
770
|
self.dismiss(None)
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
class EvrenTermsScreen(ModalScreen[bool]):
|
|
774
|
+
"""Shows the evren terms of use; dismisses True on Approve, False on Deny.
|
|
775
|
+
|
|
776
|
+
Deny holds focus when the modal opens so a stray Enter never accepts.
|
|
777
|
+
"""
|
|
778
|
+
|
|
779
|
+
BINDINGS = [("escape", "deny", "Deny")]
|
|
780
|
+
|
|
781
|
+
def __init__(self, terms: EvrenTerms) -> None:
|
|
782
|
+
super().__init__()
|
|
783
|
+
self.terms = terms
|
|
784
|
+
|
|
785
|
+
def compose(self) -> ComposeResult:
|
|
786
|
+
with Vertical():
|
|
787
|
+
yield Label(
|
|
788
|
+
f"evren — Terms of Use v{self.terms.version}", classes="prompt", markup=False
|
|
789
|
+
)
|
|
790
|
+
with VerticalScroll(id="terms-scroll"):
|
|
791
|
+
yield Markdown(self.terms.content, id="terms-content")
|
|
792
|
+
with Horizontal(id="terms-buttons"):
|
|
793
|
+
yield Button("Approve", id="terms-approve", variant="success")
|
|
794
|
+
yield Button("Deny", id="terms-deny", variant="error")
|
|
795
|
+
|
|
796
|
+
def on_mount(self) -> None:
|
|
797
|
+
self.query_one("#terms-deny", Button).focus()
|
|
798
|
+
|
|
799
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
800
|
+
event.stop()
|
|
801
|
+
self.dismiss(event.button.id == "terms-approve")
|
|
802
|
+
|
|
803
|
+
def action_deny(self) -> None:
|
|
804
|
+
self.dismiss(False)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""The first-run config.toml ships an evren profile with an empty API key."""
|
|
2
|
+
|
|
3
|
+
import tomllib
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from ayder_cli.core import config as config_module
|
|
8
|
+
from ayder_cli.core.config import DEFAULTS, load_config_for_provider
|
|
9
|
+
from ayder_cli.core.config_migration import ensure_latest_config
|
|
10
|
+
|
|
11
|
+
EVREN_PROFILE = {
|
|
12
|
+
"driver": "openai",
|
|
13
|
+
"base_url": "https://evren-llmapi.ssyz.org.tr/v1",
|
|
14
|
+
"api_key": "",
|
|
15
|
+
"model": "deepseek-v4-flash",
|
|
16
|
+
"num_ctx": 131072,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def fresh_config(tmp_path, monkeypatch):
|
|
22
|
+
path = tmp_path / ".ayder" / "config.toml"
|
|
23
|
+
monkeypatch.setattr(config_module, "CONFIG_DIR", path.parent)
|
|
24
|
+
monkeypatch.setattr(config_module, "CONFIG_PATH", path)
|
|
25
|
+
ensure_latest_config(path, defaults=DEFAULTS)
|
|
26
|
+
return path
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_first_run_config_has_evren_profile(fresh_config):
|
|
30
|
+
data = tomllib.loads(fresh_config.read_text())
|
|
31
|
+
|
|
32
|
+
assert data["llm"]["evren"] == EVREN_PROFILE
|
|
33
|
+
assert data["app"]["provider"] == "openai"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_evren_profile_loads_with_empty_key(fresh_config):
|
|
37
|
+
cfg = load_config_for_provider("evren")
|
|
38
|
+
|
|
39
|
+
assert (cfg.driver, cfg.base_url, cfg.api_key, cfg.model) == (
|
|
40
|
+
"openai",
|
|
41
|
+
"https://evren-llmapi.ssyz.org.tr/v1",
|
|
42
|
+
"",
|
|
43
|
+
"deepseek-v4-flash",
|
|
44
|
+
)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""evren terms helpers: error detection, terms fetch, terms acceptance."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import openai
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from ayder_cli.providers.evren import EvrenTerms, accept_terms, fetch_terms, is_evren_terms_error
|
|
10
|
+
|
|
11
|
+
EVREN = "https://evren-llmapi.ssyz.org.tr/v1"
|
|
12
|
+
TERMS_CONTENT = "# EVREN Kullanım Şartları\n\nMadde 1."
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _status_error(cls: type[openai.APIStatusError], status: int, code: str) -> openai.APIStatusError:
|
|
16
|
+
request = httpx.Request("GET", f"{EVREN}/models")
|
|
17
|
+
body = {"message": "refused", "type": "permission_error", "param": None, "code": code}
|
|
18
|
+
return cls("refused", response=httpx.Response(status, request=request), body=body)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
TERMS_403 = _status_error(openai.PermissionDeniedError, 403, "terms_not_accepted")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.parametrize(
|
|
25
|
+
("base_url", "exc", "expected"),
|
|
26
|
+
[
|
|
27
|
+
(EVREN, TERMS_403, True),
|
|
28
|
+
("https://evren-llmapi.ssyz.org.tr/", TERMS_403, True),
|
|
29
|
+
("https://api.example.com/v1", TERMS_403, False),
|
|
30
|
+
(EVREN, _status_error(openai.PermissionDeniedError, 403, "forbidden"), False),
|
|
31
|
+
(EVREN, _status_error(openai.NotFoundError, 404, "terms_not_accepted"), False),
|
|
32
|
+
(EVREN, RuntimeError("terms_not_accepted"), False),
|
|
33
|
+
(None, TERMS_403, False),
|
|
34
|
+
],
|
|
35
|
+
)
|
|
36
|
+
def test_is_evren_terms_error(base_url, exc, expected):
|
|
37
|
+
assert is_evren_terms_error(base_url, exc) is expected
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _client(responses: dict[str, httpx.Response], requests: list[httpx.Request]) -> openai.AsyncOpenAI:
|
|
41
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
42
|
+
requests.append(request)
|
|
43
|
+
return responses[request.url.path]
|
|
44
|
+
|
|
45
|
+
return openai.AsyncOpenAI(
|
|
46
|
+
base_url=EVREN,
|
|
47
|
+
api_key="k",
|
|
48
|
+
max_retries=0,
|
|
49
|
+
http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler)),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.asyncio
|
|
54
|
+
async def test_fetch_terms():
|
|
55
|
+
requests: list[httpx.Request] = []
|
|
56
|
+
body = {"version": 1, "doc_path": "docs/legal/x.md", "content": TERMS_CONTENT}
|
|
57
|
+
client = _client({"/v1/terms/text": httpx.Response(200, json=body)}, requests)
|
|
58
|
+
|
|
59
|
+
assert await fetch_terms(client) == EvrenTerms(version=1, content=TERMS_CONTENT)
|
|
60
|
+
assert [(r.method, r.url.path) for r in requests] == [("GET", "/v1/terms/text")]
|
|
61
|
+
assert requests[0].headers["Authorization"] == "Bearer k"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.mark.asyncio
|
|
65
|
+
@pytest.mark.parametrize(
|
|
66
|
+
"response_body",
|
|
67
|
+
[
|
|
68
|
+
{"accepted_version": 1, "accepted_at": "2026-09-29T11:27:40.291459+00:00"},
|
|
69
|
+
{},
|
|
70
|
+
],
|
|
71
|
+
)
|
|
72
|
+
async def test_accept_terms(response_body):
|
|
73
|
+
requests: list[httpx.Request] = []
|
|
74
|
+
client = _client({"/v1/terms/accept": httpx.Response(200, json=response_body)}, requests)
|
|
75
|
+
|
|
76
|
+
assert await accept_terms(client, 1) == 1
|
|
77
|
+
assert [(r.method, r.url.path) for r in requests] == [("POST", "/v1/terms/accept")]
|
|
78
|
+
assert json.loads(requests[0].content) == {"version": 1}
|
|
79
|
+
|