coderouter-cli 2.3.0a3__tar.gz → 2.3.0a4__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.
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/CHANGELOG.md +45 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/PKG-INFO +1 -1
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/plugins/__init__.py +5 -8
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/routing/fallback.py +19 -1
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/pyproject.toml +1 -1
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_plugins_integration.py +13 -118
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_plugins_loader.py +3 -7
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_plugins_registry.py +1 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/.gitignore +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/LICENSE +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/README.en.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/README.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/__init__.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/__main__.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/adapters/__init__.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/adapters/anthropic_native.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/adapters/base.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/adapters/openai_compat.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/adapters/registry.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/cli.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/cli_stats.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/config/__init__.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/config/capability_registry.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/config/env_file.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/config/loader.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/config/schemas.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/cost.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/data/__init__.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/data/model-capabilities.yaml +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/doctor.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/doctor_apply.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/env_security.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/errors.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/guards/__init__.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/guards/backend_health.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/guards/context_budget.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/guards/continuous_probe.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/guards/drift_actions.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/guards/drift_detection.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/guards/memory_pressure.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/guards/self_healing.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/guards/tool_loop.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/ingress/__init__.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/ingress/anthropic_routes.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/ingress/app.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/ingress/dashboard_routes.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/ingress/metrics_routes.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/ingress/openai_routes.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/logging.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/metrics/__init__.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/metrics/collector.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/metrics/prometheus.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/output_filters.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/plugins/base.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/plugins/loader.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/plugins/registry.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/routing/__init__.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/routing/adaptive.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/routing/auto_router.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/routing/budget.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/routing/capability.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/state/__init__.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/state/audit_log.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/state/replay.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/state/request_log.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/state/store.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/token_estimation.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/translation/__init__.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/translation/anthropic.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/translation/convert.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/coderouter/translation/tool_repair.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/architecture.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/assets/dashboard-demo.png +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/context-budget.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/continuous-probing.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/designs/v1.5-dashboard-mockup.html +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/designs/v1.6-auto-router-verification.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/designs/v1.6-auto-router.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/drift-detection.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/free-tier-guide.en.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/free-tier-guide.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/gguf_dl.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/hf-ollama-models.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/llamacpp-direct.en.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/llamacpp-direct.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/lmstudio-direct.en.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/lmstudio-direct.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/openrouter-roster/CHANGES.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/openrouter-roster/README.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/openrouter-roster/latest.json +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/partial-stitch.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/quickstart.en.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/quickstart.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/retrospectives/v0.4.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/retrospectives/v0.5-verify.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/retrospectives/v0.5.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/retrospectives/v0.6.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/retrospectives/v0.7.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/retrospectives/v1.0-verify.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/retrospectives/v1.0.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/security.en.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/security.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/troubleshooting.en.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/troubleshooting.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/usage-guide.en.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/usage-guide.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/when-do-i-need-coderouter.en.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/when-do-i-need-coderouter.md +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/examples/.env.example +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/examples/providers.auto-custom.yaml +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/examples/providers.auto.yaml +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/examples/providers.note-2026.yaml +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/examples/providers.nvidia-nim.yaml +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/examples/providers.raspberrypi.yaml +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/examples/providers.v2-context-budget.yaml +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/examples/providers.yaml +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/scripts/demo_traffic.sh +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/scripts/openrouter_roster_diff.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/scripts/smoke_v2_2.sh +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/scripts/verify_v0_5.sh +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/scripts/verify_v1_0.sh +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/__init__.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/conftest.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_adapter_anthropic.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_audit_log.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_auto_router.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_backend_health.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_budget.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_capability.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_capability_degraded_payload.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_capability_registry.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_capability_registry_cache_control.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_claude_code_suitability.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_cli.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_cli_stats.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_config.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_context_budget.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_continuous_probe.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_dashboard_endpoint.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_doctor.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_doctor_apply.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_doctor_cache_probe.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_drift_actions.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_drift_detection.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_drift_detection_integration.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_env_file.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_env_security.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_errors.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_examples_yaml.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_fallback.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_fallback_anthropic.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_fallback_cache_control.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_fallback_cache_observed.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_fallback_misconfig_warn.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_fallback_paid_gate.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_fallback_thinking.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_guards_tool_loop.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_ingress_anthropic.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_ingress_profile.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_memory_pressure.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_metrics_cache.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_metrics_collector.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_metrics_cost.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_metrics_endpoint.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_metrics_jsonl.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_metrics_prometheus.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_metrics_prometheus_cache.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_openai_compat.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_openrouter_roster_diff.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_output_filters.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_output_filters_adapters.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_partial_stitch.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_reasoning_strip.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_request_log.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_routing_adaptive.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_self_healing.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_setup_sh.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_state_store.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_token_estimation.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_tool_repair.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_translation_anthropic.py +0 -0
- {coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_translation_reverse.py +0 -0
|
@@ -6,6 +6,51 @@ versioning follows [SemVer](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [v2.3.0a4] — 2026-05-08 (Plugin SDK — ruff cleanup)
|
|
10
|
+
|
|
11
|
+
Patch over `v2.3.0a3`. CI's `ruff check .` job surfaced six lint
|
|
12
|
+
findings in the new Plugin SDK code. None affect runtime behavior.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **RUF022**: `__all__` in `coderouter/plugins/__init__.py` is now
|
|
17
|
+
isort-sorted alphabetically.
|
|
18
|
+
- **RUF006**: `_fanout_observers` was using `asyncio.create_task`
|
|
19
|
+
without holding a strong reference. Asyncio's task tracker only
|
|
20
|
+
keeps a weakref, so a fanout-in-flight task could be GC'd before
|
|
21
|
+
the observer ran. Fixed by storing tasks in a per-engine
|
|
22
|
+
``_observer_tasks: set[asyncio.Task[None]]`` and removing each
|
|
23
|
+
via ``task.add_done_callback(set.discard)`` on completion. The
|
|
24
|
+
attribute is lazy-initialized in `_fanout_observers` itself so
|
|
25
|
+
engines built via ``__new__`` (which bypass ``__init__``) still
|
|
26
|
+
work.
|
|
27
|
+
- **I001 + F841**: `tests/test_plugins_integration.py` had unused
|
|
28
|
+
imports (`AnthropicResponse`, `AnthropicUsage`) and an unused
|
|
29
|
+
local (`captured_chat`) left over from a build-engine helper
|
|
30
|
+
whose code path never ran. Removed the helper entirely; the
|
|
31
|
+
remaining tests exercise the engine's hook surface
|
|
32
|
+
(``_apply_input_filters`` / ``_fanout_observers`` /
|
|
33
|
+
``_safe_observe``) directly, which is what they always actually
|
|
34
|
+
did.
|
|
35
|
+
- **I001**: `tests/test_plugins_loader.py` import block reordered
|
|
36
|
+
alphabetically by module name.
|
|
37
|
+
|
|
38
|
+
### Files touched
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
M coderouter/plugins/__init__.py — __all__ alphabetical
|
|
42
|
+
M coderouter/routing/fallback.py — task strong-ref set
|
|
43
|
+
M tests/test_plugins_integration.py — drop dead helper
|
|
44
|
+
M tests/test_plugins_loader.py — import order
|
|
45
|
+
M tests/test_plugins_registry.py — formatting nit (blank line)
|
|
46
|
+
M CHANGELOG.md, pyproject.toml — 2.3.0a3 → 2.3.0a4
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
After this patch, ``ruff check .`` passes against every tracked
|
|
50
|
+
Python file in the repo.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
9
54
|
## [v2.3.0a3] — 2026-05-08 (Plugin SDK — LogRecord.module collision fix)
|
|
10
55
|
|
|
11
56
|
Patch over `v2.3.0a2`. The wheel-install-and-test job in CI surfaced
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coderouter-cli
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.0a4
|
|
4
4
|
Summary: Local-first, free-first, fallback-built-in LLM router. Claude Code / OpenAI compatible.
|
|
5
5
|
Project-URL: Homepage, https://github.com/zephel01/CodeRouter
|
|
6
6
|
Project-URL: Repository, https://github.com/zephel01/CodeRouter
|
|
@@ -43,17 +43,14 @@ from coderouter.plugins.loader import (
|
|
|
43
43
|
from coderouter.plugins.registry import PluginRegistry
|
|
44
44
|
|
|
45
45
|
__all__ = [
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
# Future hooks (Protocol-only, no engine integration yet)
|
|
46
|
+
"PLUGIN_GROUPS_FUTURE",
|
|
47
|
+
"PLUGIN_GROUPS_V2_3",
|
|
48
|
+
"Adapter",
|
|
50
49
|
"Frontend",
|
|
51
50
|
"Guard",
|
|
51
|
+
"InputFilter",
|
|
52
|
+
"Observer",
|
|
52
53
|
"OutputFilter",
|
|
53
|
-
"Adapter",
|
|
54
|
-
# Discovery + container
|
|
55
54
|
"PluginRegistry",
|
|
56
55
|
"discover_and_load",
|
|
57
|
-
"PLUGIN_GROUPS_V2_3",
|
|
58
|
-
"PLUGIN_GROUPS_FUTURE",
|
|
59
56
|
]
|
|
@@ -838,6 +838,12 @@ class FallbackEngine:
|
|
|
838
838
|
# so tests that build the engine via ``FallbackEngine.__new__``
|
|
839
839
|
# see an empty registry instead of AttributeError.
|
|
840
840
|
self._plugin_registry: PluginRegistry = plugins or PluginRegistry.empty()
|
|
841
|
+
# v2.3.0: holds strong refs to in-flight Observer fanout tasks
|
|
842
|
+
# so the asyncio event loop's weak-ref bookkeeping doesn't GC
|
|
843
|
+
# them mid-flight (RUF006). Tasks remove themselves on done
|
|
844
|
+
# via ``add_done_callback(_observer_tasks.discard)`` in
|
|
845
|
+
# :meth:`_fanout_observers`.
|
|
846
|
+
self._observer_tasks: set[asyncio.Task[None]] = set()
|
|
841
847
|
# Cache adapters so we don't re-instantiate per request
|
|
842
848
|
self._adapters: dict[str, BaseAdapter] = {
|
|
843
849
|
p.name: build_adapter(p) for p in config.providers
|
|
@@ -1881,10 +1887,22 @@ class FallbackEngine:
|
|
|
1881
1887
|
observers = self.plugins.observers
|
|
1882
1888
|
if not observers:
|
|
1883
1889
|
return
|
|
1890
|
+
# Lazy-init the task set for engines built via ``__new__`` —
|
|
1891
|
+
# mirrors the lazy ``plugins`` property pattern so legacy
|
|
1892
|
+
# tests that bypass __init__ don't crash here.
|
|
1893
|
+
if not hasattr(self, "_observer_tasks"):
|
|
1894
|
+
self._observer_tasks = set()
|
|
1884
1895
|
for obs in observers:
|
|
1885
|
-
asyncio.create_task(
|
|
1896
|
+
task = asyncio.create_task(
|
|
1886
1897
|
self._safe_observe(obs, event_type, payload)
|
|
1887
1898
|
)
|
|
1899
|
+
# Strong-ref keeps the task alive past the loop iteration;
|
|
1900
|
+
# ``discard`` cleans up after the task completes (success
|
|
1901
|
+
# or exception). Avoids the RUF006 footgun where
|
|
1902
|
+
# asyncio.create_task's weakref-only bookkeeping can let
|
|
1903
|
+
# the loop GC a fanout-in-progress task.
|
|
1904
|
+
self._observer_tasks.add(task)
|
|
1905
|
+
task.add_done_callback(self._observer_tasks.discard)
|
|
1888
1906
|
|
|
1889
1907
|
async def _safe_observe(
|
|
1890
1908
|
self,
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# in plan.md §11.B; once granted, this name will become an alias and
|
|
12
12
|
# `coderouter` will become the canonical distribution name.
|
|
13
13
|
name = "coderouter-cli"
|
|
14
|
-
version = "2.3.
|
|
14
|
+
version = "2.3.0a4"
|
|
15
15
|
description = "Local-first, free-first, fallback-built-in LLM router. Claude Code / OpenAI compatible."
|
|
16
16
|
readme = "README.md"
|
|
17
17
|
requires-python = ">=3.12"
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
"""Integration tests: FallbackEngine + InputFilter / Observer hooks (v2.3.0).
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- the no-plugin path is bit-identical to v2.2.0 (zero hook calls)
|
|
11
|
-
|
|
12
|
-
Adapter calls are mocked at the dispatch level so we don't reach
|
|
13
|
-
HTTPx — keeps the tests fast and offline.
|
|
3
|
+
These exercise the engine-side helpers directly
|
|
4
|
+
(``_apply_input_filters`` / ``_fanout_observers`` /
|
|
5
|
+
``_safe_observe``). We don't actually invoke ``generate_anthropic`` /
|
|
6
|
+
``stream_anthropic`` here because doing so would mean stubbing the
|
|
7
|
+
entire HTTP / chain dispatch path; the helpers themselves carry the
|
|
8
|
+
plugin contract, and the broader engine paths are covered by
|
|
9
|
+
``test_fallback*.py``.
|
|
14
10
|
"""
|
|
11
|
+
|
|
15
12
|
from __future__ import annotations
|
|
16
13
|
|
|
17
14
|
import asyncio
|
|
@@ -22,13 +19,7 @@ import pytest
|
|
|
22
19
|
from coderouter.config.schemas import CodeRouterConfig
|
|
23
20
|
from coderouter.plugins.registry import PluginRegistry
|
|
24
21
|
from coderouter.routing.fallback import FallbackEngine
|
|
25
|
-
from coderouter.translation.anthropic import
|
|
26
|
-
AnthropicMessage,
|
|
27
|
-
AnthropicRequest,
|
|
28
|
-
AnthropicResponse,
|
|
29
|
-
AnthropicUsage,
|
|
30
|
-
)
|
|
31
|
-
|
|
22
|
+
from coderouter.translation.anthropic import AnthropicMessage, AnthropicRequest
|
|
32
23
|
|
|
33
24
|
# ---------------------------------------------------------------------
|
|
34
25
|
# Synthetic plugin classes
|
|
@@ -78,101 +69,10 @@ class _RecordingObserver:
|
|
|
78
69
|
|
|
79
70
|
|
|
80
71
|
# ---------------------------------------------------------------------
|
|
81
|
-
#
|
|
72
|
+
# Helpers
|
|
82
73
|
# ---------------------------------------------------------------------
|
|
83
74
|
|
|
84
75
|
|
|
85
|
-
@pytest.fixture
|
|
86
|
-
def fake_response() -> AnthropicResponse:
|
|
87
|
-
return AnthropicResponse(
|
|
88
|
-
id="msg_test",
|
|
89
|
-
type="message",
|
|
90
|
-
role="assistant",
|
|
91
|
-
content=[{"type": "text", "text": "ok"}],
|
|
92
|
-
model="x",
|
|
93
|
-
stop_reason="end_turn",
|
|
94
|
-
usage=AnthropicUsage(input_tokens=5, output_tokens=2),
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def _build_engine(
|
|
99
|
-
basic_config: CodeRouterConfig,
|
|
100
|
-
*,
|
|
101
|
-
plugins: PluginRegistry | None = None,
|
|
102
|
-
fake_response: AnthropicResponse | None = None,
|
|
103
|
-
) -> tuple[FallbackEngine, list[AnthropicRequest]]:
|
|
104
|
-
"""Build an engine and stub its first adapter to capture the
|
|
105
|
-
request and return ``fake_response``.
|
|
106
|
-
|
|
107
|
-
Returns ``(engine, captured_requests)`` — captured_requests is the
|
|
108
|
-
list of requests the (mocked) adapter saw, useful for asserting
|
|
109
|
-
that the InputFilter's mutation reached the dispatch layer.
|
|
110
|
-
"""
|
|
111
|
-
engine = FallbackEngine(basic_config, plugins=plugins)
|
|
112
|
-
captured: list[AnthropicRequest] = []
|
|
113
|
-
|
|
114
|
-
# Stub: only used when test hands us a fake_response to return.
|
|
115
|
-
if fake_response is not None:
|
|
116
|
-
async def stub_generate_anthropic(
|
|
117
|
-
req: AnthropicRequest, *, overrides: Any = None
|
|
118
|
-
) -> AnthropicResponse:
|
|
119
|
-
captured.append(req)
|
|
120
|
-
return fake_response
|
|
121
|
-
|
|
122
|
-
# Wrap the first adapter into a "native Anthropic" shape.
|
|
123
|
-
first_name = next(iter(engine._adapters))
|
|
124
|
-
first_adapter = engine._adapters[first_name]
|
|
125
|
-
# Convert the stub into AnthropicAdapter-shaped surface by
|
|
126
|
-
# patching the method directly. The engine's isinstance check
|
|
127
|
-
# for AnthropicAdapter still matters; instead of fighting it,
|
|
128
|
-
# we use a simpler approach: replace the chain resolution to
|
|
129
|
-
# always pick our stub adapter, AND patch generate_anthropic.
|
|
130
|
-
from coderouter.adapters.anthropic_native import AnthropicAdapter
|
|
131
|
-
|
|
132
|
-
if not isinstance(first_adapter, AnthropicAdapter):
|
|
133
|
-
# Not an Anthropic-native adapter — patch generate() and
|
|
134
|
-
# rely on the openai_compat code path which calls
|
|
135
|
-
# to_chat_request → adapter.generate() → to_anthropic_response.
|
|
136
|
-
async def stub_generate(
|
|
137
|
-
chat_req: Any, *, overrides: Any = None
|
|
138
|
-
) -> Any:
|
|
139
|
-
# Build a minimal ChatResponse using the existing
|
|
140
|
-
# AnthropicResponse text. We import lazily to avoid a
|
|
141
|
-
# heavy module import at test collection time.
|
|
142
|
-
from coderouter.translation.convert import to_chat_request # noqa: F401
|
|
143
|
-
from coderouter.translation import ( # noqa: F401
|
|
144
|
-
ChatResponse,
|
|
145
|
-
ChatResponseMessage,
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
# Capture the inbound chat request so we can also
|
|
149
|
-
# introspect what InputFilters mutated.
|
|
150
|
-
captured_chat = chat_req
|
|
151
|
-
# Build a chat-shaped response.
|
|
152
|
-
return ChatResponse( # type: ignore[call-arg]
|
|
153
|
-
id="cmpl_test",
|
|
154
|
-
object="chat.completion",
|
|
155
|
-
created=0,
|
|
156
|
-
model=chat_req.model,
|
|
157
|
-
choices=[
|
|
158
|
-
{
|
|
159
|
-
"index": 0,
|
|
160
|
-
"message": {"role": "assistant", "content": "ok"},
|
|
161
|
-
"finish_reason": "stop",
|
|
162
|
-
}
|
|
163
|
-
],
|
|
164
|
-
usage={"prompt_tokens": 5, "completion_tokens": 2, "total_tokens": 7},
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
first_adapter.generate = stub_generate # type: ignore[method-assign]
|
|
168
|
-
|
|
169
|
-
# Always patch generate_anthropic regardless — if the adapter
|
|
170
|
-
# IS Anthropic-native this is the path; otherwise it's harmless.
|
|
171
|
-
first_adapter.generate_anthropic = stub_generate_anthropic # type: ignore[method-assign,attr-defined]
|
|
172
|
-
|
|
173
|
-
return engine, captured
|
|
174
|
-
|
|
175
|
-
|
|
176
76
|
def _make_request(text: str = "hi") -> AnthropicRequest:
|
|
177
77
|
return AnthropicRequest(
|
|
178
78
|
model="x",
|
|
@@ -188,15 +88,13 @@ def _make_request(text: str = "hi") -> AnthropicRequest:
|
|
|
188
88
|
|
|
189
89
|
def test_no_plugins_means_no_hook_calls(basic_config: CodeRouterConfig) -> None:
|
|
190
90
|
"""With an empty registry, hook helpers are short-circuit no-ops."""
|
|
191
|
-
engine
|
|
91
|
+
engine = FallbackEngine(basic_config)
|
|
192
92
|
|
|
193
|
-
#
|
|
93
|
+
# InputFilter chain returns request unchanged.
|
|
194
94
|
out = asyncio.run(engine._apply_input_filters(_make_request()))
|
|
195
95
|
assert out.messages[0].content == "hi"
|
|
196
96
|
|
|
197
|
-
#
|
|
198
|
-
# Synchronous probe: just confirm it returns without error and
|
|
199
|
-
# doesn't iterate (no tasks pending afterwards).
|
|
97
|
+
# Observer fanout with empty registry must NOT spawn tasks.
|
|
200
98
|
engine._fanout_observers("anything", request=None, response=None)
|
|
201
99
|
|
|
202
100
|
|
|
@@ -254,7 +152,6 @@ def test_safe_observe_swallows_exceptions(
|
|
|
254
152
|
obs = BadObserver()
|
|
255
153
|
|
|
256
154
|
with caplog.at_level("WARNING"):
|
|
257
|
-
# Direct call — the fanout wrapper does the same try/except.
|
|
258
155
|
asyncio.run(engine._safe_observe(obs, "request_completed", {}))
|
|
259
156
|
|
|
260
157
|
assert any(rec.msg == "observer-failed" for rec in caplog.records)
|
|
@@ -279,8 +176,6 @@ def test_observer_fanout_creates_tasks_when_observers_present(
|
|
|
279
176
|
engine._fanout_observers("custom_event", payload_key="value")
|
|
280
177
|
# Yield control so the spawned task gets a chance to run.
|
|
281
178
|
await asyncio.sleep(0)
|
|
282
|
-
# Give the loop one more tick to ensure the fire-and-forget
|
|
283
|
-
# coroutine actually completes before we inspect.
|
|
284
179
|
await asyncio.sleep(0)
|
|
285
180
|
|
|
286
181
|
asyncio.run(run())
|
|
@@ -11,6 +11,7 @@ while exercising the loader's real path: enabled-allowlist filtering,
|
|
|
11
11
|
``__init__`` dispatch with config dict, and degraded-continue on
|
|
12
12
|
construction failure.
|
|
13
13
|
"""
|
|
14
|
+
|
|
14
15
|
from __future__ import annotations
|
|
15
16
|
|
|
16
17
|
import importlib.metadata as md
|
|
@@ -28,7 +29,6 @@ from coderouter.config.schemas import (
|
|
|
28
29
|
from coderouter.plugins import loader as loader_mod
|
|
29
30
|
from coderouter.plugins.loader import discover_and_load
|
|
30
31
|
|
|
31
|
-
|
|
32
32
|
# ---------------------------------------------------------------------
|
|
33
33
|
# Synthetic plugin classes — module-level so EntryPoint.load() can
|
|
34
34
|
# resolve "tests.test_plugins_loader:..." strings via importlib.
|
|
@@ -187,9 +187,7 @@ def test_failing_init_does_not_abort_loader(
|
|
|
187
187
|
)
|
|
188
188
|
monkeypatch.setattr(loader_mod.md, "entry_points", fake_eps)
|
|
189
189
|
|
|
190
|
-
cfg = _make_config(
|
|
191
|
-
plugins_cfg=PluginsConfig(enabled=["bad-init", "good-filter"])
|
|
192
|
-
)
|
|
190
|
+
cfg = _make_config(plugins_cfg=PluginsConfig(enabled=["bad-init", "good-filter"]))
|
|
193
191
|
with caplog.at_level("ERROR"):
|
|
194
192
|
reg = discover_and_load(cfg)
|
|
195
193
|
|
|
@@ -256,6 +254,4 @@ def test_future_group_logs_warning_but_still_loads(
|
|
|
256
254
|
reg = discover_and_load(cfg)
|
|
257
255
|
|
|
258
256
|
assert reg.count("frontend") == 1
|
|
259
|
-
assert any(
|
|
260
|
-
rec.msg == "plugin-group-not-yet-active" for rec in caplog.records
|
|
261
|
-
)
|
|
257
|
+
assert any(rec.msg == "plugin-group-not-yet-active" for rec in caplog.records)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/docs/designs/v1.6-auto-router-verification.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coderouter_cli-2.3.0a3 → coderouter_cli-2.3.0a4}/tests/test_capability_registry_cache_control.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|