coderouter-cli 2.5.5__tar.gz → 2.6.1__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 (215) hide show
  1. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/.gitignore +1 -0
  2. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/CHANGELOG.md +116 -0
  3. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/PKG-INFO +32 -1
  4. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/README.en.md +31 -0
  5. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/README.md +31 -0
  6. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/config/schemas.py +31 -0
  7. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/cost.py +32 -0
  8. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/ingress/dashboard_routes.py +65 -0
  9. coderouter_cli-2.6.1/coderouter/language_tax.py +244 -0
  10. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/logging.py +8 -0
  11. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/metrics/collector.py +105 -0
  12. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/routing/auto_router.py +7 -0
  13. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/routing/fallback.py +30 -0
  14. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/token_estimation.py +47 -0
  15. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/README.md +38 -1
  16. coderouter_cli-2.6.1/docs/backends/claude-code-llamacpp-vllm.md +172 -0
  17. coderouter_cli-2.6.1/docs/guides/language-tax.en.md +138 -0
  18. coderouter_cli-2.6.1/docs/guides/language-tax.md +139 -0
  19. coderouter_cli-2.6.1/examples/README.md +68 -0
  20. coderouter_cli-2.6.1/examples/providers-multiagent.yaml +188 -0
  21. coderouter_cli-2.5.5/examples/providers.v2-context-budget.yaml → coderouter_cli-2.6.1/examples/providers.context-budget-test.yaml +2 -1
  22. coderouter_cli-2.5.5/examples/providers.llama-cpp-vllm.yaml → coderouter_cli-2.6.1/examples/providers.llamacpp-vllm.yaml +19 -9
  23. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/examples/providers.nvidia-nim.yaml +1 -0
  24. coderouter_cli-2.5.5/examples/providers.auto-custom.yaml → coderouter_cli-2.6.1/examples/providers.ollama-auto-custom.yaml +6 -5
  25. coderouter_cli-2.5.5/examples/providers.auto.yaml → coderouter_cli-2.6.1/examples/providers.ollama-auto.yaml +2 -1
  26. coderouter_cli-2.5.5/examples/providers.note-2026.yaml → coderouter_cli-2.6.1/examples/providers.ollama-free-chain.yaml +3 -2
  27. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/examples/providers.raspberrypi.yaml +1 -0
  28. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/examples/providers.yaml +1 -0
  29. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/pyproject.toml +1 -1
  30. coderouter_cli-2.6.1/tests/test_auto_router_cjk.py +99 -0
  31. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_dashboard_endpoint.py +6 -0
  32. coderouter_cli-2.6.1/tests/test_language_tax.py +147 -0
  33. coderouter_cli-2.6.1/tests/test_language_tax_integration.py +215 -0
  34. coderouter_cli-2.6.1/tests/test_tokens_saved_metric.py +87 -0
  35. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/LICENSE +0 -0
  36. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/__init__.py +0 -0
  37. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/__main__.py +0 -0
  38. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/adapters/__init__.py +0 -0
  39. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/adapters/anthropic_native.py +0 -0
  40. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/adapters/base.py +0 -0
  41. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/adapters/openai_compat.py +0 -0
  42. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/adapters/registry.py +0 -0
  43. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/cli.py +0 -0
  44. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/cli_stats.py +0 -0
  45. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/config/__init__.py +0 -0
  46. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/config/capability_registry.py +0 -0
  47. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/config/env_file.py +0 -0
  48. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/config/loader.py +0 -0
  49. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/data/__init__.py +0 -0
  50. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/data/model-capabilities.yaml +0 -0
  51. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/doctor.py +0 -0
  52. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/doctor_apply.py +0 -0
  53. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/env_security.py +0 -0
  54. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/errors.py +0 -0
  55. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/gguf_introspect.py +0 -0
  56. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/guards/__init__.py +0 -0
  57. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/guards/_fingerprint.py +0 -0
  58. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/guards/backend_health.py +0 -0
  59. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/guards/context_budget.py +0 -0
  60. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/guards/continuous_probe.py +0 -0
  61. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/guards/drift_actions.py +0 -0
  62. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/guards/drift_detection.py +0 -0
  63. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/guards/memory_budget.py +0 -0
  64. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/guards/memory_pressure.py +0 -0
  65. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/guards/self_healing.py +0 -0
  66. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/guards/tool_loop.py +0 -0
  67. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/hardware.py +0 -0
  68. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/ingress/__init__.py +0 -0
  69. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/ingress/anthropic_routes.py +0 -0
  70. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/ingress/app.py +0 -0
  71. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/ingress/launcher_routes.py +0 -0
  72. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/ingress/metrics_routes.py +0 -0
  73. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/ingress/openai_routes.py +0 -0
  74. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/metrics/__init__.py +0 -0
  75. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/metrics/prometheus.py +0 -0
  76. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/output_filters.py +0 -0
  77. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/plugins/__init__.py +0 -0
  78. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/plugins/base.py +0 -0
  79. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/plugins/loader.py +0 -0
  80. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/plugins/registry.py +0 -0
  81. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/routing/__init__.py +0 -0
  82. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/routing/adaptive.py +0 -0
  83. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/routing/budget.py +0 -0
  84. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/routing/capability.py +0 -0
  85. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/state/__init__.py +0 -0
  86. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/state/audit_log.py +0 -0
  87. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/state/replay.py +0 -0
  88. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/state/request_log.py +0 -0
  89. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/state/store.py +0 -0
  90. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/state/suggest_rules.py +0 -0
  91. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/token_estimation_accurate.py +0 -0
  92. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/translation/__init__.py +0 -0
  93. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/translation/anthropic.py +0 -0
  94. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/translation/convert.py +0 -0
  95. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/coderouter/translation/tool_repair.py +0 -0
  96. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/assets/dashboard-demo.png +0 -0
  97. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/backends/gguf_dl.md +0 -0
  98. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/backends/hf-ollama-models.md +0 -0
  99. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/backends/install-backends.en.md +0 -0
  100. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/backends/install-backends.md +0 -0
  101. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/backends/launcher-quickstart.md +0 -0
  102. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/backends/launcher.md +0 -0
  103. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/backends/llamacpp-direct.en.md +0 -0
  104. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/backends/llamacpp-direct.md +0 -0
  105. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/backends/lmstudio-direct.en.md +0 -0
  106. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/backends/lmstudio-direct.md +0 -0
  107. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/backends/verify-ollama-0.23.1.md +0 -0
  108. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/concepts/architecture.md +0 -0
  109. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/concepts/context-budget.md +0 -0
  110. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/concepts/continuous-probing.md +0 -0
  111. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/concepts/drift-detection.md +0 -0
  112. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/concepts/partial-stitch.md +0 -0
  113. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/designs/v1.5-dashboard-mockup.html +0 -0
  114. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/designs/v1.6-auto-router-verification.md +0 -0
  115. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/designs/v1.6-auto-router.md +0 -0
  116. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/guides/free-tier-guide.en.md +0 -0
  117. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/guides/free-tier-guide.md +0 -0
  118. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/guides/security.en.md +0 -0
  119. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/guides/security.md +0 -0
  120. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/guides/troubleshooting.en.md +0 -0
  121. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/guides/troubleshooting.md +0 -0
  122. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/guides/usage-guide.en.md +0 -0
  123. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/guides/usage-guide.md +0 -0
  124. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/low-memory-integration.md +0 -0
  125. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/openrouter-roster/CHANGES.md +0 -0
  126. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/openrouter-roster/README.md +0 -0
  127. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/openrouter-roster/latest.json +0 -0
  128. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/retrospectives/v0.4.md +0 -0
  129. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/retrospectives/v0.5-verify.md +0 -0
  130. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/retrospectives/v0.5.md +0 -0
  131. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/retrospectives/v0.6.md +0 -0
  132. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/retrospectives/v0.7.md +0 -0
  133. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/retrospectives/v1.0-verify.md +0 -0
  134. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/retrospectives/v1.0.md +0 -0
  135. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/start/quickstart.en.md +0 -0
  136. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/start/quickstart.md +0 -0
  137. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/start/when-do-i-need-coderouter.en.md +0 -0
  138. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/docs/start/when-do-i-need-coderouter.md +0 -0
  139. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/examples/.env.example +0 -0
  140. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/scripts/demo_traffic.sh +0 -0
  141. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/scripts/openrouter_roster_diff.py +0 -0
  142. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/scripts/smoke_v2_2.sh +0 -0
  143. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/scripts/verify-providers.yaml +0 -0
  144. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/scripts/verify_ollama_0_23.py +0 -0
  145. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/scripts/verify_v0_5.sh +0 -0
  146. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/scripts/verify_v1_0.sh +0 -0
  147. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/__init__.py +0 -0
  148. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/conftest.py +0 -0
  149. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_adapter_anthropic.py +0 -0
  150. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_audit_log.py +0 -0
  151. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_auto_router.py +0 -0
  152. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_backend_health.py +0 -0
  153. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_budget.py +0 -0
  154. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_capability.py +0 -0
  155. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_capability_degraded_payload.py +0 -0
  156. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_capability_registry.py +0 -0
  157. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_capability_registry_cache_control.py +0 -0
  158. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_claude_code_suitability.py +0 -0
  159. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_cli.py +0 -0
  160. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_cli_stats.py +0 -0
  161. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_config.py +0 -0
  162. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_context_budget.py +0 -0
  163. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_continuous_probe.py +0 -0
  164. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_doctor.py +0 -0
  165. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_doctor_apply.py +0 -0
  166. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_doctor_cache_probe.py +0 -0
  167. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_drift_actions.py +0 -0
  168. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_drift_detection.py +0 -0
  169. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_drift_detection_integration.py +0 -0
  170. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_env_file.py +0 -0
  171. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_env_security.py +0 -0
  172. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_errors.py +0 -0
  173. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_examples_yaml.py +0 -0
  174. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_fallback.py +0 -0
  175. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_fallback_anthropic.py +0 -0
  176. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_fallback_cache_control.py +0 -0
  177. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_fallback_cache_observed.py +0 -0
  178. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_fallback_misconfig_warn.py +0 -0
  179. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_fallback_paid_gate.py +0 -0
  180. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_fallback_thinking.py +0 -0
  181. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_gguf_introspect.py +0 -0
  182. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_guards_tool_loop.py +0 -0
  183. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_hardware.py +0 -0
  184. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_ingress_anthropic.py +0 -0
  185. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_ingress_profile.py +0 -0
  186. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_memory_budget.py +0 -0
  187. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_memory_pressure.py +0 -0
  188. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_metrics_cache.py +0 -0
  189. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_metrics_collector.py +0 -0
  190. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_metrics_cost.py +0 -0
  191. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_metrics_endpoint.py +0 -0
  192. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_metrics_jsonl.py +0 -0
  193. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_metrics_prometheus.py +0 -0
  194. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_metrics_prometheus_cache.py +0 -0
  195. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_openai_compat.py +0 -0
  196. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_openrouter_roster_diff.py +0 -0
  197. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_output_filters.py +0 -0
  198. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_output_filters_adapters.py +0 -0
  199. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_partial_stitch.py +0 -0
  200. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_plugins_integration.py +0 -0
  201. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_plugins_loader.py +0 -0
  202. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_plugins_registry.py +0 -0
  203. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_reasoning_strip.py +0 -0
  204. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_repair_byte_fallback.py +0 -0
  205. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_request_log.py +0 -0
  206. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_role_normalization.py +0 -0
  207. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_routing_adaptive.py +0 -0
  208. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_self_healing.py +0 -0
  209. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_setup_sh.py +0 -0
  210. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_state_store.py +0 -0
  211. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_token_estimation.py +0 -0
  212. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_token_estimation_accurate.py +0 -0
  213. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_tool_repair.py +0 -0
  214. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_translation_anthropic.py +0 -0
  215. {coderouter_cli-2.5.5 → coderouter_cli-2.6.1}/tests/test_translation_reverse.py +0 -0
@@ -36,6 +36,7 @@ ENV/
36
36
  # _OUTPUTS
37
37
  # ============================================================
38
38
  _OUTPUTS/
39
+ _run/
39
40
 
40
41
  # uv
41
42
  # Note: uv.lock SHOULD be committed (per plan.md §5.4)
@@ -6,6 +6,122 @@ versioning follows [SemVer](https://semver.org/).
6
6
 
7
7
  ---
8
8
 
9
+ ## [v2.6.1] — 2026-06-28 (Token-savings accounting)
10
+
11
+ Patch release: surfaces **token-savings accounting** in the metrics layer
12
+ and dashboard. The figure is owned by core, so it appears even when no
13
+ plugin is installed (trim savings from the context-budget guard), and the
14
+ optional `compress` plugin adds to the same total via a neutral
15
+ `tokens-saved` log event. **No new core dependency**, **no behavioral
16
+ change** to existing paths, and **fully backward compatible** — existing
17
+ counters and events are untouched; the new buckets are additive.
18
+
19
+ ### Added
20
+
21
+ - **Token-savings buckets in `MetricsCollector`**
22
+ (`coderouter/metrics/collector.py`). `tokens_saved_total` plus a
23
+ per-mechanism breakdown (`tokens_saved_by_mechanism`). Two feeds
24
+ aggregate under one schema: `trim` (derived from the existing
25
+ `context-budget-trimmed` event's before/after token estimate) and
26
+ `compress` (the neutral `tokens-saved` event emitted by the compress
27
+ plugin — no core import). Wired through `snapshot()`, `save_state()`,
28
+ `load_state()`, and `reset()`.
29
+ - **Dashboard Token Savings tiles**
30
+ (`coderouter/ingress/dashboard_routes.py`). Three tiles — trim /
31
+ compress / total — in the "Cost & Language Tax" panel, zero-filled so a
32
+ fresh or local-only deployment renders cleanly.
33
+
34
+ ### Docs / examples
35
+
36
+ - Reorganized provider samples under `examples/` with a category index.
37
+ - Added the language-tax guide (JA/EN) and a Claude Code +
38
+ llama.cpp/vllm backend guide.
39
+
40
+ ### Tests
41
+
42
+ - `tests/test_tokens_saved_metric.py` — trim/compress accounting,
43
+ negative-delta clamping, combined aggregate, persistence round-trip,
44
+ and reset. Full suite: **1263 passed, 1 skipped**.
45
+
46
+ ### Companion
47
+
48
+ - `coderouter-plugin-compress` emits the `tokens-saved` event (plugin
49
+ branch `feat/tokens-saved-emit`, v0.2.0). CodeRouter core works
50
+ standalone — trim savings show without the plugin.
51
+
52
+ ---
53
+
54
+ ## [v2.6.0] — 2026-06-20 (Language Tax: measure, route, visualize)
55
+
56
+ Minor release: makes the CJK **"language tax"** — cloud tokenizers bill
57
+ Japanese/Chinese/Korean text ~1.2–1.5× more tokens per character than
58
+ English, while local models are unaffected — measurable, routable, and
59
+ visible. Built entirely on existing infrastructure; **no new core
60
+ dependency** (the accurate tokenizer is the existing optional `accuracy`
61
+ extra), **no network** (local `tokenizer.json` only), and **fully
62
+ backward compatible** — the feature is inert until a provider declares
63
+ `tokenizer_path`.
64
+
65
+ ### Added
66
+
67
+ - **Language-tax measurement (`coderouter/language_tax.py`).** A leaf
68
+ module exposing `cjk_char_ratio`, `estimate_language_tax`,
69
+ `LanguageTaxBreakdown`, and `language_tax_usd`. CJK detection is
70
+ stdlib-only (Unicode range checks); the accurate token count is
71
+ delegated to the optional `accuracy` (`tokenizers`) backend with a
72
+ char/4 fallback. The tax multiplier is `tokens_accurate /
73
+ tokens_heuristic` — ~1.0 for English/code, ~2.0–4.0 for pure CJK.
74
+
75
+ - **End-to-end cost integration.** `CostBreakdown` gains
76
+ `language_tax_multiplier` / `language_tax_usd`;
77
+ `compute_cost_for_attempt` accepts an optional `language_tax=`. Both
78
+ `cache-observed` emit sites in `routing/fallback.py` (streaming +
79
+ non-streaming) build a `LanguageTaxBreakdown` **only when the provider
80
+ declares `tokenizer_path`**, so the hot path is untouched by default.
81
+ The `cache-observed` log line now carries `language_tax_usd` /
82
+ `language_tax_multiplier`, and `MetricsCollector` aggregates per-provider
83
+ + total language-tax spend (mirroring the cost-savings aggregation).
84
+
85
+ - **`ProviderConfig.tokenizer_path`** — optional path to a local
86
+ `tokenizer.json` for accurate (language-tax) token counting. Local-file
87
+ only; never contacts the HuggingFace Hub. Inert when unset.
88
+
89
+ - **`cjk_ratio_min` auto-route matcher.** A new `RuleMatcher` variant that
90
+ routes turns whose latest user message CJK ratio ≥ threshold to a
91
+ (typically local, tax-free) profile, while ASCII/code turns fall through
92
+ to the cloud chain. Per-turn property mirroring `code_fence_ratio_min`.
93
+
94
+ ```yaml
95
+ auto_router:
96
+ rules:
97
+ - match: { cjk_ratio_min: 0.3 } # JA-heavy turns → local
98
+ profile: local
99
+ - match: { has_tools: true }
100
+ profile: cloud
101
+ default_rule_profile: cloud
102
+ ```
103
+
104
+ - **Dashboard "Cost & Language Tax" panel** on `/dashboard`: total spend,
105
+ cache savings, and CJK language-tax spend (aggregate + per-provider).
106
+ Also surfaces the previously-hidden cost aggregates.
107
+
108
+ - **`token_estimation.extract_text_from_anthropic_request()`** — pulls the
109
+ concatenated request text for the accurate tokenizer leg.
110
+
111
+ ### Security
112
+
113
+ - **Bump starlette 1.0.1 → 1.3.1**, clearing four advisories
114
+ (CVE-2026-48817 / CVE-2026-48818 / CVE-2026-54282 / CVE-2026-54283) that
115
+ failed the `cve-audit` CI job (`pip-audit --strict`).
116
+
117
+ ### Notes
118
+
119
+ - 38 new tests (`test_language_tax`, `test_language_tax_integration`,
120
+ `test_auto_router_cjk`, extended dashboard contract). Full suite:
121
+ **1250 passed, 8 skipped**. ruff clean. The 5-deps invariant is intact.
122
+
123
+ ---
124
+
9
125
  ## [v2.5.5] — 2026-06-06 (Claude Code >= 2.1.154 `system` role normalization)
10
126
 
11
127
  Patch release: ingress-side workaround for a Claude Code CLI regression.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coderouter-cli
3
- Version: 2.5.5
3
+ Version: 2.6.1
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
@@ -161,6 +161,36 @@ ANTHROPIC_BASE_URL=http://localhost:8088 ANTHROPIC_AUTH_TOKEN=dummy claude
161
161
  | **`coderouter replay`** | provider 切替の効果を統計比較 (A/B 分析) / `--suggest-rules` でルール最適化提案 |
162
162
  | **Continuous Probe** | idle 時も定期的に backend を監視 |
163
163
 
164
+ ### 言語税トラッキング — v2.6.0
165
+
166
+ 日本語などの CJK テキストは、クラウドのトークナイザだと「同じ意味の英語」より多くのトークンを消費します(**実測: GPT-4o 系 o200k で平均 1.6 倍、GPT-4 系 cl100k で平均 2.0 倍**)。ローカル LLM は課金されないので、この「言語税」はクラウド利用時だけ効いてきます。CodeRouter v2.6.0 はこれを **計測・ルーティング回避・可視化** します。
167
+
168
+ | 機能 | 何をしてくれるか |
169
+ |---|---|
170
+ | **言語税の計測** | プロバイダに `tokenizer_path`(ローカルの `tokenizer.json`)を指定すると、char/4 ヒューリスティック比の実トークン倍率と割増 USD を算出(ネットワーク不要・未設定なら無効) |
171
+ | **`cjk_ratio_min` ルーティング** | CJK 比率が高いリクエストを自動でローカル LLM(課金ゼロ)へ。コードや英語はクラウドへ |
172
+ | **ダッシュボード可視化** | `/dashboard` の「Cost & Language Tax」パネルで総支出・キャッシュ節約・言語税をリアルタイム表示 |
173
+
174
+ ```yaml
175
+ # providers.yaml — CJK 多めのターンはローカルへ自動回避
176
+ auto_router:
177
+ rules:
178
+ - match: { cjk_ratio_min: 0.3 } # 日本語が3割以上 → ローカル
179
+ profile: local
180
+ - match: { has_tools: true } # ツール使用 → クラウド
181
+ profile: cloud
182
+ default_rule_profile: cloud
183
+
184
+ providers:
185
+ - name: cloud-sonnet
186
+ kind: anthropic
187
+ base_url: https://api.anthropic.com
188
+ model: claude-sonnet-4-6
189
+ tokenizer_path: ~/.coderouter/tokenizers/sonnet.json # 言語税の正確計測(任意)
190
+ ```
191
+
192
+ 詳細 → [言語税ガイド](./docs/guides/language-tax.md)
193
+
164
194
  ### Launcher — llama.cpp / vllm 起動 UI
165
195
 
166
196
  `http://localhost:8088/launcher` で開けるブラウザ UI。llama.cpp や vllm を GUI で起動・管理できます。
@@ -229,6 +259,7 @@ providers:
229
259
  | 使いこなす | [利用ガイド](./docs/guides/usage-guide.md) |
230
260
  | 無料で回す | [無料枠ガイド](./docs/guides/free-tier-guide.md) |
231
261
  | llama.cpp / vllm を GUI で起動 | [Launcher ガイド](./docs/backends/launcher.md) |
262
+ | 言語税を計測・回避する | [言語税ガイド](./docs/guides/language-tax.md) |
232
263
  | 詰まった | [トラブルシューティング](./docs/guides/troubleshooting.md) |
233
264
  | 設計を知りたい | [アーキテクチャ詳細](./docs/concepts/architecture.md) |
234
265
  | 全リリース履歴 | [CHANGELOG](./CHANGELOG.md) |
@@ -116,6 +116,36 @@ Full decision matrix → [Do I need CodeRouter?](./docs/start/when-do-i-need-cod
116
116
  | **`coderouter replay`** | Compare providers statistically (A/B analysis) / `--suggest-rules` for automated rule suggestions |
117
117
  | **Continuous Probe** | Background health monitoring even during idle |
118
118
 
119
+ ### Language Tax tracking — v2.6.0
120
+
121
+ CJK text (Japanese / Chinese / Korean) costs more tokens on cloud tokenizers than the same meaning in English (**measured: ~1.6× on average with GPT-4o-era o200k, ~2.0× with GPT-4-era cl100k**). Local models bill nothing per token, so this "language tax" only bites on the cloud leg. CodeRouter v2.6.0 makes it **measurable, routable, and visible**.
122
+
123
+ | Feature | What it does |
124
+ |---|---|
125
+ | **Language-tax measurement** | Set a provider's `tokenizer_path` (a local `tokenizer.json`) to compute the real token multiplier vs the char/4 heuristic and the extra USD. No network; inert when unset. |
126
+ | **`cjk_ratio_min` routing** | Auto-route CJK-heavy turns to a local (tax-free) model; code/English falls through to the cloud chain. |
127
+ | **Dashboard panel** | The `/dashboard` "Cost & Language Tax" panel shows total spend, cache savings, and language-tax spend live. |
128
+
129
+ ```yaml
130
+ # providers.yaml — steer CJK-heavy turns to local
131
+ auto_router:
132
+ rules:
133
+ - match: { cjk_ratio_min: 0.3 } # >=30% CJK chars -> local
134
+ profile: local
135
+ - match: { has_tools: true } # tool use -> cloud
136
+ profile: cloud
137
+ default_rule_profile: cloud
138
+
139
+ providers:
140
+ - name: cloud-sonnet
141
+ kind: anthropic
142
+ base_url: https://api.anthropic.com
143
+ model: claude-sonnet-4-6
144
+ tokenizer_path: ~/.coderouter/tokenizers/sonnet.json # accurate language-tax (optional)
145
+ ```
146
+
147
+ Details → [Language Tax guide](./docs/guides/language-tax.en.md)
148
+
119
149
  ### Launcher — llama.cpp / vllm GUI
120
150
 
121
151
  Browser UI at `http://localhost:8088/launcher` for starting and managing local inference backends.
@@ -184,6 +214,7 @@ More detail → [Usage guide](./docs/guides/usage-guide.en.md) · [Architecture]
184
214
  | Use it well | [Usage guide](./docs/guides/usage-guide.en.md) |
185
215
  | Run for free | [Free-tier guide](./docs/guides/free-tier-guide.en.md) |
186
216
  | Launch llama.cpp / vllm via GUI | [Launcher guide](./docs/backends/launcher.md) |
217
+ | Measure & avoid the language tax | [Language Tax guide](./docs/guides/language-tax.en.md) |
187
218
  | Stuck? | [Troubleshooting](./docs/guides/troubleshooting.en.md) |
188
219
  | Understand the design | [Architecture](./docs/concepts/architecture.md) |
189
220
  | Full release history | [CHANGELOG](./CHANGELOG.md) |
@@ -116,6 +116,36 @@ ANTHROPIC_BASE_URL=http://localhost:8088 ANTHROPIC_AUTH_TOKEN=dummy claude
116
116
  | **`coderouter replay`** | provider 切替の効果を統計比較 (A/B 分析) / `--suggest-rules` でルール最適化提案 |
117
117
  | **Continuous Probe** | idle 時も定期的に backend を監視 |
118
118
 
119
+ ### 言語税トラッキング — v2.6.0
120
+
121
+ 日本語などの CJK テキストは、クラウドのトークナイザだと「同じ意味の英語」より多くのトークンを消費します(**実測: GPT-4o 系 o200k で平均 1.6 倍、GPT-4 系 cl100k で平均 2.0 倍**)。ローカル LLM は課金されないので、この「言語税」はクラウド利用時だけ効いてきます。CodeRouter v2.6.0 はこれを **計測・ルーティング回避・可視化** します。
122
+
123
+ | 機能 | 何をしてくれるか |
124
+ |---|---|
125
+ | **言語税の計測** | プロバイダに `tokenizer_path`(ローカルの `tokenizer.json`)を指定すると、char/4 ヒューリスティック比の実トークン倍率と割増 USD を算出(ネットワーク不要・未設定なら無効) |
126
+ | **`cjk_ratio_min` ルーティング** | CJK 比率が高いリクエストを自動でローカル LLM(課金ゼロ)へ。コードや英語はクラウドへ |
127
+ | **ダッシュボード可視化** | `/dashboard` の「Cost & Language Tax」パネルで総支出・キャッシュ節約・言語税をリアルタイム表示 |
128
+
129
+ ```yaml
130
+ # providers.yaml — CJK 多めのターンはローカルへ自動回避
131
+ auto_router:
132
+ rules:
133
+ - match: { cjk_ratio_min: 0.3 } # 日本語が3割以上 → ローカル
134
+ profile: local
135
+ - match: { has_tools: true } # ツール使用 → クラウド
136
+ profile: cloud
137
+ default_rule_profile: cloud
138
+
139
+ providers:
140
+ - name: cloud-sonnet
141
+ kind: anthropic
142
+ base_url: https://api.anthropic.com
143
+ model: claude-sonnet-4-6
144
+ tokenizer_path: ~/.coderouter/tokenizers/sonnet.json # 言語税の正確計測(任意)
145
+ ```
146
+
147
+ 詳細 → [言語税ガイド](./docs/guides/language-tax.md)
148
+
119
149
  ### Launcher — llama.cpp / vllm 起動 UI
120
150
 
121
151
  `http://localhost:8088/launcher` で開けるブラウザ UI。llama.cpp や vllm を GUI で起動・管理できます。
@@ -184,6 +214,7 @@ providers:
184
214
  | 使いこなす | [利用ガイド](./docs/guides/usage-guide.md) |
185
215
  | 無料で回す | [無料枠ガイド](./docs/guides/free-tier-guide.md) |
186
216
  | llama.cpp / vllm を GUI で起動 | [Launcher ガイド](./docs/backends/launcher.md) |
217
+ | 言語税を計測・回避する | [言語税ガイド](./docs/guides/language-tax.md) |
187
218
  | 詰まった | [トラブルシューティング](./docs/guides/troubleshooting.md) |
188
219
  | 設計を知りたい | [アーキテクチャ詳細](./docs/concepts/architecture.md) |
189
220
  | 全リリース履歴 | [CHANGELOG](./CHANGELOG.md) |
@@ -185,6 +185,19 @@ class ProviderConfig(BaseModel):
185
185
  )
186
186
  timeout_s: float = Field(default=30.0, ge=1.0, le=600.0)
187
187
 
188
+ # v2.6 language-tax track: path to a LOCAL ``tokenizer.json`` for this
189
+ # provider's model, used to measure the CJK over-count vs the char/4
190
+ # baseline (see ``coderouter.language_tax``). Loaded local-file-only —
191
+ # never contacts the HuggingFace Hub. When unset, language-tax falls
192
+ # back to char/4 (multiplier 1.0) and the feature is silently inert.
193
+ tokenizer_path: str | None = Field(
194
+ default=None,
195
+ description=(
196
+ "Local tokenizer.json for accurate (language-tax) token "
197
+ "counting. No network access. Requires the 'accuracy' extra."
198
+ ),
199
+ )
200
+
188
201
  # Provider-specific extras merged into the outbound request body.
189
202
  # Use for non-standard fields like Ollama's `think: false`, `keep_alive`,
190
203
  # `options.num_ctx`, or any vendor-specific toggle. User-supplied request
@@ -763,6 +776,16 @@ class RuleMatcher(BaseModel):
763
776
  ``request.tools`` set). The ``has_tools`` matcher is the
764
777
  profile-level lever for steering tool-laden traffic to the right
765
778
  chain entirely.
779
+
780
+ Variants (v2.6 / language-tax routing):
781
+
782
+ - ``cjk_ratio_min: 0.3`` — CJK character ratio of the latest user
783
+ message is ``>=`` this threshold. Routes CJK-heavy turns (which
784
+ pay the cloud "language tax" of ~1.2-1.5x more tokens) to a local
785
+ model that bills nothing per token, while ASCII/code turns fall
786
+ through to the cloud chain. Per-turn property like
787
+ ``code_fence_ratio_min``; see
788
+ :func:`coderouter.language_tax.cjk_char_ratio`.
766
789
  """
767
790
 
768
791
  model_config = ConfigDict(extra="forbid")
@@ -773,6 +796,13 @@ class RuleMatcher(BaseModel):
773
796
  content_regex: str | None = None
774
797
  model_pattern: str | None = None
775
798
  content_token_count_min: int | None = Field(default=None, ge=1)
799
+ # v2.6 language-tax routing: CJK character ratio of the latest user
800
+ # message >= this threshold. Lets operators steer CJK-heavy traffic
801
+ # (which carries the cloud language tax) to a local model that bills
802
+ # nothing per token. Operates on the latest user message like
803
+ # ``code_fence_ratio_min`` (a per-turn property), not the whole
804
+ # request. See ``coderouter.language_tax.cjk_char_ratio``.
805
+ cjk_ratio_min: float | None = Field(default=None, ge=0.0, le=1.0)
776
806
  # [Unreleased]: tool-aware routing (OpenClaw + Raspberry Pi 由来).
777
807
  # See class docstring "Variants ([Unreleased] / tool-aware routing)"
778
808
  # above for the full rationale. Boolean shape mirrors ``has_image`` —
@@ -789,6 +819,7 @@ class RuleMatcher(BaseModel):
789
819
  "model_pattern",
790
820
  "content_token_count_min",
791
821
  "has_tools",
822
+ "cjk_ratio_min",
792
823
  )
793
824
 
794
825
  @model_validator(mode="after")
@@ -58,9 +58,13 @@ in the cost calc.
58
58
  from __future__ import annotations
59
59
 
60
60
  from dataclasses import dataclass
61
+ from typing import TYPE_CHECKING
61
62
 
62
63
  from coderouter.config.schemas import CostConfig
63
64
 
65
+ if TYPE_CHECKING: # avoid an import cycle at runtime; used only for typing
66
+ from coderouter.language_tax import LanguageTaxBreakdown
67
+
64
68
 
65
69
  @dataclass(frozen=True)
66
70
  class CostBreakdown:
@@ -82,6 +86,12 @@ class CostBreakdown:
82
86
  chart. ``input_usd`` is "fresh input only" (does not
83
87
  include cache buckets); cache_read_usd / cache_creation_usd
84
88
  are the post-discount / post-premium values.
89
+ language_tax_multiplier: ``tokens_accurate / tokens_heuristic``
90
+ for the request text (v2.6 language-tax track). 1.0 when no
91
+ tax is measurable (English/code, or no accurate tokenizer).
92
+ language_tax_usd: USD share of ``total_usd`` attributable to the
93
+ CJK over-count vs CodeRouter's char/4 English baseline.
94
+ 0.0 for free / local providers. See :mod:`coderouter.language_tax`.
85
95
  """
86
96
 
87
97
  total_usd: float = 0.0
@@ -90,6 +100,10 @@ class CostBreakdown:
90
100
  output_usd: float = 0.0
91
101
  cache_read_usd: float = 0.0
92
102
  cache_creation_usd: float = 0.0
103
+ # v2.6 language-tax track (additive; defaults keep pre-v2.6 behaviour
104
+ # and equality with a bare ``CostBreakdown()``).
105
+ language_tax_multiplier: float = 1.0
106
+ language_tax_usd: float = 0.0
93
107
 
94
108
 
95
109
  _PER_MILLION: float = 1_000_000.0
@@ -102,6 +116,7 @@ def compute_cost_for_attempt(
102
116
  output_tokens: int,
103
117
  cache_read_input_tokens: int,
104
118
  cache_creation_input_tokens: int,
119
+ language_tax: LanguageTaxBreakdown | None = None,
105
120
  ) -> CostBreakdown:
106
121
  """Translate per-attempt token counts into a USD :class:`CostBreakdown`.
107
122
 
@@ -144,6 +159,21 @@ def compute_cost_for_attempt(
144
159
  full_rate_for_cache_read = safe_read * input_rate
145
160
  savings_usd = full_rate_for_cache_read - cache_read_usd
146
161
 
162
+ # v2.6 language tax: the share of fresh-input spend attributable to
163
+ # the CJK over-count vs the char/4 English baseline. Defaults to a
164
+ # 1.0 multiplier / $0 when no LanguageTaxBreakdown is supplied, so
165
+ # the pre-v2.6 call shape is unchanged.
166
+ lt_multiplier = 1.0
167
+ lt_usd = 0.0
168
+ if language_tax is not None:
169
+ lt_multiplier = language_tax.tax_multiplier
170
+ from coderouter.language_tax import language_tax_usd
171
+
172
+ lt_usd = language_tax_usd(
173
+ language_tax.extra_tokens,
174
+ input_tokens_per_million=cost_config.input_tokens_per_million,
175
+ )
176
+
147
177
  return CostBreakdown(
148
178
  total_usd=total_usd,
149
179
  savings_usd=max(savings_usd, 0.0),
@@ -151,4 +181,6 @@ def compute_cost_for_attempt(
151
181
  output_usd=output_usd,
152
182
  cache_read_usd=cache_read_usd,
153
183
  cache_creation_usd=cache_creation_usd,
184
+ language_tax_multiplier=lt_multiplier,
185
+ language_tax_usd=lt_usd,
154
186
  )
@@ -165,6 +165,41 @@ _DASHBOARD_HTML = r"""<!doctype html>
165
165
  </main>
166
166
 
167
167
  <footer class="max-w-7xl mx-auto px-4 md:px-6 pb-8">
168
+ <!-- Panel: Cost & Language Tax (v2.6) -->
169
+ <section class="bg-slate-900/60 border border-slate-800 rounded-lg p-4 mb-4">
170
+ <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-400 mb-3">Cost &amp; Language Tax</h2>
171
+ <div class="grid grid-cols-3 gap-3">
172
+ <div class="rounded-md bg-slate-800/50 p-3">
173
+ <div class="text-xs text-slate-400">Total spend</div>
174
+ <div class="text-2xl font-semibold tabnum" data-bind="cost_total">$0.00</div>
175
+ </div>
176
+ <div class="rounded-md bg-slate-800/50 p-3">
177
+ <div class="text-xs text-slate-400">Cache savings</div>
178
+ <div class="text-2xl font-semibold tabnum text-green-400" data-bind="cost_savings">$0.00</div>
179
+ </div>
180
+ <div class="rounded-md bg-slate-800/50 p-3">
181
+ <div class="text-xs text-slate-400">Language tax (CJK)</div>
182
+ <div class="text-2xl font-semibold tabnum text-amber-400" data-bind="language_tax_total">$0.00</div>
183
+ <div class="text-xs text-slate-500" data-bind="language_tax_hint">no tokenizer configured</div>
184
+ </div>
185
+ </div>
186
+ <div id="language-tax-by-provider" class="text-xs text-slate-400 tabnum mt-3"></div>
187
+ <!-- Token Savings: trim (core budget guard) + compress (plugin) -->
188
+ <div class="grid grid-cols-3 gap-3 mt-3">
189
+ <div class="rounded-md bg-slate-800/50 p-3">
190
+ <div class="text-xs text-slate-400">Tokens saved &middot; trim</div>
191
+ <div class="text-2xl font-semibold tabnum text-sky-400" data-bind="tokens_saved_trim">0</div>
192
+ </div>
193
+ <div class="rounded-md bg-slate-800/50 p-3">
194
+ <div class="text-xs text-slate-400">Tokens saved &middot; compress</div>
195
+ <div class="text-2xl font-semibold tabnum text-sky-400" data-bind="tokens_saved_compress">0</div>
196
+ </div>
197
+ <div class="rounded-md bg-slate-800/50 p-3">
198
+ <div class="text-xs text-slate-400">Tokens saved &middot; total</div>
199
+ <div class="text-2xl font-semibold tabnum text-green-400" data-bind="tokens_saved_total">0</div>
200
+ </div>
201
+ </div>
202
+ </section>
168
203
  <section class="bg-slate-900/60 border border-slate-800 rounded-lg p-4">
169
204
  <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-400 mb-3">Usage Mix</h2>
170
205
  <div id="usage-bar" class="flex h-3 rounded-full overflow-hidden bg-slate-800" role="img" aria-label="usage mix"></div>
@@ -435,6 +470,35 @@ _DASHBOARD_HTML = r"""<!doctype html>
435
470
  {"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"}[c]
436
471
  ));
437
472
 
473
+ // v2.6: cost + language-tax panel. The collector zero-fills these, so
474
+ // a fresh/local-only deployment shows $0.00 across the board.
475
+ const renderCostTax = (snap) => {
476
+ const c = snap.counters || {};
477
+ const usd = (x) => "$" + (Number(x) || 0).toFixed(4);
478
+ setBind("cost_total", usd(c.cost_total_usd_aggregate));
479
+ setBind("cost_savings", usd(c.cost_savings_usd_aggregate));
480
+ const taxTotal = Number(c.language_tax_usd_aggregate) || 0;
481
+ setBind("language_tax_total", usd(taxTotal));
482
+ setBind("language_tax_hint",
483
+ taxTotal > 0 ? "extra paid for CJK vs char/4 baseline"
484
+ : "no tax measured (set provider tokenizer_path)");
485
+ const byProv = c.language_tax_usd || {};
486
+ const el = document.getElementById("language-tax-by-provider");
487
+ const rows = Object.entries(byProv).filter(([, v]) => Number(v) > 0);
488
+ el.innerHTML = rows.length === 0 ? "" :
489
+ rows.map(([n, v]) =>
490
+ '<span class="mr-4"><span class="text-slate-500">' + escapeHTML(n) +
491
+ '</span> ' + usd(v) + '</span>').join("");
492
+ // token-savings tiles. trim comes from the core budget guard;
493
+ // compress from the optional plugin. Collector zero-fills, so an
494
+ // install with neither still renders "0" cleanly.
495
+ const ts = c.tokens_saved_by_mechanism || {};
496
+ const intfmt = (x) => (Number(x) || 0).toLocaleString();
497
+ setBind("tokens_saved_trim", intfmt(ts.trim));
498
+ setBind("tokens_saved_compress", intfmt(ts.compress));
499
+ setBind("tokens_saved_total", intfmt(c.tokens_saved_total));
500
+ };
501
+
438
502
  const renderSnapshot = (snap) => {
439
503
  const startup = snap.startup || {};
440
504
  const cfg = snap.config || {};
@@ -451,6 +515,7 @@ _DASHBOARD_HTML = r"""<!doctype html>
451
515
  renderSparkline(snap);
452
516
  renderRecent(snap);
453
517
  renderUsageMix(snap);
518
+ renderCostTax(snap);
454
519
  };
455
520
 
456
521
  const renderError = (msg) => {