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.
Files changed (182) hide show
  1. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/CHANGELOG.md +184 -0
  2. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/PKG-INFO +1 -1
  3. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/config/schemas.py +61 -1
  4. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/ingress/app.py +8 -1
  5. coderouter_cli-2.3.0a4/coderouter/plugins/__init__.py +56 -0
  6. coderouter_cli-2.3.0a4/coderouter/plugins/base.py +168 -0
  7. coderouter_cli-2.3.0a4/coderouter/plugins/loader.py +176 -0
  8. coderouter_cli-2.3.0a4/coderouter/plugins/registry.py +83 -0
  9. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/routing/fallback.py +181 -1
  10. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/pyproject.toml +1 -1
  11. coderouter_cli-2.3.0a4/tests/test_plugins_integration.py +198 -0
  12. coderouter_cli-2.3.0a4/tests/test_plugins_loader.py +257 -0
  13. coderouter_cli-2.3.0a4/tests/test_plugins_registry.py +91 -0
  14. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/.gitignore +0 -0
  15. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/LICENSE +0 -0
  16. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/README.en.md +0 -0
  17. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/README.md +0 -0
  18. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/__init__.py +0 -0
  19. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/__main__.py +0 -0
  20. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/adapters/__init__.py +0 -0
  21. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/adapters/anthropic_native.py +0 -0
  22. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/adapters/base.py +0 -0
  23. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/adapters/openai_compat.py +0 -0
  24. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/adapters/registry.py +0 -0
  25. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/cli.py +0 -0
  26. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/cli_stats.py +0 -0
  27. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/config/__init__.py +0 -0
  28. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/config/capability_registry.py +0 -0
  29. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/config/env_file.py +0 -0
  30. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/config/loader.py +0 -0
  31. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/cost.py +0 -0
  32. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/data/__init__.py +0 -0
  33. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/data/model-capabilities.yaml +0 -0
  34. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/doctor.py +0 -0
  35. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/doctor_apply.py +0 -0
  36. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/env_security.py +0 -0
  37. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/errors.py +0 -0
  38. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/__init__.py +0 -0
  39. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/backend_health.py +0 -0
  40. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/context_budget.py +0 -0
  41. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/continuous_probe.py +0 -0
  42. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/drift_actions.py +0 -0
  43. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/drift_detection.py +0 -0
  44. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/memory_pressure.py +0 -0
  45. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/self_healing.py +0 -0
  46. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/guards/tool_loop.py +0 -0
  47. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/ingress/__init__.py +0 -0
  48. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/ingress/anthropic_routes.py +0 -0
  49. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/ingress/dashboard_routes.py +0 -0
  50. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/ingress/metrics_routes.py +0 -0
  51. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/ingress/openai_routes.py +0 -0
  52. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/logging.py +0 -0
  53. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/metrics/__init__.py +0 -0
  54. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/metrics/collector.py +0 -0
  55. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/metrics/prometheus.py +0 -0
  56. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/output_filters.py +0 -0
  57. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/routing/__init__.py +0 -0
  58. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/routing/adaptive.py +0 -0
  59. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/routing/auto_router.py +0 -0
  60. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/routing/budget.py +0 -0
  61. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/routing/capability.py +0 -0
  62. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/state/__init__.py +0 -0
  63. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/state/audit_log.py +0 -0
  64. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/state/replay.py +0 -0
  65. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/state/request_log.py +0 -0
  66. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/state/store.py +0 -0
  67. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/token_estimation.py +0 -0
  68. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/translation/__init__.py +0 -0
  69. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/translation/anthropic.py +0 -0
  70. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/translation/convert.py +0 -0
  71. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/coderouter/translation/tool_repair.py +0 -0
  72. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/architecture.md +0 -0
  73. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/assets/dashboard-demo.png +0 -0
  74. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/context-budget.md +0 -0
  75. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/continuous-probing.md +0 -0
  76. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/designs/v1.5-dashboard-mockup.html +0 -0
  77. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/designs/v1.6-auto-router-verification.md +0 -0
  78. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/designs/v1.6-auto-router.md +0 -0
  79. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/drift-detection.md +0 -0
  80. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/free-tier-guide.en.md +0 -0
  81. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/free-tier-guide.md +0 -0
  82. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/gguf_dl.md +0 -0
  83. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/hf-ollama-models.md +0 -0
  84. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/llamacpp-direct.en.md +0 -0
  85. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/llamacpp-direct.md +0 -0
  86. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/lmstudio-direct.en.md +0 -0
  87. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/lmstudio-direct.md +0 -0
  88. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/openrouter-roster/CHANGES.md +0 -0
  89. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/openrouter-roster/README.md +0 -0
  90. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/openrouter-roster/latest.json +0 -0
  91. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/partial-stitch.md +0 -0
  92. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/quickstart.en.md +0 -0
  93. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/quickstart.md +0 -0
  94. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/retrospectives/v0.4.md +0 -0
  95. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/retrospectives/v0.5-verify.md +0 -0
  96. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/retrospectives/v0.5.md +0 -0
  97. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/retrospectives/v0.6.md +0 -0
  98. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/retrospectives/v0.7.md +0 -0
  99. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/retrospectives/v1.0-verify.md +0 -0
  100. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/retrospectives/v1.0.md +0 -0
  101. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/security.en.md +0 -0
  102. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/security.md +0 -0
  103. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/troubleshooting.en.md +0 -0
  104. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/troubleshooting.md +0 -0
  105. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/usage-guide.en.md +0 -0
  106. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/usage-guide.md +0 -0
  107. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/when-do-i-need-coderouter.en.md +0 -0
  108. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/docs/when-do-i-need-coderouter.md +0 -0
  109. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/examples/.env.example +0 -0
  110. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/examples/providers.auto-custom.yaml +0 -0
  111. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/examples/providers.auto.yaml +0 -0
  112. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/examples/providers.note-2026.yaml +0 -0
  113. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/examples/providers.nvidia-nim.yaml +0 -0
  114. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/examples/providers.raspberrypi.yaml +0 -0
  115. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/examples/providers.v2-context-budget.yaml +0 -0
  116. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/examples/providers.yaml +0 -0
  117. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/scripts/demo_traffic.sh +0 -0
  118. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/scripts/openrouter_roster_diff.py +0 -0
  119. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/scripts/smoke_v2_2.sh +0 -0
  120. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/scripts/verify_v0_5.sh +0 -0
  121. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/scripts/verify_v1_0.sh +0 -0
  122. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/__init__.py +0 -0
  123. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/conftest.py +0 -0
  124. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_adapter_anthropic.py +0 -0
  125. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_audit_log.py +0 -0
  126. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_auto_router.py +0 -0
  127. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_backend_health.py +0 -0
  128. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_budget.py +0 -0
  129. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_capability.py +0 -0
  130. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_capability_degraded_payload.py +0 -0
  131. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_capability_registry.py +0 -0
  132. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_capability_registry_cache_control.py +0 -0
  133. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_claude_code_suitability.py +0 -0
  134. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_cli.py +0 -0
  135. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_cli_stats.py +0 -0
  136. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_config.py +0 -0
  137. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_context_budget.py +0 -0
  138. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_continuous_probe.py +0 -0
  139. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_dashboard_endpoint.py +0 -0
  140. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_doctor.py +0 -0
  141. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_doctor_apply.py +0 -0
  142. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_doctor_cache_probe.py +0 -0
  143. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_drift_actions.py +0 -0
  144. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_drift_detection.py +0 -0
  145. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_drift_detection_integration.py +0 -0
  146. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_env_file.py +0 -0
  147. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_env_security.py +0 -0
  148. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_errors.py +0 -0
  149. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_examples_yaml.py +0 -0
  150. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_fallback.py +0 -0
  151. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_fallback_anthropic.py +0 -0
  152. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_fallback_cache_control.py +0 -0
  153. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_fallback_cache_observed.py +0 -0
  154. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_fallback_misconfig_warn.py +0 -0
  155. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_fallback_paid_gate.py +0 -0
  156. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_fallback_thinking.py +0 -0
  157. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_guards_tool_loop.py +0 -0
  158. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_ingress_anthropic.py +0 -0
  159. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_ingress_profile.py +0 -0
  160. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_memory_pressure.py +0 -0
  161. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_metrics_cache.py +0 -0
  162. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_metrics_collector.py +0 -0
  163. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_metrics_cost.py +0 -0
  164. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_metrics_endpoint.py +0 -0
  165. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_metrics_jsonl.py +0 -0
  166. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_metrics_prometheus.py +0 -0
  167. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_metrics_prometheus_cache.py +0 -0
  168. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_openai_compat.py +0 -0
  169. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_openrouter_roster_diff.py +0 -0
  170. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_output_filters.py +0 -0
  171. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_output_filters_adapters.py +0 -0
  172. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_partial_stitch.py +0 -0
  173. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_reasoning_strip.py +0 -0
  174. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_request_log.py +0 -0
  175. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_routing_adaptive.py +0 -0
  176. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_self_healing.py +0 -0
  177. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_setup_sh.py +0 -0
  178. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_state_store.py +0 -0
  179. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_token_estimation.py +0 -0
  180. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_tool_repair.py +0 -0
  181. {coderouter_cli-2.2.0 → coderouter_cli-2.3.0a4}/tests/test_translation_anthropic.py +0 -0
  182. {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.2.0
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
- engine = FallbackEngine(config)
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