coderouter-cli 2.5.3__tar.gz → 2.5.4__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 (204) hide show
  1. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/CHANGELOG.md +44 -0
  2. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/PKG-INFO +1 -1
  3. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/output_filters.py +148 -0
  4. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/pyproject.toml +1 -1
  5. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_output_filters.py +6 -1
  6. coderouter_cli-2.5.4/tests/test_repair_byte_fallback.py +175 -0
  7. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/.gitignore +0 -0
  8. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/LICENSE +0 -0
  9. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/README.en.md +0 -0
  10. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/README.md +0 -0
  11. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/__init__.py +0 -0
  12. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/__main__.py +0 -0
  13. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/adapters/__init__.py +0 -0
  14. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/adapters/anthropic_native.py +0 -0
  15. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/adapters/base.py +0 -0
  16. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/adapters/openai_compat.py +0 -0
  17. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/adapters/registry.py +0 -0
  18. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/cli.py +0 -0
  19. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/cli_stats.py +0 -0
  20. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/config/__init__.py +0 -0
  21. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/config/capability_registry.py +0 -0
  22. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/config/env_file.py +0 -0
  23. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/config/loader.py +0 -0
  24. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/config/schemas.py +0 -0
  25. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/cost.py +0 -0
  26. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/data/__init__.py +0 -0
  27. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/data/model-capabilities.yaml +0 -0
  28. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/doctor.py +0 -0
  29. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/doctor_apply.py +0 -0
  30. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/env_security.py +0 -0
  31. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/errors.py +0 -0
  32. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/gguf_introspect.py +0 -0
  33. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/guards/__init__.py +0 -0
  34. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/guards/_fingerprint.py +0 -0
  35. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/guards/backend_health.py +0 -0
  36. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/guards/context_budget.py +0 -0
  37. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/guards/continuous_probe.py +0 -0
  38. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/guards/drift_actions.py +0 -0
  39. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/guards/drift_detection.py +0 -0
  40. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/guards/memory_budget.py +0 -0
  41. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/guards/memory_pressure.py +0 -0
  42. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/guards/self_healing.py +0 -0
  43. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/guards/tool_loop.py +0 -0
  44. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/hardware.py +0 -0
  45. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/ingress/__init__.py +0 -0
  46. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/ingress/anthropic_routes.py +0 -0
  47. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/ingress/app.py +0 -0
  48. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/ingress/dashboard_routes.py +0 -0
  49. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/ingress/launcher_routes.py +0 -0
  50. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/ingress/metrics_routes.py +0 -0
  51. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/ingress/openai_routes.py +0 -0
  52. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/logging.py +0 -0
  53. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/metrics/__init__.py +0 -0
  54. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/metrics/collector.py +0 -0
  55. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/metrics/prometheus.py +0 -0
  56. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/plugins/__init__.py +0 -0
  57. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/plugins/base.py +0 -0
  58. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/plugins/loader.py +0 -0
  59. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/plugins/registry.py +0 -0
  60. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/routing/__init__.py +0 -0
  61. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/routing/adaptive.py +0 -0
  62. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/routing/auto_router.py +0 -0
  63. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/routing/budget.py +0 -0
  64. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/routing/capability.py +0 -0
  65. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/routing/fallback.py +0 -0
  66. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/state/__init__.py +0 -0
  67. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/state/audit_log.py +0 -0
  68. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/state/replay.py +0 -0
  69. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/state/request_log.py +0 -0
  70. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/state/store.py +0 -0
  71. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/state/suggest_rules.py +0 -0
  72. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/token_estimation.py +0 -0
  73. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/token_estimation_accurate.py +0 -0
  74. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/translation/__init__.py +0 -0
  75. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/translation/anthropic.py +0 -0
  76. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/translation/convert.py +0 -0
  77. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/coderouter/translation/tool_repair.py +0 -0
  78. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/README.md +0 -0
  79. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/assets/dashboard-demo.png +0 -0
  80. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/backends/gguf_dl.md +0 -0
  81. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/backends/hf-ollama-models.md +0 -0
  82. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/backends/install-backends.en.md +0 -0
  83. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/backends/install-backends.md +0 -0
  84. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/backends/launcher-quickstart.md +0 -0
  85. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/backends/launcher.md +0 -0
  86. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/backends/llamacpp-direct.en.md +0 -0
  87. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/backends/llamacpp-direct.md +0 -0
  88. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/backends/lmstudio-direct.en.md +0 -0
  89. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/backends/lmstudio-direct.md +0 -0
  90. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/backends/verify-ollama-0.23.1.md +0 -0
  91. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/concepts/architecture.md +0 -0
  92. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/concepts/context-budget.md +0 -0
  93. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/concepts/continuous-probing.md +0 -0
  94. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/concepts/drift-detection.md +0 -0
  95. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/concepts/partial-stitch.md +0 -0
  96. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/designs/v1.5-dashboard-mockup.html +0 -0
  97. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/designs/v1.6-auto-router-verification.md +0 -0
  98. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/designs/v1.6-auto-router.md +0 -0
  99. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/guides/free-tier-guide.en.md +0 -0
  100. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/guides/free-tier-guide.md +0 -0
  101. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/guides/security.en.md +0 -0
  102. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/guides/security.md +0 -0
  103. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/guides/troubleshooting.en.md +0 -0
  104. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/guides/troubleshooting.md +0 -0
  105. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/guides/usage-guide.en.md +0 -0
  106. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/guides/usage-guide.md +0 -0
  107. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/low-memory-integration.md +0 -0
  108. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/openrouter-roster/CHANGES.md +0 -0
  109. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/openrouter-roster/README.md +0 -0
  110. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/openrouter-roster/latest.json +0 -0
  111. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/retrospectives/v0.4.md +0 -0
  112. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/retrospectives/v0.5-verify.md +0 -0
  113. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/retrospectives/v0.5.md +0 -0
  114. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/retrospectives/v0.6.md +0 -0
  115. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/retrospectives/v0.7.md +0 -0
  116. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/retrospectives/v1.0-verify.md +0 -0
  117. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/retrospectives/v1.0.md +0 -0
  118. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/start/quickstart.en.md +0 -0
  119. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/start/quickstart.md +0 -0
  120. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/start/when-do-i-need-coderouter.en.md +0 -0
  121. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/docs/start/when-do-i-need-coderouter.md +0 -0
  122. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/examples/.env.example +0 -0
  123. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/examples/providers.auto-custom.yaml +0 -0
  124. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/examples/providers.auto.yaml +0 -0
  125. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/examples/providers.llama-cpp-vllm.yaml +0 -0
  126. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/examples/providers.note-2026.yaml +0 -0
  127. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/examples/providers.nvidia-nim.yaml +0 -0
  128. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/examples/providers.raspberrypi.yaml +0 -0
  129. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/examples/providers.v2-context-budget.yaml +0 -0
  130. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/examples/providers.yaml +0 -0
  131. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/scripts/demo_traffic.sh +0 -0
  132. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/scripts/openrouter_roster_diff.py +0 -0
  133. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/scripts/smoke_v2_2.sh +0 -0
  134. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/scripts/verify-providers.yaml +0 -0
  135. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/scripts/verify_ollama_0_23.py +0 -0
  136. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/scripts/verify_v0_5.sh +0 -0
  137. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/scripts/verify_v1_0.sh +0 -0
  138. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/__init__.py +0 -0
  139. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/conftest.py +0 -0
  140. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_adapter_anthropic.py +0 -0
  141. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_audit_log.py +0 -0
  142. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_auto_router.py +0 -0
  143. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_backend_health.py +0 -0
  144. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_budget.py +0 -0
  145. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_capability.py +0 -0
  146. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_capability_degraded_payload.py +0 -0
  147. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_capability_registry.py +0 -0
  148. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_capability_registry_cache_control.py +0 -0
  149. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_claude_code_suitability.py +0 -0
  150. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_cli.py +0 -0
  151. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_cli_stats.py +0 -0
  152. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_config.py +0 -0
  153. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_context_budget.py +0 -0
  154. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_continuous_probe.py +0 -0
  155. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_dashboard_endpoint.py +0 -0
  156. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_doctor.py +0 -0
  157. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_doctor_apply.py +0 -0
  158. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_doctor_cache_probe.py +0 -0
  159. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_drift_actions.py +0 -0
  160. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_drift_detection.py +0 -0
  161. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_drift_detection_integration.py +0 -0
  162. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_env_file.py +0 -0
  163. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_env_security.py +0 -0
  164. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_errors.py +0 -0
  165. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_examples_yaml.py +0 -0
  166. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_fallback.py +0 -0
  167. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_fallback_anthropic.py +0 -0
  168. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_fallback_cache_control.py +0 -0
  169. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_fallback_cache_observed.py +0 -0
  170. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_fallback_misconfig_warn.py +0 -0
  171. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_fallback_paid_gate.py +0 -0
  172. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_fallback_thinking.py +0 -0
  173. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_gguf_introspect.py +0 -0
  174. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_guards_tool_loop.py +0 -0
  175. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_hardware.py +0 -0
  176. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_ingress_anthropic.py +0 -0
  177. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_ingress_profile.py +0 -0
  178. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_memory_budget.py +0 -0
  179. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_memory_pressure.py +0 -0
  180. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_metrics_cache.py +0 -0
  181. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_metrics_collector.py +0 -0
  182. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_metrics_cost.py +0 -0
  183. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_metrics_endpoint.py +0 -0
  184. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_metrics_jsonl.py +0 -0
  185. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_metrics_prometheus.py +0 -0
  186. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_metrics_prometheus_cache.py +0 -0
  187. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_openai_compat.py +0 -0
  188. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_openrouter_roster_diff.py +0 -0
  189. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_output_filters_adapters.py +0 -0
  190. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_partial_stitch.py +0 -0
  191. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_plugins_integration.py +0 -0
  192. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_plugins_loader.py +0 -0
  193. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_plugins_registry.py +0 -0
  194. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_reasoning_strip.py +0 -0
  195. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_request_log.py +0 -0
  196. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_routing_adaptive.py +0 -0
  197. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_self_healing.py +0 -0
  198. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_setup_sh.py +0 -0
  199. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_state_store.py +0 -0
  200. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_token_estimation.py +0 -0
  201. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_token_estimation_accurate.py +0 -0
  202. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_tool_repair.py +0 -0
  203. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_translation_anthropic.py +0 -0
  204. {coderouter_cli-2.5.3 → coderouter_cli-2.5.4}/tests/test_translation_reverse.py +0 -0
@@ -6,6 +6,50 @@ versioning follows [SemVer](https://semver.org/).
6
6
 
7
7
  ---
8
8
 
9
+ ## [v2.5.4] — 2026-06-05 (Gemma `<0xNN>` byte-fallback repair filter)
10
+
11
+ Patch release: a new opt-in output filter that repairs Japanese (and other
12
+ multi-byte) text corrupted by the Ollama 0.30 / llama.cpp detokenizer change.
13
+
14
+ ### Added
15
+
16
+ - **`repair_byte_fallback` output filter.** Ollama 0.30 unified its GGUF
17
+ runtime onto llama.cpp (`ollama/ollama#16031`). For gemma4 the detokenizer
18
+ changed, and multi-byte characters it cannot assemble now leak as
19
+ llama.cpp's byte-fallback notation `<0xNN>`:
20
+ - full-width space ` ` → `<0xE3><0x80><0x80>`
21
+ - rare kanji `躙` → `<0xE8><0xBA><0x99>`
22
+
23
+ These corrupt Japanese prose **and** tool-call JSON argument strings routed
24
+ through CodeRouter (a stray `<0xNN>` inside an argument breaks JSON parsing).
25
+ The new filter reassembles consecutive `<0xNN>` runs back into UTF-8.
26
+
27
+ - **Opt-in** per provider: `output_filters: [repair_byte_fallback]`
28
+ (disabled by default). Place it **before** `tool_repair` / tool-call XML
29
+ strip so byte-fallback inside tool-call arguments is restored before JSON
30
+ extraction.
31
+ - **Streaming-safe**: handles chunk boundaries inside a single token
32
+ (`<0x` | `E3>`) and inside a multi-byte run (`<0xE3>` | `<0x80><0x80>`).
33
+ A pending byte run is only flushed once it has definitively ended.
34
+ - **Lossless**: bytes that cannot form valid UTF-8 are re-emitted verbatim
35
+ as `<0xNN>`, so output is never made worse than llama.cpp already left it.
36
+ - **No new runtime dependencies** (stdlib `re` only).
37
+
38
+ Verified with 22 new unit tests (61 filter tests total pass on py3.12),
39
+ ruff clean, a 20,000-iteration streaming chunk-boundary fuzz (0 mismatches),
40
+ and a Japanese/emoji round-trip.
41
+
42
+ Ref: <https://note.com/akb428/n/n737e786f32ce>
43
+
44
+ ### Known mitigations (documented)
45
+
46
+ - For gemma4, staying on / downgrading to Ollama 0.24 is the most reliable
47
+ fix (the root cause is the 0.30 llama.cpp detokenizer swap).
48
+ - A larger `num_ctx` increases the leak rate; consider not auto-raising it for
49
+ gemma4 on Ollama 0.30.
50
+
51
+ ---
52
+
9
53
  ## [v2.5.2] — 2026-05-22 (Backend-aware Launcher suggestions + backend install guide)
10
54
 
11
55
  Patch release: a Launcher bug fix and documentation improvements.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coderouter-cli
3
- Version: 2.5.3
3
+ Version: 2.5.4
4
4
  Summary: Local-first, free-first, fallback-built-in LLM router. Claude Code / OpenAI compatible.
5
5
  Project-URL: Homepage, https://github.com/zephel01/CodeRouter
6
6
  Project-URL: Repository, https://github.com/zephel01/CodeRouter
@@ -43,6 +43,7 @@ Reference: plan.md §10.2 "出力クリーニング" / docs/retrospectives/v0.7.
43
43
 
44
44
  from __future__ import annotations
45
45
 
46
+ import re
46
47
  from typing import Protocol
47
48
 
48
49
  __all__ = [
@@ -50,6 +51,7 @@ __all__ = [
50
51
  "KNOWN_FILTERS",
51
52
  "OutputFilter",
52
53
  "OutputFilterChain",
54
+ "RepairByteFallbackFilter",
53
55
  "StripStopMarkersFilter",
54
56
  "StripThinkingFilter",
55
57
  "StripToolCallXmlFilter",
@@ -382,6 +384,151 @@ class StripToolCallXmlFilter:
382
384
  return "".join(out_parts)
383
385
 
384
386
 
387
+ # ---------------------------------------------------------------------------
388
+ # repair_byte_fallback (v2.x)
389
+ # ---------------------------------------------------------------------------
390
+
391
+
392
+ # A complete byte-fallback token: ``<0x`` + exactly two hex digits + ``>``.
393
+ _BYTE_RE = re.compile(r"<0x([0-9A-Fa-f]{2})>")
394
+
395
+ # The whole remaining buffer is a *proper prefix* of some ``<0xHH>`` token,
396
+ # i.e. it could still complete (and continue a run) on the next feed:
397
+ # ``<`` / ``<0`` / ``<0x`` / ``<0xH`` / ``<0xHH`` (closing ``>`` not yet seen).
398
+ _PREFIX_RE = re.compile(r"<(0(x[0-9A-Fa-f]{0,2})?)?")
399
+
400
+ _BYTE_TOKEN_START = "<0x"
401
+
402
+
403
+ def _decode_byte_run(buf: bytes) -> str:
404
+ """Decode a run of fallback bytes to text, losslessly.
405
+
406
+ Decodes the maximal valid UTF-8 prefix; any byte that cannot start or
407
+ continue a valid sequence is re-emitted as its ``<0xHH>`` token and
408
+ decoding resumes after it. So ``b"\\xe3\\x80\\x80"`` -> ``" "`` while a
409
+ stray ``b"\\xff"`` round-trips to ``"<0xFF>"`` — we never make the stream
410
+ worse than llama.cpp already did.
411
+ """
412
+ parts: list[str] = []
413
+ i = 0
414
+ n = len(buf)
415
+ while i < n:
416
+ try:
417
+ parts.append(buf[i:].decode("utf-8"))
418
+ break
419
+ except UnicodeDecodeError as exc:
420
+ good_end = i + exc.start
421
+ if good_end > i:
422
+ parts.append(buf[i:good_end].decode("utf-8"))
423
+ parts.append(f"<0x{buf[good_end]:02X}>")
424
+ i = good_end + 1
425
+ return "".join(parts)
426
+
427
+
428
+ class RepairByteFallbackFilter:
429
+ """Reassemble llama.cpp ``<0xNN>`` byte-fallback leaks into UTF-8 text.
430
+
431
+ Ollama 0.30 unified its GGUF runtime onto llama.cpp
432
+ (``ollama/ollama#16031``). For gemma4 the detokenizer changed, and
433
+ multi-byte characters it cannot assemble now leak as llama.cpp's
434
+ byte-fallback notation::
435
+
436
+ full-width space `` `` -> ``<0xE3><0x80><0x80>``
437
+ rare kanji ``躙`` -> ``<0xE8><0xBA><0x99>``
438
+
439
+ These corrupt Japanese prose AND tool-call JSON arguments (a stray
440
+ ``<0xNN>`` inside an argument string breaks JSON parsing). This filter
441
+ reassembles runs of consecutive ``<0xNN>`` tokens back into UTF-8.
442
+
443
+ Stateful across ``feed`` calls so a token split across SSE deltas
444
+ (``<0x`` | ``E3>``) and a multi-byte run split across deltas
445
+ (``<0xE3>`` | ``<0x80><0x80>``) both reassemble correctly. A pending byte
446
+ run is only flushed once we are certain it has ended (confirmed normal
447
+ text follows, or ``eof``) — never at a bare chunk boundary, where the run
448
+ might continue in the next delta. Bytes that cannot form valid UTF-8 are
449
+ re-emitted verbatim (lossless).
450
+
451
+ ``modified`` flips True the first time any ``<0xNN>`` token is consumed —
452
+ the adapter uses it to gate the log-once "output-filter-applied" line.
453
+
454
+ Ordering note: place this BEFORE ``tool_repair`` / the tool-call XML
455
+ strip so byte-fallback inside tool-call argument strings is restored
456
+ before JSON extraction.
457
+ """
458
+
459
+ name = "repair_byte_fallback"
460
+
461
+ def __init__(self) -> None:
462
+ """Initialize per-request buffer, pending byte run and state."""
463
+ self.modified: bool = False
464
+ self._buffer: str = ""
465
+ self._pending = bytearray()
466
+
467
+ def _flush_pending(self, out: list[str]) -> None:
468
+ """Decode and emit the accumulated byte run, then clear it."""
469
+ if self._pending:
470
+ out.append(_decode_byte_run(bytes(self._pending)))
471
+ self._pending.clear()
472
+
473
+ def feed(self, text: str, *, eof: bool = False) -> str:
474
+ """Consume ``text``; return the portion safe to emit now."""
475
+ self._buffer += text
476
+ out: list[str] = []
477
+
478
+ while self._buffer:
479
+ m = _BYTE_RE.match(self._buffer)
480
+ if m is not None:
481
+ # Complete byte token at position 0 — extend the run.
482
+ self._pending.append(int(m.group(1), 16))
483
+ self._buffer = self._buffer[m.end() :]
484
+ self.modified = True
485
+ continue
486
+
487
+ idx = self._buffer.find(_BYTE_TOKEN_START)
488
+ if idx == -1:
489
+ # No complete/started token in the buffer. Hold a trailing
490
+ # partial of ``<0x`` (it may complete — and CONTINUE the run —
491
+ # on the next feed); treat anything before it as confirmed
492
+ # normal text that ends the run.
493
+ hold = (
494
+ 0 if eof else _max_suffix_overlap(self._buffer, _BYTE_TOKEN_START)
495
+ )
496
+ safe = self._buffer[:-hold] if hold else self._buffer
497
+ if safe:
498
+ self._flush_pending(out)
499
+ out.append(safe)
500
+ self._buffer = self._buffer[len(safe) :]
501
+ # else: whole buffer is a token-start prefix; keep pending
502
+ # (the run might continue) and wait for more input.
503
+ break
504
+
505
+ if idx > 0:
506
+ # Normal text precedes the next token start — run ended.
507
+ self._flush_pending(out)
508
+ out.append(self._buffer[:idx])
509
+ self._buffer = self._buffer[idx:]
510
+ continue
511
+
512
+ # idx == 0: buffer starts with ``<0x`` but is not a complete token.
513
+ if not eof and _PREFIX_RE.fullmatch(self._buffer):
514
+ # Could still complete next feed — hold token AND pending run.
515
+ break
516
+
517
+ # Malformed ``<0x..`` (non-hex, or stuck at eof). The ``<`` is
518
+ # ordinary text; the run (if any) has ended.
519
+ self._flush_pending(out)
520
+ out.append("<")
521
+ self._buffer = self._buffer[1:]
522
+
523
+ if eof:
524
+ self._flush_pending(out)
525
+ if self._buffer:
526
+ out.append(self._buffer)
527
+ self._buffer = ""
528
+
529
+ return "".join(out)
530
+
531
+
385
532
  # ---------------------------------------------------------------------------
386
533
  # Registry + chain
387
534
  # ---------------------------------------------------------------------------
@@ -391,6 +538,7 @@ KNOWN_FILTERS: dict[str, type[OutputFilter]] = {
391
538
  StripThinkingFilter.name: StripThinkingFilter,
392
539
  StripStopMarkersFilter.name: StripStopMarkersFilter,
393
540
  StripToolCallXmlFilter.name: StripToolCallXmlFilter,
541
+ RepairByteFallbackFilter.name: RepairByteFallbackFilter,
394
542
  }
395
543
  """Registry of string-name → filter class.
396
544
 
@@ -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.3"
14
+ version = "2.5.4"
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"
@@ -61,7 +61,12 @@ def test_validate_rejects_when_mixing_known_and_unknown() -> None:
61
61
 
62
62
  def test_registry_matches_expected_set() -> None:
63
63
  """Regression: if a filter is added, this test reminds us to doc it."""
64
- assert set(KNOWN_FILTERS) == {"strip_thinking", "strip_stop_markers", "strip_tool_call_xml"}
64
+ assert set(KNOWN_FILTERS) == {
65
+ "strip_thinking",
66
+ "strip_stop_markers",
67
+ "strip_tool_call_xml",
68
+ "repair_byte_fallback",
69
+ }
65
70
 
66
71
 
67
72
  # ======================================================================
@@ -0,0 +1,175 @@
1
+ """Unit tests for ``repair_byte_fallback`` — the Gemma/llama.cpp
2
+ ``<0xNN>`` byte-fallback repair filter.
3
+
4
+ Mirrors the streaming-boundary style of ``test_output_filters.py``: we
5
+ exercise (1) whole-string repair, (2) chunk boundaries inside a single
6
+ token, (3) chunk boundaries inside a multi-byte run, (4) passthrough of
7
+ ordinary text, and (5) lossless handling of malformed/incomplete tokens.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import pytest
13
+
14
+ from coderouter.output_filters import (
15
+ KNOWN_FILTERS,
16
+ RepairByteFallbackFilter,
17
+ apply_output_filters,
18
+ )
19
+
20
+
21
+ def _repair(text: str) -> tuple[str, bool]:
22
+ """One-shot repair, returning ``(out, modified)``."""
23
+ f = RepairByteFallbackFilter()
24
+ out = f.feed(text, eof=True)
25
+ return out, f.modified
26
+
27
+
28
+ def _stream(chunks: list[str]) -> str:
29
+ """Feed chunks one at a time, flush at eof, return concatenated output."""
30
+ f = RepairByteFallbackFilter()
31
+ out = []
32
+ for c in chunks:
33
+ out.append(f.feed(c))
34
+ out.append(f.feed("", eof=True))
35
+ return "".join(out)
36
+
37
+
38
+ # ----------------------------------------------------------------------
39
+ # registry wiring
40
+ # ----------------------------------------------------------------------
41
+
42
+
43
+ def test_registered_in_known_filters() -> None:
44
+ assert KNOWN_FILTERS["repair_byte_fallback"] is RepairByteFallbackFilter
45
+
46
+
47
+ def test_usable_through_chain() -> None:
48
+ out, applied = apply_output_filters(
49
+ ["repair_byte_fallback"], "残酷な<0xE3><0x80><0x80>蹂"
50
+ )
51
+ assert out == "残酷な 蹂"
52
+ assert applied == ["repair_byte_fallback"]
53
+
54
+
55
+ # ----------------------------------------------------------------------
56
+ # 1. whole-string repair
57
+ # ----------------------------------------------------------------------
58
+
59
+
60
+ def test_full_width_space_single_shot() -> None:
61
+ out, mod = _repair("残酷な<0xE3><0x80><0x80>蹂")
62
+ assert out == "残酷な 蹂"
63
+ assert mod is True
64
+
65
+
66
+ def test_rare_kanji_single_shot() -> None:
67
+ # 躙 = U+8E99 = E8 BA 99
68
+ out, mod = _repair("残酷な蹂<0xE8><0xBA><0x99>だった")
69
+ assert out == "残酷な蹂躙だった"
70
+ assert mod is True
71
+
72
+
73
+ def test_mixed_multiple_runs() -> None:
74
+ out, _ = _repair("A<0xE3><0x80><0x80>B<0xE8><0xBA><0x99>C")
75
+ assert out == "A B躙C"
76
+
77
+
78
+ # ----------------------------------------------------------------------
79
+ # 2. chunk boundary inside a single token
80
+ # ----------------------------------------------------------------------
81
+
82
+
83
+ def test_split_inside_token() -> None:
84
+ assert _stream(["残酷な<0x", "E3><0x80><0x80>蹂"]) == "残酷な 蹂"
85
+
86
+
87
+ def test_split_every_char() -> None:
88
+ s = "x<0xE3><0x80><0x80>y"
89
+ assert _stream(list(s)) == "x y"
90
+
91
+
92
+ # ----------------------------------------------------------------------
93
+ # 3. chunk boundary inside a multi-byte run
94
+ # ----------------------------------------------------------------------
95
+
96
+
97
+ def test_split_between_run_tokens() -> None:
98
+ assert _stream(["<0xE3>", "<0x80><0x80>"]) == " "
99
+
100
+
101
+ def test_split_run_three_ways() -> None:
102
+ assert _stream(["pre<0xE8>", "<0xBA>", "<0x99>post"]) == "pre躙post"
103
+
104
+
105
+ # ----------------------------------------------------------------------
106
+ # 4. ordinary text passthrough (must not corrupt existing content)
107
+ # ----------------------------------------------------------------------
108
+
109
+
110
+ def test_plain_text_untouched() -> None:
111
+ out, mod = _repair("def foo(): return 1 # 普通の日本語")
112
+ assert out == "def foo(): return 1 # 普通の日本語"
113
+ assert mod is False
114
+
115
+
116
+ def test_non_byte_angle_tags_untouched() -> None:
117
+ s = "<think>hmm</think><tool_call>{}</tool_call>"
118
+ out, mod = _repair(s)
119
+ assert out == s
120
+ assert mod is False
121
+
122
+
123
+ def test_literal_less_than_zero_passes() -> None:
124
+ out, _ = _repair("if x <0 and y >1: pass")
125
+ assert out == "if x <0 and y >1: pass"
126
+
127
+
128
+ # ----------------------------------------------------------------------
129
+ # 5. lossless handling of malformed / incomplete tokens
130
+ # ----------------------------------------------------------------------
131
+
132
+
133
+ def test_malformed_non_hex_kept_literal() -> None:
134
+ out, mod = _repair("a<0xZZ>b")
135
+ assert out == "a<0xZZ>b"
136
+ assert mod is False
137
+
138
+
139
+ def test_incomplete_token_at_eof_kept_literal() -> None:
140
+ out, _ = _repair("tail<0xE3")
141
+ assert out == "tail<0xE3"
142
+
143
+
144
+ def test_lone_invalid_byte_round_trips() -> None:
145
+ out, mod = _repair("x<0xFF>y")
146
+ assert out == "x<0xFF>y"
147
+ assert mod is True # a token WAS consumed, even though the byte was undecodable
148
+
149
+
150
+ def test_valid_prefix_then_invalid_byte() -> None:
151
+ out, _ = _repair("<0xE3><0x80><0xFF>")
152
+ assert out == "<0xE3><0x80><0xFF>"
153
+
154
+
155
+ # ----------------------------------------------------------------------
156
+ # invariants
157
+ # ----------------------------------------------------------------------
158
+
159
+
160
+ @pytest.mark.parametrize(
161
+ "s",
162
+ ["", "hello world", "マルチバイト日本語テキスト", "<<<>>>", "a < b and c > d"],
163
+ )
164
+ def test_no_byte_tokens_means_byte_identical(s: str) -> None:
165
+ out, mod = _repair(s)
166
+ assert out == s
167
+ assert mod is False
168
+
169
+
170
+ def test_modified_flag_semantics() -> None:
171
+ f = RepairByteFallbackFilter()
172
+ f.feed("plain", eof=False)
173
+ assert f.modified is False
174
+ f.feed("<0xE3><0x80><0x80>", eof=True)
175
+ assert f.modified is True
File without changes
File without changes