coderouter-cli 2.5.4__tar.gz → 2.5.5__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 (205) hide show
  1. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/CHANGELOG.md +41 -0
  2. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/PKG-INFO +1 -1
  3. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/translation/anthropic.py +124 -1
  4. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/pyproject.toml +1 -1
  5. coderouter_cli-2.5.5/tests/test_role_normalization.py +201 -0
  6. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/.gitignore +0 -0
  7. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/LICENSE +0 -0
  8. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/README.en.md +0 -0
  9. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/README.md +0 -0
  10. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/__init__.py +0 -0
  11. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/__main__.py +0 -0
  12. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/adapters/__init__.py +0 -0
  13. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/adapters/anthropic_native.py +0 -0
  14. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/adapters/base.py +0 -0
  15. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/adapters/openai_compat.py +0 -0
  16. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/adapters/registry.py +0 -0
  17. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/cli.py +0 -0
  18. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/cli_stats.py +0 -0
  19. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/config/__init__.py +0 -0
  20. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/config/capability_registry.py +0 -0
  21. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/config/env_file.py +0 -0
  22. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/config/loader.py +0 -0
  23. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/config/schemas.py +0 -0
  24. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/cost.py +0 -0
  25. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/data/__init__.py +0 -0
  26. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/data/model-capabilities.yaml +0 -0
  27. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/doctor.py +0 -0
  28. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/doctor_apply.py +0 -0
  29. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/env_security.py +0 -0
  30. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/errors.py +0 -0
  31. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/gguf_introspect.py +0 -0
  32. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/guards/__init__.py +0 -0
  33. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/guards/_fingerprint.py +0 -0
  34. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/guards/backend_health.py +0 -0
  35. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/guards/context_budget.py +0 -0
  36. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/guards/continuous_probe.py +0 -0
  37. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/guards/drift_actions.py +0 -0
  38. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/guards/drift_detection.py +0 -0
  39. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/guards/memory_budget.py +0 -0
  40. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/guards/memory_pressure.py +0 -0
  41. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/guards/self_healing.py +0 -0
  42. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/guards/tool_loop.py +0 -0
  43. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/hardware.py +0 -0
  44. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/ingress/__init__.py +0 -0
  45. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/ingress/anthropic_routes.py +0 -0
  46. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/ingress/app.py +0 -0
  47. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/ingress/dashboard_routes.py +0 -0
  48. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/ingress/launcher_routes.py +0 -0
  49. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/ingress/metrics_routes.py +0 -0
  50. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/ingress/openai_routes.py +0 -0
  51. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/logging.py +0 -0
  52. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/metrics/__init__.py +0 -0
  53. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/metrics/collector.py +0 -0
  54. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/metrics/prometheus.py +0 -0
  55. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/output_filters.py +0 -0
  56. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/plugins/__init__.py +0 -0
  57. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/plugins/base.py +0 -0
  58. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/plugins/loader.py +0 -0
  59. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/plugins/registry.py +0 -0
  60. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/routing/__init__.py +0 -0
  61. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/routing/adaptive.py +0 -0
  62. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/routing/auto_router.py +0 -0
  63. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/routing/budget.py +0 -0
  64. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/routing/capability.py +0 -0
  65. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/routing/fallback.py +0 -0
  66. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/state/__init__.py +0 -0
  67. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/state/audit_log.py +0 -0
  68. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/state/replay.py +0 -0
  69. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/state/request_log.py +0 -0
  70. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/state/store.py +0 -0
  71. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/state/suggest_rules.py +0 -0
  72. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/token_estimation.py +0 -0
  73. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/token_estimation_accurate.py +0 -0
  74. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/translation/__init__.py +0 -0
  75. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/translation/convert.py +0 -0
  76. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/coderouter/translation/tool_repair.py +0 -0
  77. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/README.md +0 -0
  78. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/assets/dashboard-demo.png +0 -0
  79. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/backends/gguf_dl.md +0 -0
  80. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/backends/hf-ollama-models.md +0 -0
  81. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/backends/install-backends.en.md +0 -0
  82. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/backends/install-backends.md +0 -0
  83. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/backends/launcher-quickstart.md +0 -0
  84. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/backends/launcher.md +0 -0
  85. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/backends/llamacpp-direct.en.md +0 -0
  86. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/backends/llamacpp-direct.md +0 -0
  87. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/backends/lmstudio-direct.en.md +0 -0
  88. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/backends/lmstudio-direct.md +0 -0
  89. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/backends/verify-ollama-0.23.1.md +0 -0
  90. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/concepts/architecture.md +0 -0
  91. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/concepts/context-budget.md +0 -0
  92. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/concepts/continuous-probing.md +0 -0
  93. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/concepts/drift-detection.md +0 -0
  94. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/concepts/partial-stitch.md +0 -0
  95. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/designs/v1.5-dashboard-mockup.html +0 -0
  96. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/designs/v1.6-auto-router-verification.md +0 -0
  97. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/designs/v1.6-auto-router.md +0 -0
  98. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/guides/free-tier-guide.en.md +0 -0
  99. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/guides/free-tier-guide.md +0 -0
  100. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/guides/security.en.md +0 -0
  101. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/guides/security.md +0 -0
  102. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/guides/troubleshooting.en.md +0 -0
  103. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/guides/troubleshooting.md +0 -0
  104. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/guides/usage-guide.en.md +0 -0
  105. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/guides/usage-guide.md +0 -0
  106. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/low-memory-integration.md +0 -0
  107. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/openrouter-roster/CHANGES.md +0 -0
  108. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/openrouter-roster/README.md +0 -0
  109. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/openrouter-roster/latest.json +0 -0
  110. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/retrospectives/v0.4.md +0 -0
  111. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/retrospectives/v0.5-verify.md +0 -0
  112. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/retrospectives/v0.5.md +0 -0
  113. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/retrospectives/v0.6.md +0 -0
  114. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/retrospectives/v0.7.md +0 -0
  115. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/retrospectives/v1.0-verify.md +0 -0
  116. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/retrospectives/v1.0.md +0 -0
  117. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/start/quickstart.en.md +0 -0
  118. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/start/quickstart.md +0 -0
  119. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/start/when-do-i-need-coderouter.en.md +0 -0
  120. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/docs/start/when-do-i-need-coderouter.md +0 -0
  121. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/examples/.env.example +0 -0
  122. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/examples/providers.auto-custom.yaml +0 -0
  123. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/examples/providers.auto.yaml +0 -0
  124. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/examples/providers.llama-cpp-vllm.yaml +0 -0
  125. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/examples/providers.note-2026.yaml +0 -0
  126. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/examples/providers.nvidia-nim.yaml +0 -0
  127. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/examples/providers.raspberrypi.yaml +0 -0
  128. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/examples/providers.v2-context-budget.yaml +0 -0
  129. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/examples/providers.yaml +0 -0
  130. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/scripts/demo_traffic.sh +0 -0
  131. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/scripts/openrouter_roster_diff.py +0 -0
  132. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/scripts/smoke_v2_2.sh +0 -0
  133. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/scripts/verify-providers.yaml +0 -0
  134. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/scripts/verify_ollama_0_23.py +0 -0
  135. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/scripts/verify_v0_5.sh +0 -0
  136. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/scripts/verify_v1_0.sh +0 -0
  137. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/__init__.py +0 -0
  138. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/conftest.py +0 -0
  139. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_adapter_anthropic.py +0 -0
  140. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_audit_log.py +0 -0
  141. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_auto_router.py +0 -0
  142. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_backend_health.py +0 -0
  143. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_budget.py +0 -0
  144. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_capability.py +0 -0
  145. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_capability_degraded_payload.py +0 -0
  146. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_capability_registry.py +0 -0
  147. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_capability_registry_cache_control.py +0 -0
  148. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_claude_code_suitability.py +0 -0
  149. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_cli.py +0 -0
  150. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_cli_stats.py +0 -0
  151. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_config.py +0 -0
  152. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_context_budget.py +0 -0
  153. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_continuous_probe.py +0 -0
  154. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_dashboard_endpoint.py +0 -0
  155. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_doctor.py +0 -0
  156. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_doctor_apply.py +0 -0
  157. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_doctor_cache_probe.py +0 -0
  158. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_drift_actions.py +0 -0
  159. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_drift_detection.py +0 -0
  160. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_drift_detection_integration.py +0 -0
  161. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_env_file.py +0 -0
  162. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_env_security.py +0 -0
  163. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_errors.py +0 -0
  164. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_examples_yaml.py +0 -0
  165. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_fallback.py +0 -0
  166. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_fallback_anthropic.py +0 -0
  167. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_fallback_cache_control.py +0 -0
  168. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_fallback_cache_observed.py +0 -0
  169. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_fallback_misconfig_warn.py +0 -0
  170. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_fallback_paid_gate.py +0 -0
  171. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_fallback_thinking.py +0 -0
  172. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_gguf_introspect.py +0 -0
  173. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_guards_tool_loop.py +0 -0
  174. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_hardware.py +0 -0
  175. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_ingress_anthropic.py +0 -0
  176. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_ingress_profile.py +0 -0
  177. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_memory_budget.py +0 -0
  178. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_memory_pressure.py +0 -0
  179. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_metrics_cache.py +0 -0
  180. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_metrics_collector.py +0 -0
  181. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_metrics_cost.py +0 -0
  182. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_metrics_endpoint.py +0 -0
  183. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_metrics_jsonl.py +0 -0
  184. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_metrics_prometheus.py +0 -0
  185. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_metrics_prometheus_cache.py +0 -0
  186. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_openai_compat.py +0 -0
  187. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_openrouter_roster_diff.py +0 -0
  188. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_output_filters.py +0 -0
  189. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_output_filters_adapters.py +0 -0
  190. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_partial_stitch.py +0 -0
  191. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_plugins_integration.py +0 -0
  192. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_plugins_loader.py +0 -0
  193. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_plugins_registry.py +0 -0
  194. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_reasoning_strip.py +0 -0
  195. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_repair_byte_fallback.py +0 -0
  196. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_request_log.py +0 -0
  197. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_routing_adaptive.py +0 -0
  198. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_self_healing.py +0 -0
  199. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_setup_sh.py +0 -0
  200. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_state_store.py +0 -0
  201. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_token_estimation.py +0 -0
  202. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_token_estimation_accurate.py +0 -0
  203. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_tool_repair.py +0 -0
  204. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_translation_anthropic.py +0 -0
  205. {coderouter_cli-2.5.4 → coderouter_cli-2.5.5}/tests/test_translation_reverse.py +0 -0
@@ -6,6 +6,47 @@ versioning follows [SemVer](https://semver.org/).
6
6
 
7
7
  ---
8
8
 
9
+ ## [v2.5.5] — 2026-06-06 (Claude Code >= 2.1.154 `system` role normalization)
10
+
11
+ Patch release: ingress-side workaround for a Claude Code CLI regression.
12
+
13
+ ### Fixed
14
+
15
+ - **Claude Code CLI >= 2.1.154 requests no longer 422 at ingress.**
16
+ Claude Code 2.1.154 introduced a regression where it emits messages with
17
+ `role: "system"` (and reportedly `ctx` / `msg`) inside the Anthropic
18
+ `messages` array, which the Messages API spec restricts to
19
+ `user` / `assistant`. CodeRouter's wire validation correctly rejected
20
+ these with `Input should be 'user' or 'assistant'` — breaking every
21
+ request from affected Claude Code versions (2.1.150 and earlier are fine).
22
+
23
+ A new `model_validator(mode="before")` on `AnthropicRequest` now
24
+ normalizes such payloads before validation:
25
+
26
+ - `role: "system"` → text content merged into the top-level `system`
27
+ field (newline-joined after any existing system prompt; text block
28
+ appended when `system` is a block list).
29
+ - Any other non-spec role (`ctx`, `msg`, ...) → coerced to `user`,
30
+ preserving conversation position (Anthropic merges consecutive
31
+ same-role turns, so this is safe).
32
+ - Messages with no salvageable text content are dropped (Anthropic
33
+ rejects empty turns).
34
+ - A `normalized-nonspec-message-roles` warning is logged whenever
35
+ normalization fires.
36
+
37
+ The strict `AnthropicMessage` role enum is **unchanged** — the wire
38
+ model still matches the Anthropic spec, and the native adapter forwards
39
+ a normalized (valid) payload to `api.anthropic.com`, avoiding the same
40
+ 400 upstream.
41
+
42
+ Verified with 16 new unit tests (`tests/test_role_normalization.py`);
43
+ full suite 1191 passed / 0 failed on py3.12.
44
+
45
+ Refs: `anthropics/claude-code#63469`, `anthropics/claude-code#63473`,
46
+ `vllm-project/vllm#44000`
47
+
48
+ ---
49
+
9
50
  ## [v2.5.4] — 2026-06-05 (Gemma `<0xNN>` byte-fallback repair filter)
10
51
 
11
52
  Patch release: a new opt-in output filter that repairs Japanese (and other
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coderouter-cli
3
- Version: 2.5.4
3
+ Version: 2.5.5
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
@@ -10,9 +10,12 @@ through unchanged if a client sends them.
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
+ import logging
13
14
  from typing import Any, Literal
14
15
 
15
- from pydantic import BaseModel, ConfigDict, Field
16
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
17
+
18
+ logger = logging.getLogger(__name__)
16
19
 
17
20
  # ============================================================
18
21
  # Content blocks
@@ -105,6 +108,113 @@ class AnthropicTool(BaseModel):
105
108
  input_schema: dict[str, Any] = Field(default_factory=dict)
106
109
 
107
110
 
111
+ # ============================================================
112
+ # Role normalization (Claude Code CLI >= 2.1.154 workaround)
113
+ # ============================================================
114
+
115
+ _SPEC_MESSAGE_ROLES = frozenset({"user", "assistant"})
116
+
117
+
118
+ def _content_as_text(content: Any) -> str:
119
+ """Best-effort plain-text extraction from a message ``content`` field.
120
+
121
+ Strings pass through; block lists contribute their ``text`` blocks
122
+ joined with newlines; anything else yields "".
123
+ """
124
+ if isinstance(content, str):
125
+ return content
126
+ if isinstance(content, list):
127
+ parts: list[str] = []
128
+ for block in content:
129
+ if isinstance(block, dict) and block.get("type") == "text":
130
+ parts.append(str(block.get("text", "")))
131
+ return "\n".join(p for p in parts if p)
132
+ return ""
133
+
134
+
135
+ def normalize_message_roles(payload: dict[str, Any]) -> dict[str, Any]:
136
+ """Normalize non-spec roles inside ``messages`` before validation.
137
+
138
+ Claude Code CLI >= 2.1.154 has a regression where it emits messages
139
+ with ``role: "system"`` (and reportedly ``ctx`` / ``msg``) inside the
140
+ ``messages`` array. The Anthropic Messages API spec allows only
141
+ ``user`` / ``assistant`` there, so without this hop those requests
142
+ die in validation with "Input should be 'user' or 'assistant'"
143
+ (see anthropics/claude-code#63469, vllm-project/vllm#44000).
144
+
145
+ Policy:
146
+ - ``role: "system"`` → text content merged into the top-level
147
+ ``system`` field (appended after any existing system prompt;
148
+ same join rule as ``convert.to_anthropic_request``).
149
+ - any other non-spec role (``ctx``, ``msg``, ...) → coerced to
150
+ ``user`` so conversation position is preserved. Anthropic
151
+ merges consecutive same-role turns, so this is safe.
152
+ - messages whose salvaged content is empty are dropped entirely
153
+ (Anthropic rejects empty turns).
154
+
155
+ Returns a shallow-copied payload; the caller's dict is not mutated.
156
+ Non-dict message entries (already-validated models) pass through.
157
+ """
158
+ messages = payload.get("messages")
159
+ if not isinstance(messages, list):
160
+ return payload
161
+
162
+ system_texts: list[str] = []
163
+ messages_out: list[Any] = []
164
+ coerced_roles: list[str] = []
165
+
166
+ for msg in messages:
167
+ if not isinstance(msg, dict):
168
+ # Already a validated AnthropicMessage (internal construction
169
+ # path, e.g. convert.to_anthropic_request) — spec roles only.
170
+ messages_out.append(msg)
171
+ continue
172
+ role = msg.get("role")
173
+ if role in _SPEC_MESSAGE_ROLES:
174
+ messages_out.append(msg)
175
+ continue
176
+ if role == "system":
177
+ text = _content_as_text(msg.get("content"))
178
+ if text:
179
+ system_texts.append(text)
180
+ coerced_roles.append("system")
181
+ continue
182
+ # Unknown role (ctx / msg / future surprises): keep its position
183
+ # in the conversation as a user turn; drop if nothing salvageable.
184
+ text = _content_as_text(msg.get("content"))
185
+ coerced_roles.append(str(role))
186
+ if text:
187
+ messages_out.append({"role": "user", "content": text})
188
+
189
+ if not coerced_roles:
190
+ return payload
191
+
192
+ out = dict(payload)
193
+ out["messages"] = messages_out
194
+
195
+ if system_texts:
196
+ joined = "\n".join(system_texts)
197
+ existing = out.get("system")
198
+ if existing is None:
199
+ out["system"] = joined
200
+ elif isinstance(existing, str):
201
+ out["system"] = f"{existing}\n{joined}" if existing else joined
202
+ elif isinstance(existing, list):
203
+ out["system"] = [*existing, {"type": "text", "text": joined}]
204
+ else: # unexpected shape — don't lose the client's value
205
+ out["system"] = existing
206
+
207
+ logger.warning(
208
+ "normalized-nonspec-message-roles",
209
+ extra={
210
+ "roles": coerced_roles,
211
+ "system_merged": bool(system_texts),
212
+ "hint": "client is likely Claude Code CLI >= 2.1.154 (known regression)",
213
+ },
214
+ )
215
+ return out
216
+
217
+
108
218
  # ============================================================
109
219
  # Request
110
220
  # ============================================================
@@ -147,6 +257,19 @@ class AnthropicRequest(BaseModel):
147
257
  # `thinking` beyond what the default minor version accepts.
148
258
  anthropic_beta: str | None = Field(default=None, exclude=True)
149
259
 
260
+ @model_validator(mode="before")
261
+ @classmethod
262
+ def _normalize_roles(cls, data: Any) -> Any:
263
+ """Claude Code >= 2.1.154 sends system/ctx/msg roles in messages.
264
+
265
+ Normalize them before field validation so the request doesn't
266
+ 422 at ingress (and doesn't 400 upstream at api.anthropic.com
267
+ via the native adapter). See ``normalize_message_roles``.
268
+ """
269
+ if isinstance(data, dict):
270
+ return normalize_message_roles(data)
271
+ return data
272
+
150
273
 
151
274
  # ============================================================
152
275
  # Response
@@ -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.5.4"
14
+ version = "2.5.5"
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"
@@ -0,0 +1,201 @@
1
+ """Tests for Claude Code CLI >= 2.1.154 role normalization.
2
+
3
+ Claude Code 2.1.154+ has a regression where it emits ``role: "system"``
4
+ (and reportedly ``ctx`` / ``msg``) inside the Anthropic ``messages``
5
+ array, which the spec restricts to ``user`` / ``assistant``.
6
+ See anthropics/claude-code#63469, vllm-project/vllm#44000.
7
+
8
+ These tests cover ``normalize_message_roles`` and its wiring as a
9
+ ``model_validator(mode="before")`` on ``AnthropicRequest``.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import pytest
15
+ from pydantic import ValidationError
16
+
17
+ # Same import convention as test_translation_anthropic.py: adapters.base
18
+ # must load before coderouter.translation, otherwise a pre-existing circular
19
+ # import (translation → convert → adapters → anthropic_native → convert)
20
+ # blows up at collection time.
21
+ import coderouter.adapters.base # noqa: F401 (import-order anchor)
22
+ from coderouter.translation import AnthropicMessage, AnthropicRequest
23
+ from coderouter.translation.anthropic import normalize_message_roles
24
+
25
+
26
+ def _req(messages, **kwargs):
27
+ payload = {"model": "m", "max_tokens": 100, "messages": messages, **kwargs}
28
+ return AnthropicRequest.model_validate(payload)
29
+
30
+
31
+ # ------------------------------------------------------------
32
+ # The exact Claude Code >= 2.1.154 regression shape
33
+ # ------------------------------------------------------------
34
+
35
+
36
+ def test_claude_code_system_role_in_messages_is_accepted():
37
+ req = _req(
38
+ [
39
+ {"role": "user", "content": "hello"},
40
+ {"role": "system", "content": "injected system reminder"},
41
+ {"role": "assistant", "content": "hi"},
42
+ ]
43
+ )
44
+ assert [m.role for m in req.messages] == ["user", "assistant"]
45
+ assert req.system == "injected system reminder"
46
+
47
+
48
+ def test_system_role_with_block_list_content():
49
+ req = _req(
50
+ [
51
+ {"role": "user", "content": "q"},
52
+ {
53
+ "role": "system",
54
+ "content": [
55
+ {"type": "text", "text": "part one"},
56
+ {"type": "text", "text": "part two"},
57
+ ],
58
+ },
59
+ ]
60
+ )
61
+ assert req.system == "part one\npart two"
62
+
63
+
64
+ def test_system_role_appends_to_existing_string_system():
65
+ req = _req(
66
+ [
67
+ {"role": "user", "content": "q"},
68
+ {"role": "system", "content": "extra"},
69
+ ],
70
+ system="original",
71
+ )
72
+ assert req.system == "original\nextra"
73
+
74
+
75
+ def test_system_role_appends_to_existing_block_list_system():
76
+ req = _req(
77
+ [
78
+ {"role": "user", "content": "q"},
79
+ {"role": "system", "content": "extra"},
80
+ ],
81
+ system=[{"type": "text", "text": "original"}],
82
+ )
83
+ assert req.system == [
84
+ {"type": "text", "text": "original"},
85
+ {"type": "text", "text": "extra"},
86
+ ]
87
+
88
+
89
+ def test_multiple_system_messages_joined_in_order():
90
+ req = _req(
91
+ [
92
+ {"role": "system", "content": "first"},
93
+ {"role": "user", "content": "q"},
94
+ {"role": "system", "content": "second"},
95
+ ]
96
+ )
97
+ assert req.system == "first\nsecond"
98
+ assert [m.role for m in req.messages] == ["user"]
99
+
100
+
101
+ # ------------------------------------------------------------
102
+ # ctx / msg / unknown roles
103
+ # ------------------------------------------------------------
104
+
105
+
106
+ @pytest.mark.parametrize("role", ["ctx", "msg", "tool", "something_new"])
107
+ def test_unknown_role_coerced_to_user_preserving_position(role):
108
+ req = _req(
109
+ [
110
+ {"role": "user", "content": "q"},
111
+ {"role": role, "content": "context blob"},
112
+ {"role": "assistant", "content": "a"},
113
+ ]
114
+ )
115
+ assert [m.role for m in req.messages] == ["user", "user", "assistant"]
116
+ assert req.messages[1].content == "context blob"
117
+ assert req.system is None
118
+
119
+
120
+ def test_unknown_role_with_empty_content_dropped():
121
+ req = _req(
122
+ [
123
+ {"role": "user", "content": "q"},
124
+ {"role": "ctx", "content": ""},
125
+ {"role": "msg", "content": []},
126
+ ]
127
+ )
128
+ assert [m.role for m in req.messages] == ["user"]
129
+
130
+
131
+ def test_empty_system_role_message_dropped_without_touching_system():
132
+ req = _req(
133
+ [
134
+ {"role": "user", "content": "q"},
135
+ {"role": "system", "content": ""},
136
+ ]
137
+ )
138
+ assert req.system is None
139
+ assert [m.role for m in req.messages] == ["user"]
140
+
141
+
142
+ # ------------------------------------------------------------
143
+ # Spec-conformant requests are untouched
144
+ # ------------------------------------------------------------
145
+
146
+
147
+ def test_valid_request_passes_through_unchanged():
148
+ payload = {
149
+ "model": "m",
150
+ "max_tokens": 100,
151
+ "system": "sys",
152
+ "messages": [
153
+ {"role": "user", "content": "q"},
154
+ {"role": "assistant", "content": [{"type": "text", "text": "a"}]},
155
+ ],
156
+ }
157
+ req = AnthropicRequest.model_validate(payload)
158
+ assert req.system == "sys"
159
+ assert [m.role for m in req.messages] == ["user", "assistant"]
160
+ # normalize_message_roles returns the same object when nothing to do
161
+ assert normalize_message_roles(payload) is payload
162
+
163
+
164
+ def test_caller_payload_not_mutated():
165
+ payload = {
166
+ "model": "m",
167
+ "max_tokens": 100,
168
+ "messages": [
169
+ {"role": "user", "content": "q"},
170
+ {"role": "system", "content": "s"},
171
+ ],
172
+ }
173
+ AnthropicRequest.model_validate(payload)
174
+ assert len(payload["messages"]) == 2 # original untouched
175
+ assert "system" not in payload
176
+
177
+
178
+ def test_internal_construction_with_model_instances_still_works():
179
+ # convert.to_anthropic_request constructs with AnthropicMessage objects;
180
+ # the before-validator must pass them through.
181
+ req = AnthropicRequest(
182
+ model="m",
183
+ max_tokens=100,
184
+ messages=[AnthropicMessage(role="user", content="q")],
185
+ system="sys",
186
+ )
187
+ assert req.messages[0].role == "user"
188
+ assert req.system == "sys"
189
+
190
+
191
+ def test_message_model_itself_still_rejects_system_role():
192
+ # The strict wire model is unchanged — normalization happens at the
193
+ # request boundary, not by widening the role enum.
194
+ with pytest.raises(ValidationError):
195
+ AnthropicMessage.model_validate({"role": "system", "content": "x"})
196
+
197
+
198
+ def test_all_messages_invalid_roles_yields_empty_messages_but_valid_request():
199
+ req = _req([{"role": "system", "content": "only system"}])
200
+ assert req.messages == []
201
+ assert req.system == "only system"
File without changes
File without changes