coderouter-cli 2.2.0__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.2.0 → coderouter_cli-2.3.0a4}/CHANGELOG.md +184 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/PKG-INFO +1 -1
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/config/schemas.py +61 -1
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/ingress/app.py +8 -1
- coderouter_cli-2.3.0a4/coderouter/plugins/__init__.py +56 -0
- coderouter_cli-2.3.0a4/coderouter/plugins/base.py +168 -0
- coderouter_cli-2.3.0a4/coderouter/plugins/loader.py +176 -0
- coderouter_cli-2.3.0a4/coderouter/plugins/registry.py +83 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/routing/fallback.py +181 -1
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/pyproject.toml +1 -1
- coderouter_cli-2.3.0a4/tests/test_plugins_integration.py +198 -0
- coderouter_cli-2.3.0a4/tests/test_plugins_loader.py +257 -0
- coderouter_cli-2.3.0a4/tests/test_plugins_registry.py +91 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/.gitignore +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/LICENSE +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/README.en.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/README.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/__init__.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/__main__.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/adapters/__init__.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/adapters/anthropic_native.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/adapters/base.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/adapters/openai_compat.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/adapters/registry.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/cli.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/cli_stats.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/config/__init__.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/config/capability_registry.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/config/env_file.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/config/loader.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/cost.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/data/__init__.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/data/model-capabilities.yaml +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/doctor.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/doctor_apply.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/env_security.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/errors.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/__init__.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/backend_health.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/context_budget.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/continuous_probe.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/drift_actions.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/drift_detection.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/memory_pressure.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/self_healing.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/tool_loop.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/ingress/__init__.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/ingress/anthropic_routes.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/ingress/dashboard_routes.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/ingress/metrics_routes.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/ingress/openai_routes.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/logging.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/metrics/__init__.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/metrics/collector.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/metrics/prometheus.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/output_filters.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/routing/__init__.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/routing/adaptive.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/routing/auto_router.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/routing/budget.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/routing/capability.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/state/__init__.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/state/audit_log.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/state/replay.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/state/request_log.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/state/store.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/token_estimation.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/translation/__init__.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/translation/anthropic.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/translation/convert.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/translation/tool_repair.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/architecture.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/assets/dashboard-demo.png +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/context-budget.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/continuous-probing.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/designs/v1.5-dashboard-mockup.html +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/designs/v1.6-auto-router-verification.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/designs/v1.6-auto-router.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/drift-detection.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/free-tier-guide.en.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/free-tier-guide.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/gguf_dl.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/hf-ollama-models.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/llamacpp-direct.en.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/llamacpp-direct.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/lmstudio-direct.en.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/lmstudio-direct.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/openrouter-roster/CHANGES.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/openrouter-roster/README.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/openrouter-roster/latest.json +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/partial-stitch.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/quickstart.en.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/quickstart.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/retrospectives/v0.4.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/retrospectives/v0.5-verify.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/retrospectives/v0.5.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/retrospectives/v0.6.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/retrospectives/v0.7.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/retrospectives/v1.0-verify.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/retrospectives/v1.0.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/security.en.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/security.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/troubleshooting.en.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/troubleshooting.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/usage-guide.en.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/usage-guide.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/when-do-i-need-coderouter.en.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/when-do-i-need-coderouter.md +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/examples/.env.example +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/examples/providers.auto-custom.yaml +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/examples/providers.auto.yaml +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/examples/providers.note-2026.yaml +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/examples/providers.nvidia-nim.yaml +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/examples/providers.raspberrypi.yaml +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/examples/providers.v2-context-budget.yaml +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/examples/providers.yaml +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/scripts/demo_traffic.sh +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/scripts/openrouter_roster_diff.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/scripts/smoke_v2_2.sh +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/scripts/verify_v0_5.sh +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/scripts/verify_v1_0.sh +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/__init__.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/conftest.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_adapter_anthropic.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_audit_log.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_auto_router.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_backend_health.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_budget.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_capability.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_capability_degraded_payload.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_capability_registry.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_capability_registry_cache_control.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_claude_code_suitability.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_cli.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_cli_stats.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_config.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_context_budget.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_continuous_probe.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_dashboard_endpoint.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_doctor.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_doctor_apply.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_doctor_cache_probe.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_drift_actions.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_drift_detection.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_drift_detection_integration.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_env_file.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_env_security.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_errors.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_examples_yaml.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_fallback.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_fallback_anthropic.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_fallback_cache_control.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_fallback_cache_observed.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_fallback_misconfig_warn.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_fallback_paid_gate.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_fallback_thinking.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_guards_tool_loop.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_ingress_anthropic.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_ingress_profile.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_memory_pressure.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_metrics_cache.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_metrics_collector.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_metrics_cost.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_metrics_endpoint.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_metrics_jsonl.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_metrics_prometheus.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_metrics_prometheus_cache.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_openai_compat.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_openrouter_roster_diff.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_output_filters.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_output_filters_adapters.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_partial_stitch.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_reasoning_strip.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_request_log.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_routing_adaptive.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_self_healing.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_setup_sh.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_state_store.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_token_estimation.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_tool_repair.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_translation_anthropic.py +0 -0
- {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_translation_reverse.py +0 -0
|
@@ -6,6 +6,190 @@ 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
|
+
|
|
54
|
+
## [v2.3.0a3] — 2026-05-08 (Plugin SDK — LogRecord.module collision fix)
|
|
55
|
+
|
|
56
|
+
Patch over `v2.3.0a2`. The wheel-install-and-test job in CI surfaced
|
|
57
|
+
one more issue that the source-tree test runs hadn't caught.
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- **`KeyError: "Attempt to overwrite 'module' in LogRecord"`** in
|
|
62
|
+
`discover_and_load`. Python's `logging` module reserves several
|
|
63
|
+
attribute names on `LogRecord` (``name`` / ``msg`` / ``args`` /
|
|
64
|
+
``levelname`` / ``levelno`` / ``pathname`` / ``filename`` /
|
|
65
|
+
**``module``** / ``lineno`` / ``funcName`` / ``exc_info`` /
|
|
66
|
+
``exc_text`` / ``stack_info`` / ``created`` / ``msecs`` /
|
|
67
|
+
``relativeCreated`` / ``thread`` / ``threadName`` / ``processName``
|
|
68
|
+
/ ``process`` / ``message`` / ``asctime``); passing any of these
|
|
69
|
+
via ``extra=`` raises ``KeyError`` rather than silently overwriting.
|
|
70
|
+
v2.3.0a1's `plugin-loaded` and `plugin-load-failed` log lines used
|
|
71
|
+
``"module"`` as an extra key (intended to mean "the module:attr
|
|
72
|
+
string from `entry_point.value`"), which collided.
|
|
73
|
+
|
|
74
|
+
Renamed the key to ``"entry_point"`` everywhere in
|
|
75
|
+
`coderouter/plugins/loader.py`. Audited every `extra=` payload in
|
|
76
|
+
the new plugins module + the engine's hook helpers — none of them
|
|
77
|
+
use any of the other reserved names.
|
|
78
|
+
|
|
79
|
+
### Files touched
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
M coderouter/plugins/loader.py — "module" → "entry_point" (×2)
|
|
83
|
+
M CHANGELOG.md, pyproject.toml — 2.3.0a2 → 2.3.0a3
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
No other behavioral change. Downstream consumers that don't rely on
|
|
87
|
+
the structured log shape are unaffected; anyone parsing the JSON
|
|
88
|
+
log lines should rename `module` → `entry_point` to match.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## [v2.3.0a2] — 2026-05-08 (Plugin SDK — CI fixes)
|
|
93
|
+
|
|
94
|
+
Patch over `v2.3.0a1`. The Plugin SDK addition was sound but two
|
|
95
|
+
issues showed up in the test matrix and have been fixed:
|
|
96
|
+
|
|
97
|
+
### Fixed
|
|
98
|
+
|
|
99
|
+
- **`'FallbackEngine' object has no attribute 'plugins'`** —
|
|
100
|
+
Many existing tests construct the engine via
|
|
101
|
+
``FallbackEngine.__new__`` to bypass full initialization (only
|
|
102
|
+
``config`` + ``_adapters`` are populated). The new direct
|
|
103
|
+
attribute ``self.plugins`` was missing on those instances and
|
|
104
|
+
raised ``AttributeError`` whenever the engine reached the hook
|
|
105
|
+
helpers. Converted to the same lazy-property pattern that
|
|
106
|
+
``_adaptive`` / ``_budget`` / ``_memory_pressure_guard`` already
|
|
107
|
+
use: store under ``_plugin_registry`` in ``__init__``, surface
|
|
108
|
+
via a ``plugins`` property that lazily builds an empty registry
|
|
109
|
+
when the underlying attribute is missing. Bypass-tests now see
|
|
110
|
+
an empty registry and the hook helpers short-circuit cleanly.
|
|
111
|
+
|
|
112
|
+
- **`LogRecord` assertions in `test_plugins_loader.py` /
|
|
113
|
+
`test_plugins_integration.py`** — used ``rec.message`` which
|
|
114
|
+
isn't always populated (depends on whether a Formatter has
|
|
115
|
+
processed the record). Switched to ``rec.msg`` with exact
|
|
116
|
+
match, matching the rest of the test suite's convention
|
|
117
|
+
(e.g. ``test_fallback_paid_gate.py``,
|
|
118
|
+
``test_memory_pressure.py``) where structured-log event names
|
|
119
|
+
are tested via ``rec.msg == "<event-name>"``.
|
|
120
|
+
|
|
121
|
+
### Files touched
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
M coderouter/routing/fallback.py — lazy plugins property
|
|
125
|
+
M tests/test_plugins_loader.py — rec.msg ==
|
|
126
|
+
M tests/test_plugins_integration.py — rec.msg ==
|
|
127
|
+
M CHANGELOG.md, pyproject.toml — 2.3.0a1 → 2.3.0a2
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
No behavioral change vs `v2.3.0a1`. If you've already pinned
|
|
131
|
+
`v2.3.0a1` in a downstream that doesn't construct engines via
|
|
132
|
+
``__new__`` and doesn't run our test suite, the upgrade is a
|
|
133
|
+
no-op for runtime.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## [v2.3.0a1] — 2026-05-08 (Plugin SDK)
|
|
138
|
+
|
|
139
|
+
**Theme: in-process plugin SDK. Core 5 deps stays untouched.** v2.3.0a1 adds the plugin discovery + dispatch infrastructure that ``coderouter-plugin-memory`` 0.1.0+ will consume. Two of the six designed extension points (``input_filter`` and ``observer``) are wired into the engine; the other four (``frontend`` / ``guard`` / ``output_filter`` / ``adapter``) ship as Protocol contracts only — plugin authors can target them today, but engine integration is deferred until a real plugin drives the requirement (v2.4+).
|
|
140
|
+
|
|
141
|
+
### Plugin SDK (new module: ``coderouter.plugins``)
|
|
142
|
+
|
|
143
|
+
| Component | What it does |
|
|
144
|
+
|---|---|
|
|
145
|
+
| ``coderouter.plugins.base`` | Six ``Protocol`` definitions (InputFilter, Observer, Frontend, Guard, OutputFilter, Adapter). All ``runtime_checkable`` so ``isinstance(x, InputFilter)`` works for diagnostics. |
|
|
146
|
+
| ``coderouter.plugins.loader`` | Reads ``importlib.metadata.entry_points`` under ``coderouter.<group>`` and applies the user's explicit ``plugins.enabled`` allowlist. Failures are logged + degraded — never abort startup. |
|
|
147
|
+
| ``coderouter.plugins.registry`` | Group-keyed container. ``input_filters`` / ``observers`` properties return defensive copies. |
|
|
148
|
+
| ``PluginsConfig`` (in ``schemas.py``) | New ``plugins:`` block in ``providers.yaml`` — ``enabled`` list + ``config`` dict. Absent → identical behavior to v2.2.0. |
|
|
149
|
+
| ``FallbackEngine`` integration | ``__init__`` now takes ``plugins=PluginRegistry``; ``generate_anthropic`` runs the InputFilter chain before chain dispatch; both Anthropic paths fan out ``request_completed`` to observers as fire-and-forget asyncio tasks. The no-plugin code path is bit-identical to v2.2.0. |
|
|
150
|
+
| ``ingress/app.py`` | ``create_app`` calls ``discover_and_load`` and hands the registry to the engine. |
|
|
151
|
+
|
|
152
|
+
### Supply-chain defense
|
|
153
|
+
|
|
154
|
+
``pip install coderouter-plugin-X`` is **not** sufficient to activate a plugin. The user must also list its entry-point name under ``plugins.enabled`` in ``providers.yaml``. Unlisted-but-installed entry points are logged ``plugin-skipped`` and never instantiated, so a compromised transitive dependency cannot wedge itself into the request flow.
|
|
155
|
+
|
|
156
|
+
### Failure semantics
|
|
157
|
+
|
|
158
|
+
| Failure | Engine behavior |
|
|
159
|
+
|---|---|
|
|
160
|
+
| ``importlib.metadata`` finds no entry point with an enabled name | ``plugin-not-found`` warn (one per missing name); engine boots normally. |
|
|
161
|
+
| Plugin module import fails | ``plugin-load-failed`` error; engine boots without that plugin. |
|
|
162
|
+
| Plugin ``__init__`` raises | Same — error logged, plugin skipped. |
|
|
163
|
+
| ``InputFilter.transform`` raises | ``input-filter-failed`` warn; pre-mutation request flows to the next filter / chain. |
|
|
164
|
+
| ``Observer.on_event`` raises | ``observer-failed`` warn; engine response is unaffected (fanout is fire-and-forget). |
|
|
165
|
+
|
|
166
|
+
### Backward compatibility
|
|
167
|
+
|
|
168
|
+
100%. ``providers.yaml`` files written for v2.2.0 keep working unchanged because ``plugins`` is optional and defaults to ``None``. The ``FallbackEngine(config)`` legacy constructor keeps working too — the new ``plugins=`` parameter has a sane default.
|
|
169
|
+
|
|
170
|
+
### Files changed
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
A coderouter/plugins/__init__.py
|
|
174
|
+
A coderouter/plugins/base.py
|
|
175
|
+
A coderouter/plugins/loader.py
|
|
176
|
+
A coderouter/plugins/registry.py
|
|
177
|
+
M coderouter/config/schemas.py — PluginsConfig + CodeRouterConfig.plugins field
|
|
178
|
+
M coderouter/routing/fallback.py — engine __init__, _apply_input_filters,
|
|
179
|
+
_fanout_observers, _safe_observe + hook calls
|
|
180
|
+
M coderouter/ingress/app.py — discover_and_load wired into create_app
|
|
181
|
+
A tests/test_plugins_registry.py
|
|
182
|
+
A tests/test_plugins_loader.py
|
|
183
|
+
A tests/test_plugins_integration.py
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Out-of-scope (deferred)
|
|
187
|
+
|
|
188
|
+
- Engine integration for ``frontend`` / ``guard`` / ``output_filter`` / ``adapter`` — Protocol contracts only.
|
|
189
|
+
- ``coderouter-plugin-memory`` itself — separate repo, separate release cadence (0.1.0 lands after this Core release publishes).
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
9
193
|
## [v2.2.0] — 2026-05-06 (Self-healing + Multi-day operation + Replay)
|
|
10
194
|
|
|
11
195
|
**Theme: 自己修復 + 状態永続化 + 統計リプレイで "無人長時間運用" 基盤を完成。** v2.0-J で UNHEALTHY provider の自動除外 + restart + 復帰を実装、v2.0-K で sqlite3 StateStore + 構造化 audit log + request journal + `coderouter replay` 統計 A/B 分析を実装。v2.2 で Unsloth Studio 由来の堅牢化 3 件を吸収。**6 系統障害全対処 + 自己修復 + 永続化 + リプレイ**に到達。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coderouter-cli
|
|
3
|
-
Version: 2.
|
|
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
|
|
@@ -14,7 +14,7 @@ Design notes (see plan.md §2 / §5.4):
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import re
|
|
17
|
-
from typing import Literal, Self
|
|
17
|
+
from typing import Any, Literal, Self
|
|
18
18
|
|
|
19
19
|
from pydantic import BaseModel, ConfigDict, Field, HttpUrl, model_validator
|
|
20
20
|
|
|
@@ -852,6 +852,51 @@ class AutoRouterConfig(BaseModel):
|
|
|
852
852
|
)
|
|
853
853
|
|
|
854
854
|
|
|
855
|
+
class PluginsConfig(BaseModel):
|
|
856
|
+
"""The ``plugins:`` block in providers.yaml (v2.3.0).
|
|
857
|
+
|
|
858
|
+
Declarative opt-in for in-process plugins distributed as separate
|
|
859
|
+
PyPI packages (``coderouter-plugin-*``). Two-step gating:
|
|
860
|
+
|
|
861
|
+
1. ``pip install coderouter-plugin-X`` makes the entry point
|
|
862
|
+
discoverable.
|
|
863
|
+
2. The plugin's entry-point name MUST appear in :attr:`enabled`
|
|
864
|
+
before the loader will instantiate it.
|
|
865
|
+
|
|
866
|
+
Step 2 is the supply-chain defense: a malicious transitive dep
|
|
867
|
+
cannot wedge itself into the request flow without an explicit
|
|
868
|
+
user action in providers.yaml. See
|
|
869
|
+
:mod:`coderouter.plugins.loader` for the full discovery logic.
|
|
870
|
+
"""
|
|
871
|
+
|
|
872
|
+
model_config = ConfigDict(extra="forbid")
|
|
873
|
+
|
|
874
|
+
enabled: list[str] = Field(
|
|
875
|
+
default_factory=list,
|
|
876
|
+
description=(
|
|
877
|
+
"v2.3.0: ordered list of plugin entry-point names to load. "
|
|
878
|
+
"An entry-point name is the LHS of an entry in a plugin's "
|
|
879
|
+
"``[project.entry-points.\"coderouter.<group>\"]`` block — "
|
|
880
|
+
"e.g. ``memory`` for ``coderouter-plugin-memory``. Order "
|
|
881
|
+
"controls the order InputFilter chains apply (each filter "
|
|
882
|
+
"sees the previous filter's output). Empty list = no "
|
|
883
|
+
"plugins active (default behavior, identical to v2.2.0)."
|
|
884
|
+
),
|
|
885
|
+
)
|
|
886
|
+
config: dict[str, dict[str, Any]] = Field(
|
|
887
|
+
default_factory=dict,
|
|
888
|
+
description=(
|
|
889
|
+
"v2.3.0: per-plugin keyword arguments. The dict at "
|
|
890
|
+
"``config[<plugin-name>]`` is splatted into the plugin's "
|
|
891
|
+
"``__init__`` as ``**kwargs``. Validation of each "
|
|
892
|
+
"sub-dict's schema is the plugin's responsibility — Core "
|
|
893
|
+
"stays out of plugin-specific config shapes. Plugins not "
|
|
894
|
+
"listed in :attr:`enabled` are ignored even if they have "
|
|
895
|
+
"config entries here."
|
|
896
|
+
),
|
|
897
|
+
)
|
|
898
|
+
|
|
899
|
+
|
|
855
900
|
class CodeRouterConfig(BaseModel):
|
|
856
901
|
"""Top-level config loaded from providers.yaml."""
|
|
857
902
|
|
|
@@ -1001,6 +1046,21 @@ class CodeRouterConfig(BaseModel):
|
|
|
1001
1046
|
),
|
|
1002
1047
|
)
|
|
1003
1048
|
|
|
1049
|
+
# v2.3.0: in-process plugin SDK. Optional — when None, the engine
|
|
1050
|
+
# builds an empty ``PluginRegistry`` and the hook chains are
|
|
1051
|
+
# short-circuited (zero-cost path, identical to v2.2.0 behavior).
|
|
1052
|
+
plugins: PluginsConfig | None = Field(
|
|
1053
|
+
default=None,
|
|
1054
|
+
description=(
|
|
1055
|
+
"v2.3.0: in-process plugin configuration. Plugins are "
|
|
1056
|
+
"distributed as separate PyPI packages (e.g. "
|
|
1057
|
+
"``coderouter-plugin-memory``); this block lists which of "
|
|
1058
|
+
"the installed plugins to actually activate, and supplies "
|
|
1059
|
+
"their per-plugin keyword arguments. Absent or empty = no "
|
|
1060
|
+
"plugins (zero-cost, backward-compatible default)."
|
|
1061
|
+
),
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1004
1064
|
@model_validator(mode="after")
|
|
1005
1065
|
def _check_default_profile_exists(self) -> CodeRouterConfig:
|
|
1006
1066
|
"""v0.6-A: surface a typo'd ``default_profile`` at load time.
|
|
@@ -17,6 +17,7 @@ from coderouter.ingress.metrics_routes import router as metrics_router
|
|
|
17
17
|
from coderouter.ingress.openai_routes import router as openai_router
|
|
18
18
|
from coderouter.logging import configure_logging, get_logger
|
|
19
19
|
from coderouter.metrics import install_collector
|
|
20
|
+
from coderouter.plugins import discover_and_load
|
|
20
21
|
from coderouter.routing import FallbackEngine
|
|
21
22
|
from coderouter.routing.capability import check_claude_code_chain_suitability
|
|
22
23
|
|
|
@@ -38,7 +39,13 @@ def create_app(config_path: str | None = None) -> FastAPI:
|
|
|
38
39
|
# so multiple create_app() calls (tests) don't stack handlers.
|
|
39
40
|
install_collector()
|
|
40
41
|
config = load_config(config_path)
|
|
41
|
-
|
|
42
|
+
# v2.3.0: discover plugins from importlib.metadata entry points and
|
|
43
|
+
# apply the user's explicit ``plugins.enabled`` allowlist. When the
|
|
44
|
+
# ``plugins`` block is absent or empty, the loader returns an empty
|
|
45
|
+
# registry and the engine's hook loops short-circuit — the request
|
|
46
|
+
# flow is bit-identical to v2.2.0 in that default case.
|
|
47
|
+
plugin_registry = discover_and_load(config)
|
|
48
|
+
engine = FallbackEngine(config, plugins=plugin_registry)
|
|
42
49
|
|
|
43
50
|
@asynccontextmanager
|
|
44
51
|
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Plugin SDK — extension points for in-process CodeRouter plugins (v2.3.0).
|
|
2
|
+
|
|
3
|
+
CodeRouter Core stays at 5 deps. Optional functionality (memory, PII
|
|
4
|
+
redaction, observability bridges, alternative ingresses, etc.) ships
|
|
5
|
+
as separate ``coderouter-plugin-*`` packages on PyPI. Each plugin
|
|
6
|
+
declares one or more *entry points* in its ``pyproject.toml``; this
|
|
7
|
+
SDK discovers them at startup, applies the user's explicit ``enabled``
|
|
8
|
+
allowlist (supply chain defense, see :func:`loader.discover_and_load`),
|
|
9
|
+
and exposes a :class:`registry.PluginRegistry` to the engine.
|
|
10
|
+
|
|
11
|
+
Six extension points are defined as :mod:`Protocols <typing>` in
|
|
12
|
+
:mod:`coderouter.plugins.base`. v2.3.0 implements the engine-side hook
|
|
13
|
+
integration for two of them (``input_filter`` and ``observer``); the
|
|
14
|
+
remaining four (``frontend``, ``guard``, ``output_filter``,
|
|
15
|
+
``adapter``) have a stable Protocol contract so plugins can target
|
|
16
|
+
them now, but the engine doesn't yet wire them into the request flow.
|
|
17
|
+
That's intentional — adding contracts is cheap, adding hot-path code
|
|
18
|
+
isn't, so we wait for a real plugin to drive each integration.
|
|
19
|
+
|
|
20
|
+
Public API:
|
|
21
|
+
|
|
22
|
+
- :class:`InputFilter`, :class:`Observer` — implementable today.
|
|
23
|
+
- :class:`Frontend`, :class:`Guard`, :class:`OutputFilter`,
|
|
24
|
+
:class:`Adapter` — Protocol-only, integration in v2.4+.
|
|
25
|
+
- :func:`discover_and_load` — called once during config load.
|
|
26
|
+
- :class:`PluginRegistry` — held by :class:`FallbackEngine`.
|
|
27
|
+
"""
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from coderouter.plugins.base import (
|
|
31
|
+
Adapter,
|
|
32
|
+
Frontend,
|
|
33
|
+
Guard,
|
|
34
|
+
InputFilter,
|
|
35
|
+
Observer,
|
|
36
|
+
OutputFilter,
|
|
37
|
+
)
|
|
38
|
+
from coderouter.plugins.loader import (
|
|
39
|
+
PLUGIN_GROUPS_FUTURE,
|
|
40
|
+
PLUGIN_GROUPS_V2_3,
|
|
41
|
+
discover_and_load,
|
|
42
|
+
)
|
|
43
|
+
from coderouter.plugins.registry import PluginRegistry
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"PLUGIN_GROUPS_FUTURE",
|
|
47
|
+
"PLUGIN_GROUPS_V2_3",
|
|
48
|
+
"Adapter",
|
|
49
|
+
"Frontend",
|
|
50
|
+
"Guard",
|
|
51
|
+
"InputFilter",
|
|
52
|
+
"Observer",
|
|
53
|
+
"OutputFilter",
|
|
54
|
+
"PluginRegistry",
|
|
55
|
+
"discover_and_load",
|
|
56
|
+
]
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Plugin SDK Protocol contracts (v2.3.0).
|
|
2
|
+
|
|
3
|
+
Six extension points are defined here. Two are wired into the engine
|
|
4
|
+
in v2.3.0 (:class:`InputFilter`, :class:`Observer`); four are
|
|
5
|
+
Protocol-only (:class:`Frontend`, :class:`Guard`, :class:`OutputFilter`,
|
|
6
|
+
:class:`Adapter`) and will get engine integration when a real plugin
|
|
7
|
+
drives the requirement — see ``docs/inside/plugin-architecture-draft.md``
|
|
8
|
+
§3 for the full design rationale.
|
|
9
|
+
|
|
10
|
+
Why declare contracts ahead of integration? It lets a plugin author
|
|
11
|
+
build against the SDK *now* (and ship a working ``coderouter.frontend``
|
|
12
|
+
plugin once integration lands) without us having to do a
|
|
13
|
+
backward-incompatible Protocol revision later. ``runtime_checkable``
|
|
14
|
+
is used so :func:`isinstance` checks work in the loader for clearer
|
|
15
|
+
error messages.
|
|
16
|
+
|
|
17
|
+
All hooks are async. Failures must NEVER block the engine response —
|
|
18
|
+
the engine wraps every hook call in try/except and degrades gracefully
|
|
19
|
+
(see ``coderouter/routing/fallback.py`` integration site).
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
# Avoid circular imports at runtime — Protocol typing only needs
|
|
27
|
+
# these for documentation and static analysis.
|
|
28
|
+
from coderouter.config.schemas import CodeRouterConfig
|
|
29
|
+
from coderouter.translation.anthropic import (
|
|
30
|
+
AnthropicRequest,
|
|
31
|
+
AnthropicResponse,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ====================================================================
|
|
36
|
+
# Active hooks (engine integration in v2.3.0)
|
|
37
|
+
# ====================================================================
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@runtime_checkable
|
|
41
|
+
class InputFilter(Protocol):
|
|
42
|
+
"""Mutates an inbound :class:`AnthropicRequest` before chain dispatch.
|
|
43
|
+
|
|
44
|
+
Plugins MUST treat the input as immutable: build the modified
|
|
45
|
+
request with ``request.model_copy(update={...})`` and return the
|
|
46
|
+
new instance. The engine assumes the returned value is a
|
|
47
|
+
*replacement*, not the same object.
|
|
48
|
+
|
|
49
|
+
Engine semantics:
|
|
50
|
+
|
|
51
|
+
- Filters run sequentially in the order they appear in
|
|
52
|
+
``plugins.enabled``. The first filter sees the raw inbound
|
|
53
|
+
request; each subsequent filter sees the previous filter's
|
|
54
|
+
output.
|
|
55
|
+
- If :meth:`transform` raises, the engine logs ``input-filter-failed``
|
|
56
|
+
and continues with the *pre-mutation* request for that filter.
|
|
57
|
+
Other filters still run.
|
|
58
|
+
- The transform runs *after* the v1.9-E tool-loop guard but
|
|
59
|
+
*before* chain resolution and the v2.0-F context budget guard.
|
|
60
|
+
That order matters: filters can grow ``request.system`` (e.g.
|
|
61
|
+
memory injection) without bypassing the budget cap, because the
|
|
62
|
+
budget guard reruns over the post-filter payload.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
name: str
|
|
66
|
+
|
|
67
|
+
async def transform(self, request: AnthropicRequest) -> AnthropicRequest: ...
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@runtime_checkable
|
|
71
|
+
class Observer(Protocol):
|
|
72
|
+
"""Passive event consumer. Cannot mutate anything.
|
|
73
|
+
|
|
74
|
+
The engine calls observers via :func:`asyncio.create_task` (fire
|
|
75
|
+
and forget). An observer that takes 30s won't slow a 200ms
|
|
76
|
+
response down — it just runs in the background. If it raises,
|
|
77
|
+
the engine logs ``observer-failed`` and discards the exception.
|
|
78
|
+
|
|
79
|
+
Event vocabulary (v2.3.0):
|
|
80
|
+
|
|
81
|
+
- ``request_completed`` — payload: ``{request, response,
|
|
82
|
+
latency_ms, provider}``. Fires after a successful Anthropic or
|
|
83
|
+
OpenAI-compat response. Streaming requests fire this event once,
|
|
84
|
+
after the SSE stream has terminated (not on each chunk).
|
|
85
|
+
|
|
86
|
+
Plugins MUST tolerate unknown event types — the vocabulary will
|
|
87
|
+
grow over time, and an old plugin should silently ignore events
|
|
88
|
+
it doesn't recognize rather than crash.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
name: str
|
|
92
|
+
|
|
93
|
+
async def on_event(self, event_type: str, payload: dict[str, Any]) -> None: ...
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ====================================================================
|
|
97
|
+
# Future hooks (Protocol-only, engine integration in v2.4+)
|
|
98
|
+
# ====================================================================
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@runtime_checkable
|
|
102
|
+
class Frontend(Protocol):
|
|
103
|
+
"""Alternative ingress (Discord, Telegram, MCP, Voice, ...).
|
|
104
|
+
|
|
105
|
+
Frontends *run* the engine rather than being called by it.
|
|
106
|
+
Integration plan: each frontend gets its own
|
|
107
|
+
:func:`asyncio.create_task` started by ``coderouter serve``;
|
|
108
|
+
SIGTERM cancels all of them and waits for cleanup.
|
|
109
|
+
|
|
110
|
+
Not yet integrated — Protocol contract only.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
name: str
|
|
114
|
+
|
|
115
|
+
async def serve(
|
|
116
|
+
self, engine: Any, config: CodeRouterConfig
|
|
117
|
+
) -> None: ...
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@runtime_checkable
|
|
121
|
+
class Guard(Protocol):
|
|
122
|
+
"""Reliability guard, parallel to the built-in tool-loop guard.
|
|
123
|
+
|
|
124
|
+
Runs synchronously on the hot path, so implementations must be
|
|
125
|
+
cheap. Heavy work (HTTP calls, model invocations, etc.) belongs
|
|
126
|
+
in an :class:`Observer`.
|
|
127
|
+
|
|
128
|
+
Not yet integrated — Protocol contract only.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
name: str
|
|
132
|
+
|
|
133
|
+
async def check(
|
|
134
|
+
self, request: AnthropicRequest, config: CodeRouterConfig
|
|
135
|
+
) -> None: ...
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@runtime_checkable
|
|
139
|
+
class OutputFilter(Protocol):
|
|
140
|
+
"""Mutates a response before it returns to the client.
|
|
141
|
+
|
|
142
|
+
For streaming, the engine plans to call :meth:`transform` once
|
|
143
|
+
per ``AnthropicStreamEvent``; for non-streaming, once per
|
|
144
|
+
response.
|
|
145
|
+
|
|
146
|
+
Not yet integrated — Protocol contract only.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
name: str
|
|
150
|
+
|
|
151
|
+
async def transform(
|
|
152
|
+
self, response: AnthropicResponse
|
|
153
|
+
) -> AnthropicResponse: ...
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@runtime_checkable
|
|
157
|
+
class Adapter(Protocol):
|
|
158
|
+
"""New ``kind`` value in providers.yaml (e.g. ``bedrock-native``).
|
|
159
|
+
|
|
160
|
+
Plugins implement the same async surface as
|
|
161
|
+
:class:`coderouter.adapters.base.BaseAdapter` so the engine can
|
|
162
|
+
treat them indistinguishably from built-in adapters once the
|
|
163
|
+
loader registers the new ``kind`` mapping.
|
|
164
|
+
|
|
165
|
+
Not yet integrated — Protocol contract only.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
name: str
|