coderouter-cli 2.15.1__tar.gz → 2.16.0__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 (321) hide show
  1. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/CHANGELOG.md +74 -0
  2. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/PKG-INFO +1 -1
  3. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/adapters/anthropic_native.py +4 -0
  4. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/adapters/base.py +20 -0
  5. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/adapters/openai_compat.py +4 -0
  6. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/cli.py +67 -2
  7. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/config/schemas.py +36 -2
  8. coderouter_cli-2.16.0/coderouter/doctor_claude_code.py +181 -0
  9. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/guards/context_budget.py +19 -0
  10. coderouter_cli-2.16.0/coderouter/ingress/anthropic_errors.py +185 -0
  11. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/ingress/anthropic_routes.py +213 -8
  12. coderouter_cli-2.16.0/coderouter/ingress/tool_search.py +100 -0
  13. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/routing/fallback.py +10 -0
  14. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/vscode_init.py +56 -6
  15. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/concepts/context-budget.en.md +7 -6
  16. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/concepts/context-budget.md +7 -6
  17. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/guides/vscode.md +12 -0
  18. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/start/quickstart.en.md +10 -0
  19. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/start/quickstart.md +7 -0
  20. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/pyproject.toml +1 -1
  21. coderouter_cli-2.16.0/tests/test_anthropic_errors.py +352 -0
  22. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_context_budget.py +55 -0
  23. coderouter_cli-2.16.0/tests/test_doctor_claude_code.py +79 -0
  24. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_ingress_anthropic.py +15 -17
  25. coderouter_cli-2.16.0/tests/test_tool_search.py +136 -0
  26. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_vscode_init.py +80 -0
  27. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/.gitignore +0 -0
  28. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/LICENSE +0 -0
  29. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/README.en.md +0 -0
  30. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/README.md +0 -0
  31. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/__init__.py +0 -0
  32. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/__main__.py +0 -0
  33. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/adapters/__init__.py +0 -0
  34. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/adapters/registry.py +0 -0
  35. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/cli_stats.py +0 -0
  36. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/config/__init__.py +0 -0
  37. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/config/capability_registry.py +0 -0
  38. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/config/env_file.py +0 -0
  39. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/config/loader.py +0 -0
  40. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/cost.py +0 -0
  41. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/credentials.py +0 -0
  42. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/data/__init__.py +0 -0
  43. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/data/model-capabilities.yaml +0 -0
  44. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/doctor.py +0 -0
  45. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/doctor_apply.py +0 -0
  46. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/env_security.py +0 -0
  47. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/errors.py +0 -0
  48. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/gguf_introspect.py +0 -0
  49. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/guards/__init__.py +0 -0
  50. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/guards/_fingerprint.py +0 -0
  51. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/guards/backend_health.py +0 -0
  52. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/guards/continuous_probe.py +0 -0
  53. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/guards/drift_actions.py +0 -0
  54. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/guards/drift_detection.py +0 -0
  55. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/guards/memory_budget.py +0 -0
  56. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/guards/memory_pressure.py +0 -0
  57. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/guards/self_healing.py +0 -0
  58. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/guards/tool_loop.py +0 -0
  59. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/hardware.py +0 -0
  60. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/ingress/__init__.py +0 -0
  61. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/ingress/app.py +0 -0
  62. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/ingress/dashboard_routes.py +0 -0
  63. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/ingress/launcher_routes.py +0 -0
  64. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/ingress/metrics_routes.py +0 -0
  65. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/ingress/openai_routes.py +0 -0
  66. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/ingress/ui_auth.py +0 -0
  67. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/language_tax.py +0 -0
  68. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/launcher_devices.py +0 -0
  69. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/launcher_speculative.py +0 -0
  70. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/launcher_swap.py +0 -0
  71. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/logging.py +0 -0
  72. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/metrics/__init__.py +0 -0
  73. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/metrics/collector.py +0 -0
  74. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/metrics/prometheus.py +0 -0
  75. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/output_filters.py +0 -0
  76. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/plugins/__init__.py +0 -0
  77. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/plugins/base.py +0 -0
  78. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/plugins/loader.py +0 -0
  79. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/plugins/registry.py +0 -0
  80. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/py.typed +0 -0
  81. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/rollback.py +0 -0
  82. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/routing/__init__.py +0 -0
  83. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/routing/adaptive.py +0 -0
  84. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/routing/auto_router.py +0 -0
  85. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/routing/budget.py +0 -0
  86. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/routing/capability.py +0 -0
  87. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/routing/fallback_trace.py +0 -0
  88. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/secret_redaction.py +0 -0
  89. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/state/__init__.py +0 -0
  90. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/state/audit_log.py +0 -0
  91. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/state/replay.py +0 -0
  92. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/state/request_log.py +0 -0
  93. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/state/store.py +0 -0
  94. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/state/suggest_rules.py +0 -0
  95. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/token_estimation.py +0 -0
  96. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/token_estimation_accurate.py +0 -0
  97. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/translation/__init__.py +0 -0
  98. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/translation/anthropic.py +0 -0
  99. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/translation/convert.py +0 -0
  100. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/coderouter/translation/tool_repair.py +0 -0
  101. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/README.en.md +0 -0
  102. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/README.md +0 -0
  103. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/assets/dashboard-demo.png +0 -0
  104. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/claude-code-llamacpp-vllm.en.md +0 -0
  105. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/claude-code-llamacpp-vllm.md +0 -0
  106. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/external-agents.en.md +0 -0
  107. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/external-agents.md +0 -0
  108. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/gguf_dl.en.md +0 -0
  109. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/gguf_dl.md +0 -0
  110. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/hf-ollama-models.en.md +0 -0
  111. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/hf-ollama-models.md +0 -0
  112. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/install-backends.en.md +0 -0
  113. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/install-backends.md +0 -0
  114. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/launcher-quickstart.en.md +0 -0
  115. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/launcher-quickstart.md +0 -0
  116. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/launcher.en.md +0 -0
  117. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/launcher.md +0 -0
  118. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/llamacpp-direct.en.md +0 -0
  119. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/llamacpp-direct.md +0 -0
  120. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/lmstudio-direct.en.md +0 -0
  121. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/lmstudio-direct.md +0 -0
  122. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/backends/verify-ollama-0.23.1.md +0 -0
  123. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/concepts/architecture.en.md +0 -0
  124. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/concepts/architecture.md +0 -0
  125. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/concepts/continuous-probing.en.md +0 -0
  126. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/concepts/continuous-probing.md +0 -0
  127. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/concepts/drift-detection.en.md +0 -0
  128. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/concepts/drift-detection.md +0 -0
  129. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/concepts/partial-stitch.en.md +0 -0
  130. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/concepts/partial-stitch.md +0 -0
  131. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/concepts/stream-truncation.en.md +0 -0
  132. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/concepts/stream-truncation.md +0 -0
  133. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/designs/agent-cli-plugin-extraction.md +0 -0
  134. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/designs/external-agents-adapter.md +0 -0
  135. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/designs/launcher-model-swap.md +0 -0
  136. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/designs/launcher-multi-build.md +0 -0
  137. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/designs/orchestration-companion.md +0 -0
  138. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/designs/v1.5-dashboard-mockup.html +0 -0
  139. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/designs/v1.6-auto-router-verification.md +0 -0
  140. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/designs/v1.6-auto-router.md +0 -0
  141. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/guides/free-tier-guide.en.md +0 -0
  142. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/guides/free-tier-guide.md +0 -0
  143. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/guides/language-tax.en.md +0 -0
  144. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/guides/language-tax.md +0 -0
  145. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/guides/remote-access.en.md +0 -0
  146. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/guides/remote-access.md +0 -0
  147. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/guides/security.en.md +0 -0
  148. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/guides/security.md +0 -0
  149. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/guides/subagent-routing.en.md +0 -0
  150. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/guides/subagent-routing.md +0 -0
  151. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/guides/troubleshooting.en.md +0 -0
  152. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/guides/troubleshooting.md +0 -0
  153. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/guides/usage-guide.en.md +0 -0
  154. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/guides/usage-guide.md +0 -0
  155. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/low-memory-integration.en.md +0 -0
  156. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/low-memory-integration.md +0 -0
  157. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/openrouter-roster/CHANGES.md +0 -0
  158. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/openrouter-roster/README.md +0 -0
  159. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/openrouter-roster/latest.json +0 -0
  160. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/retrospectives/v0.4.md +0 -0
  161. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/retrospectives/v0.5-verify.md +0 -0
  162. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/retrospectives/v0.5.md +0 -0
  163. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/retrospectives/v0.6.md +0 -0
  164. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/retrospectives/v0.7.md +0 -0
  165. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/retrospectives/v1.0-verify.md +0 -0
  166. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/retrospectives/v1.0.md +0 -0
  167. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/start/when-do-i-need-coderouter.en.md +0 -0
  168. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/docs/start/when-do-i-need-coderouter.md +0 -0
  169. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/examples/.env.example +0 -0
  170. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/examples/README.md +0 -0
  171. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/examples/providers-agent-cli.yaml +0 -0
  172. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/examples/providers-multiagent.yaml +0 -0
  173. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/examples/providers.cli-session.yaml +0 -0
  174. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/examples/providers.context-budget-test.yaml +0 -0
  175. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/examples/providers.llamacpp-vllm.yaml +0 -0
  176. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/examples/providers.nvidia-nim.yaml +0 -0
  177. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/examples/providers.ollama-auto-custom.yaml +0 -0
  178. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/examples/providers.ollama-auto.yaml +0 -0
  179. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/examples/providers.ollama-free-chain.yaml +0 -0
  180. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/examples/providers.opusplan.yaml +0 -0
  181. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/examples/providers.raspberrypi.yaml +0 -0
  182. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/examples/providers.swap.yaml +0 -0
  183. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/examples/providers.yaml +0 -0
  184. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/scripts/demo_traffic.sh +0 -0
  185. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/scripts/openrouter_roster_diff.py +0 -0
  186. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/scripts/smoke_v2_2.sh +0 -0
  187. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/scripts/verify-providers.yaml +0 -0
  188. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/scripts/verify_ollama_0_23.py +0 -0
  189. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/scripts/verify_v0_5.sh +0 -0
  190. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/scripts/verify_v1_0.sh +0 -0
  191. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/__init__.py +0 -0
  192. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/conftest.py +0 -0
  193. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_adapter_anthropic.py +0 -0
  194. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_agent_cli.py +0 -0
  195. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_audit_log.py +0 -0
  196. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_auto_router.py +0 -0
  197. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_auto_router_cjk.py +0 -0
  198. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_backend_health.py +0 -0
  199. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_budget.py +0 -0
  200. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_capability.py +0 -0
  201. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_capability_degraded_payload.py +0 -0
  202. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_capability_registry.py +0 -0
  203. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_capability_registry_cache_control.py +0 -0
  204. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_capability_tool_choice.py +0 -0
  205. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_claude_code_suitability.py +0 -0
  206. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_cli.py +0 -0
  207. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_cli_bind_warning.py +0 -0
  208. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_cli_stats.py +0 -0
  209. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_config.py +0 -0
  210. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_config_cwd_warning.py +0 -0
  211. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_config_shim_actions.py +0 -0
  212. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_continuous_probe.py +0 -0
  213. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_credentials.py +0 -0
  214. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_dashboard_endpoint.py +0 -0
  215. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_doctor.py +0 -0
  216. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_doctor_apply.py +0 -0
  217. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_doctor_cache_probe.py +0 -0
  218. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_drift_actions.py +0 -0
  219. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_drift_detection.py +0 -0
  220. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_drift_detection_integration.py +0 -0
  221. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_env_file.py +0 -0
  222. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_env_security.py +0 -0
  223. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_errors.py +0 -0
  224. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_examples_yaml.py +0 -0
  225. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_examples_yaml_roundtrip.py +0 -0
  226. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fallback.py +0 -0
  227. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fallback_anthropic.py +0 -0
  228. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fallback_cache_control.py +0 -0
  229. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fallback_cache_observed.py +0 -0
  230. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fallback_cache_strip.py +0 -0
  231. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fallback_empty_response.py +0 -0
  232. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fallback_misconfig_warn.py +0 -0
  233. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fallback_paid_gate.py +0 -0
  234. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fallback_reason.py +0 -0
  235. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fallback_thinking.py +0 -0
  236. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fallback_tool_choice_emulate.py +0 -0
  237. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fallback_tool_integrity.py +0 -0
  238. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fix_h1_prometheus_drift.py +0 -0
  239. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fix_h2_h6_translation.py +0 -0
  240. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fix_h3_client_reuse.py +0 -0
  241. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fix_h4_h5_guards.py +0 -0
  242. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fix_h7_probe_restore.py +0 -0
  243. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fix_h8_launcher_auth.py +0 -0
  244. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fix_mA_routing.py +0 -0
  245. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fix_mB_adapters.py +0 -0
  246. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fix_mC_convert.py +0 -0
  247. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fix_mD_logging_metrics.py +0 -0
  248. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fix_mE_config_validation.py +0 -0
  249. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fix_mF_ingress.py +0 -0
  250. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_fix_v213_launcher_hardening.py +0 -0
  251. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_gguf_introspect.py +0 -0
  252. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_guards_tool_loop.py +0 -0
  253. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_hardware.py +0 -0
  254. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_import_hygiene.py +0 -0
  255. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_ingress_count_tokens.py +0 -0
  256. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_ingress_profile.py +0 -0
  257. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_language_tax.py +0 -0
  258. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_language_tax_integration.py +0 -0
  259. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_backend_variants.py +0 -0
  260. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_config_bench.py +0 -0
  261. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_devices.py +0 -0
  262. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_devices_detect.py +0 -0
  263. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_devices_routes.py +0 -0
  264. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_gui_devices.py +0 -0
  265. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_gui_events.py +0 -0
  266. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_gui_readiness.py +0 -0
  267. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_gui_sweep_close.py +0 -0
  268. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_mtp.py +0 -0
  269. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_mtp_fallback.py +0 -0
  270. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_provider_sync.py +0 -0
  271. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_readiness_restart.py +0 -0
  272. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_swap.py +0 -0
  273. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_swap_review.py +0 -0
  274. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_variant_config.py +0 -0
  275. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_variant_gui.py +0 -0
  276. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_variant_routes.py +0 -0
  277. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_launcher_variant_sweep.py +0 -0
  278. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_memory_budget.py +0 -0
  279. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_memory_pressure.py +0 -0
  280. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_metrics_cache.py +0 -0
  281. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_metrics_collector.py +0 -0
  282. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_metrics_cost.py +0 -0
  283. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_metrics_endpoint.py +0 -0
  284. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_metrics_jsonl.py +0 -0
  285. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_metrics_prometheus.py +0 -0
  286. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_metrics_prometheus_cache.py +0 -0
  287. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_models_passthrough.py +0 -0
  288. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_openai_compat.py +0 -0
  289. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_openrouter_roster_diff.py +0 -0
  290. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_output_filters.py +0 -0
  291. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_output_filters_adapters.py +0 -0
  292. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_packaging.py +0 -0
  293. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_partial_stitch.py +0 -0
  294. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_plugin_adapter.py +0 -0
  295. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_plugins_integration.py +0 -0
  296. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_plugins_loader.py +0 -0
  297. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_plugins_registry.py +0 -0
  298. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_reasoning_strip.py +0 -0
  299. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_repair_byte_fallback.py +0 -0
  300. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_request_log.py +0 -0
  301. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_role_normalization.py +0 -0
  302. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_rollback.py +0 -0
  303. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_routing_adaptive.py +0 -0
  304. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_secret_redaction.py +0 -0
  305. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_self_healing.py +0 -0
  306. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_setup_sh.py +0 -0
  307. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_state_store.py +0 -0
  308. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_stop_reason_forward_compat.py +0 -0
  309. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_stream_truncation.py +0 -0
  310. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_token_estimation.py +0 -0
  311. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_token_estimation_accurate.py +0 -0
  312. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_tokens_saved_metric.py +0 -0
  313. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_tool_repair.py +0 -0
  314. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_tool_repair_differential.py +0 -0
  315. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_tool_repair_lenient.py +0 -0
  316. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_tool_repair_r4.py +0 -0
  317. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_tool_repair_scanners.py +0 -0
  318. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_toolrepair_bench.py +0 -0
  319. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_translation_anthropic.py +0 -0
  320. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_translation_reverse.py +0 -0
  321. {coderouter_cli-2.15.1 → coderouter_cli-2.16.0}/tests/test_ui_auth.py +0 -0
@@ -9,6 +9,80 @@ are kept verbatim where the Japanese text itself is the subject).
9
9
 
10
10
  ---
11
11
 
12
+ ## [v2.16.0] — 2026-10-24 (Claude Code 2.1.3xx behind a gateway)
13
+
14
+ Claude Code 2.1.285–2.1.309 changed several defaults for sessions behind a
15
+ custom `ANTHROPIC_BASE_URL`: tool search is on, Opus 4.7+ / Sonnet 5+ /
16
+ Fable ids are treated as 1M-context models, auto mode asks the gateway for a
17
+ server-side classifier, and recovery from rejected features or an
18
+ overflowing prompt now depends on reading the gateway's error message. This
19
+ release makes CodeRouter answer in the shape those recoveries expect.
20
+
21
+ ### Added
22
+
23
+ - **Anthropic-shaped errors when the whole chain fails (`/v1/messages`).**
24
+ A failed chain used to come back as FastAPI's `{"detail": "..."}` with
25
+ HTTP 502 whatever the upstreams said. It now uses Anthropic's envelope
26
+ (`{"type": "error", "error": {"type", "message"}}`) and a status chosen
27
+ from what the upstreams said (`coderouter/ingress/anthropic_errors.py`):
28
+ - an upstream context-overflow error (llama.cpp
29
+ `exceed_context_size_error`, vLLM / OpenAI "maximum context length",
30
+ Ollama "prompt too long", …) → **400 `invalid_request_error`, message
31
+ starting `prompt is too long`**, which is what triggers Claude Code's
32
+ reactive compaction. An overflow caused by the *completion* budget
33
+ ("… 32000 in the completion", `max_tokens`) is not reported this way —
34
+ compacting the prompt cannot fix it — and goes out as a plain 400;
35
+ - a non-retryable upstream 400 / 422 → 400 with the upstream's own
36
+ message (Claude Code drops a rejected beta header / feature only when it
37
+ can read why);
38
+ - 413 → `request_too_large`; 429 → `rate_limit_error` with the upstream's
39
+ `retry-after` passed through;
40
+ - upstream 401 / 403 and everything else → 502 `api_error` (they concern
41
+ CodeRouter's own upstream credentials, not the client's).
42
+ Upstream messages are secret-redacted and length-capped. `AdapterError`
43
+ now keeps the upstream body (capped at 4096 chars) and `Retry-After`.
44
+ - **Streaming requests get a real HTTP status for pre-content failures.**
45
+ The route now pulls the engine's first event before committing the
46
+ response, so a chain that fails before producing anything is answered
47
+ with the status above instead of `200` plus an in-stream `error` event —
48
+ as api.anthropic.com does. Once the first event exists, nothing changes.
49
+ The wait shares the stream's overall M14 ceiling (one deadline, not two),
50
+ and a client that disconnects during it cancels the upstream attempt.
51
+ - **`deferred_tools_action` (profile; `auto` | `reject` | `expand` |
52
+ `passthrough`, default `auto`).** Claude Code 2.1.304+ sends a server-side
53
+ `tool_search_tool_*` and tools marked `defer_loading: true`; only
54
+ api.anthropic.com runs the search. `auto` passes the request through only
55
+ when every provider in the chain is `kind: anthropic` on api.anthropic.com
56
+ and otherwise answers 400, which makes Claude Code (2.1.305+) retry
57
+ without tool search. `expand` drops the search tool and the flags and
58
+ sends every tool up front.
59
+ - **`context_budget_action: reject`.** At `context_budget_trim_threshold`
60
+ the guard answers 400 `prompt is too long: ~N tokens > M maximum` instead
61
+ of trimming, so Claude Code summarizes the conversation itself rather
62
+ than losing history silently. Works for streaming and non-streaming.
63
+ - **`coderouter vscode-init` writes Claude Code gateway settings** by
64
+ default: `ENABLE_TOOL_SEARCH=false`, `CLAUDE_CODE_AUTO_MODE_SERVER=0`,
65
+ and — with the new `--max-context-tokens N` —
66
+ `CLAUDE_CODE_MAX_CONTEXT_TOKENS=N`. `--no-claude-code-env` opts out. Same
67
+ merge / conflict rules as the existing variables; an export of one of
68
+ them outside the `.envrc` fence conflicts only when this run writes it.
69
+ - **`coderouter doctor --check-claude-code`.** Local-only check of the
70
+ Claude Code install: warns on versions with known gateway bugs (before
71
+ 2.1.301: compaction can repeat; 2.1.304: every request fails when the
72
+ gateway rejects tool search) and on missing `ENABLE_TOOL_SEARCH`,
73
+ `CLAUDE_CODE_MAX_CONTEXT_TOKENS` (2.1.285+) and
74
+ `CLAUDE_CODE_AUTO_MODE_SERVER`. Exit 0 OK / 2 needs attention.
75
+
76
+ ### Changed
77
+
78
+ - **Breaking for clients that parse error bodies:** `/v1/messages` errors
79
+ for a failed chain are no longer `{"detail": "..."}`, and a streaming
80
+ request whose chain fails before the first event is no longer
81
+ `200` + `event: error`. Both now match the Anthropic API. The
82
+ `/v1/chat/completions` (OpenAI) route is unchanged.
83
+
84
+ ---
85
+
12
86
  ## [v2.15.1] — 2026-10-24 (docs and defaults for the October 2026 ecosystem)
13
87
 
14
88
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: coderouter-cli
3
- Version: 2.15.1
3
+ Version: 2.16.0
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
@@ -458,6 +458,8 @@ class AnthropicAdapter(BaseAdapter):
458
458
  provider=self.name,
459
459
  status_code=resp.status_code,
460
460
  retryable=resp.status_code in _RETRYABLE_STATUSES,
461
+ upstream_body=resp.text,
462
+ retry_after=resp.headers.get("retry-after"),
461
463
  )
462
464
 
463
465
  try:
@@ -570,6 +572,8 @@ class AnthropicAdapter(BaseAdapter):
570
572
  provider=self.name,
571
573
  status_code=resp.status_code,
572
574
  retryable=resp.status_code in _RETRYABLE_STATUSES,
575
+ upstream_body=body.decode("utf-8", errors="replace"),
576
+ retry_after=resp.headers.get("retry-after"),
573
577
  )
574
578
 
575
579
  # Anthropic SSE block shape:
@@ -101,6 +101,12 @@ class StreamChunk(BaseModel):
101
101
  usage: dict[str, Any] | None = None
102
102
 
103
103
 
104
+ #: v2.16.0: max chars of an upstream error body an :class:`AdapterError`
105
+ #: keeps. Error bodies are small; the cap only bounds a misbehaving upstream
106
+ #: that answers an error status with a large HTML page.
107
+ UPSTREAM_BODY_CAP = 4096
108
+
109
+
104
110
  class AdapterError(CodeRouterError):
105
111
  """Raised when a provider call fails in a way the fallback engine should retry on."""
106
112
 
@@ -111,6 +117,8 @@ class AdapterError(CodeRouterError):
111
117
  provider: str,
112
118
  status_code: int | None = None,
113
119
  retryable: bool = True,
120
+ upstream_body: str | None = None,
121
+ retry_after: str | None = None,
114
122
  ) -> None:
115
123
  """Construct an AdapterError.
116
124
 
@@ -125,11 +133,23 @@ class AdapterError(CodeRouterError):
125
133
  retryable: When True, the fallback engine may try the next
126
134
  provider in the chain. When False, the engine stops
127
135
  and surfaces the error as a terminal failure.
136
+ upstream_body: v2.16.0 — the upstream's error body (capped at
137
+ :data:`UPSTREAM_BODY_CAP` chars), kept so the ingress can
138
+ hand the upstream's own wording back to the client.
139
+ Claude Code recovers from several 400s (rejected beta
140
+ headers, tool search, context overflow) only when it can
141
+ read the upstream message. Never logged by this class.
142
+ retry_after: v2.16.0 — the upstream's ``Retry-After`` header
143
+ value, verbatim, when it sent one.
128
144
  """
129
145
  super().__init__(message)
130
146
  self.provider = provider
131
147
  self.status_code = status_code
132
148
  self.retryable = retryable
149
+ self.upstream_body = (
150
+ upstream_body[:UPSTREAM_BODY_CAP] if upstream_body is not None else None
151
+ )
152
+ self.retry_after = retry_after
133
153
 
134
154
  def __str__(self) -> str:
135
155
  """Render as ``[provider status=NNN] message`` for log trails."""
@@ -244,6 +244,8 @@ class OpenAICompatAdapter(BaseAdapter):
244
244
  provider=self.name,
245
245
  status_code=resp.status_code,
246
246
  retryable=resp.status_code in _RETRYABLE_STATUSES,
247
+ upstream_body=resp.text,
248
+ retry_after=resp.headers.get("retry-after"),
247
249
  )
248
250
 
249
251
  try:
@@ -381,6 +383,8 @@ class OpenAICompatAdapter(BaseAdapter):
381
383
  provider=self.name,
382
384
  status_code=resp.status_code,
383
385
  retryable=resp.status_code in _RETRYABLE_STATUSES,
386
+ upstream_body=body.decode("utf-8", errors="replace"),
387
+ retry_after=resp.headers.get("retry-after"),
384
388
  )
385
389
  async for line in resp.aiter_lines():
386
390
  if not line:
@@ -165,6 +165,17 @@ def _build_parser() -> argparse.ArgumentParser:
165
165
  "for live key values. Exit 0 clean / 2 needs attention / 1 leak."
166
166
  ),
167
167
  )
168
+ doctor.add_argument(
169
+ "--check-claude-code",
170
+ action="store_true",
171
+ help=(
172
+ "v2.16.0: check the local Claude Code install for gateway use — "
173
+ "known-bad versions (<2.1.301, 2.1.304) and the env vars that "
174
+ "matter behind a non-Anthropic gateway (ENABLE_TOOL_SEARCH, "
175
+ "CLAUDE_CODE_MAX_CONTEXT_TOKENS, CLAUDE_CODE_AUTO_MODE_SERVER). "
176
+ "No network. Exit 0 OK / 2 needs attention."
177
+ ),
178
+ )
168
179
  doctor.add_argument(
169
180
  "--config",
170
181
  default=None,
@@ -454,6 +465,28 @@ def _build_parser() -> argparse.ArgumentParser:
454
465
  "generation. Tip: keep secrets in a separate .envrc.local."
455
466
  ),
456
467
  )
468
+ vscode_init.add_argument(
469
+ "--max-context-tokens",
470
+ type=int,
471
+ default=None,
472
+ metavar="N",
473
+ help=(
474
+ "v2.16.0: also write CLAUDE_CODE_MAX_CONTEXT_TOKENS=N so Claude "
475
+ "Code compacts before your local model's context fills up "
476
+ "(since 2.1.285 it assumes 1M behind a gateway). Use the "
477
+ "smallest context window in the profile's chain."
478
+ ),
479
+ )
480
+ vscode_init.add_argument(
481
+ "--no-claude-code-env",
482
+ action="store_true",
483
+ help=(
484
+ "v2.16.0: do not write the Claude Code gateway settings "
485
+ "(ENABLE_TOOL_SEARCH=false, CLAUDE_CODE_AUTO_MODE_SERVER=0, "
486
+ "and --max-context-tokens). Use when the chain is "
487
+ "api.anthropic.com only."
488
+ ),
489
+ )
457
490
  vscode_init.add_argument(
458
491
  "--dry-run",
459
492
  action="store_true",
@@ -595,10 +628,16 @@ def _run_doctor(args: argparse.Namespace) -> int:
595
628
  risks AND broken providers can use a single command.
596
629
  """
597
630
  check_secrets = bool(getattr(args, "check_secrets", False))
598
- if args.check_model is None and args.check_env is None and not check_secrets:
631
+ check_claude_code = bool(getattr(args, "check_claude_code", False))
632
+ if (
633
+ args.check_model is None
634
+ and args.check_env is None
635
+ and not check_secrets
636
+ and not check_claude_code
637
+ ):
599
638
  print(
600
639
  "doctor: provide --check-model PROVIDER, --check-env [PATH], "
601
- "and/or --check-secrets",
640
+ "--check-secrets and/or --check-claude-code",
602
641
  file=sys.stderr,
603
642
  )
604
643
  return 1
@@ -614,6 +653,14 @@ def _run_doctor(args: argparse.Namespace) -> int:
614
653
  if check_secrets:
615
654
  worst_exit = max(worst_exit, _run_check_secrets(args.config))
616
655
 
656
+ if check_claude_code:
657
+ from coderouter.doctor_claude_code import format_report as _cc_format
658
+ from coderouter.doctor_claude_code import run_check as _cc_run
659
+
660
+ cc_report = _cc_run()
661
+ print(_cc_format(cc_report))
662
+ worst_exit = max(worst_exit, cc_report.exit_code)
663
+
617
664
  if args.check_model is not None:
618
665
  worst_exit = max(worst_exit, _run_check_model(args))
619
666
 
@@ -976,6 +1023,7 @@ def _run_vscode_init(args: argparse.Namespace) -> int:
976
1023
  """
977
1024
  from coderouter.vscode_init import (
978
1025
  DEFAULT_PORT,
1026
+ claude_code_env,
979
1027
  exit_code_for,
980
1028
  format_result,
981
1029
  run_vscode_init,
@@ -991,6 +1039,22 @@ def _run_vscode_init(args: argparse.Namespace) -> int:
991
1039
 
992
1040
  port = args.port if args.port is not None else DEFAULT_PORT
993
1041
 
1042
+ extra_env: dict[str, str] | None = None
1043
+ max_ctx = getattr(args, "max_context_tokens", None)
1044
+ if getattr(args, "no_claude_code_env", False):
1045
+ if max_ctx is not None:
1046
+ print(
1047
+ "vscode-init: --max-context-tokens has no effect with --no-claude-code-env",
1048
+ file=sys.stderr,
1049
+ )
1050
+ return 2
1051
+ else:
1052
+ try:
1053
+ extra_env = claude_code_env(max_context_tokens=max_ctx)
1054
+ except ValueError as exc:
1055
+ print(f"vscode-init: {exc}", file=sys.stderr)
1056
+ return 2
1057
+
994
1058
  try:
995
1059
  result = run_vscode_init(
996
1060
  target,
@@ -999,6 +1063,7 @@ def _run_vscode_init(args: argparse.Namespace) -> int:
999
1063
  with_envrc=args.with_envrc,
1000
1064
  dry_run=args.dry_run,
1001
1065
  force=args.force,
1066
+ extra_env=extra_env,
1002
1067
  )
1003
1068
  except FileNotFoundError as exc:
1004
1069
  # Raised only when target vanishes between the pre-check above
@@ -1089,7 +1089,13 @@ class FallbackChain(BaseModel):
1089
1089
  # (``context_budget_preserve_last_n``) are always
1090
1090
  # kept, and tool_use / tool_result pairs are preserved
1091
1091
  # atomically to avoid breaking agent loops.
1092
- context_budget_action: Literal["off", "warn", "trim"] = Field(
1092
+ # * ``reject`` — v2.16.0: ``warn`` + at ``context_budget_trim_threshold``
1093
+ # refuse the request with Anthropic's own overflow error
1094
+ # (HTTP 400, ``prompt is too long: ...``). Claude Code
1095
+ # answers that by compacting the conversation itself
1096
+ # (a summary, not a silent cut), so this is the
1097
+ # recommended action for Claude Code sessions.
1098
+ context_budget_action: Literal["off", "warn", "trim", "reject"] = Field(
1093
1099
  default="off",
1094
1100
  description=(
1095
1101
  "v2.0-F (L1): action when estimated request tokens approach "
@@ -1097,7 +1103,10 @@ class FallbackChain(BaseModel):
1097
1103
  "disables the guard entirely. ``warn`` emits a log and "
1098
1104
  "response header at ``context_budget_warn_threshold``. "
1099
1105
  "``trim`` additionally removes old messages at "
1100
- "``context_budget_trim_threshold`` to reclaim context space."
1106
+ "``context_budget_trim_threshold`` to reclaim context space. "
1107
+ "``reject`` (v2.16.0) instead answers HTTP 400 "
1108
+ "``prompt is too long`` at that threshold so the client "
1109
+ "compacts the conversation itself (Claude Code does)."
1101
1110
  ),
1102
1111
  )
1103
1112
  context_budget_warn_threshold: float = Field(
@@ -1222,6 +1231,31 @@ class FallbackChain(BaseModel):
1222
1231
  # --- v2.0-H (L6): Mid-stream partial stitching --------------------------
1223
1232
  # * ``off`` — discard partial content on mid-stream failure (legacy).
1224
1233
  # * ``surface`` — return partial content as a truncated-but-valid response.
1234
+ # v2.16.0 (tool search): Claude Code 2.1.304+ turns tool search on behind
1235
+ # a custom ANTHROPIC_BASE_URL. The request then carries a server-side
1236
+ # tool-search tool (``type: tool_search_tool_*``) and tools marked
1237
+ # ``defer_loading: true``, which only api.anthropic.com executes. A
1238
+ # local model sees the search tool as an ordinary function (and may
1239
+ # "call" it), while every deferred MCP tool lands in its context anyway.
1240
+ # Claude Code retries without tool search when the gateway answers 400.
1241
+ # * ``auto`` — ``passthrough`` when every provider in the chain
1242
+ # is ``kind: anthropic`` on api.anthropic.com,
1243
+ # otherwise ``reject`` (default).
1244
+ # * ``reject`` — answer 400 so the client turns tool search off.
1245
+ # * ``expand`` — drop the search tool and the ``defer_loading``
1246
+ # flags; every tool is sent up front.
1247
+ # * ``passthrough`` — forward unchanged (pre-v2.16.0 behaviour).
1248
+ deferred_tools_action: Literal["auto", "reject", "expand", "passthrough"] = Field(
1249
+ default="auto",
1250
+ description=(
1251
+ "v2.16.0: what to do with Claude Code tool search "
1252
+ "(``defer_loading`` tools / ``tool_search_tool_*``). ``auto`` "
1253
+ "passes it through only for chains made entirely of "
1254
+ "api.anthropic.com providers and rejects it (HTTP 400, which "
1255
+ "makes Claude Code retry without tool search) otherwise."
1256
+ ),
1257
+ )
1258
+
1225
1259
  partial_stitch_action: Literal["off", "surface"] = Field(
1226
1260
  default="off",
1227
1261
  description=(
@@ -0,0 +1,181 @@
1
+ """``coderouter doctor --check-claude-code`` (v2.16.0).
2
+
3
+ Local-only checks for a Claude Code install that talks to CodeRouter:
4
+
5
+ 1. **Version.** Known gateway-relevant regressions:
6
+
7
+ - before 2.1.301: compaction could repeat when the response's ``model``
8
+ name differs from the requested one (every translating gateway);
9
+ - exactly 2.1.304: every request failed behind a gateway that rejects
10
+ tool search (fixed in 2.1.305).
11
+
12
+ 2. **Environment.** Variables that matter behind a non-Anthropic gateway
13
+ (see ``coderouter vscode-init``):
14
+
15
+ - ``ENABLE_TOOL_SEARCH`` unset → tool search is on by default since
16
+ 2.1.304; CodeRouter refuses it (``deferred_tools_action``) and Claude
17
+ Code retries without it — works, but costs a round trip per session;
18
+ - ``CLAUDE_CODE_MAX_CONTEXT_TOKENS`` unset on 2.1.285+ → Claude Code may
19
+ assume a 1M window and compact far too late for a local model;
20
+ - ``CLAUDE_CODE_AUTO_MODE_SERVER`` unset → auto mode asks the gateway
21
+ for a server-side classifier a local backend does not have.
22
+
23
+ No network, no config file. Exit codes follow the other doctor checks:
24
+ 0 = OK, 2 = needs attention, 1 = could not run.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import os
30
+ import re
31
+ import shutil
32
+ import subprocess
33
+ from collections.abc import Callable, Mapping
34
+ from dataclasses import dataclass, field
35
+
36
+ _VERSION_RE = re.compile(r"(\d+)\.(\d+)\.(\d+)")
37
+
38
+ Version = tuple[int, int, int]
39
+
40
+
41
+ @dataclass
42
+ class ClaudeCodeFinding:
43
+ level: str # "ok" | "warn" | "info"
44
+ message: str
45
+
46
+
47
+ @dataclass
48
+ class ClaudeCodeReport:
49
+ version: Version | None
50
+ findings: list[ClaudeCodeFinding] = field(default_factory=list)
51
+ error: str | None = None
52
+
53
+ @property
54
+ def exit_code(self) -> int:
55
+ if self.error is not None:
56
+ return 1
57
+ return 2 if any(f.level == "warn" for f in self.findings) else 0
58
+
59
+
60
+ def parse_version(text: str) -> Version | None:
61
+ m = _VERSION_RE.search(text)
62
+ if m is None:
63
+ return None
64
+ return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
65
+
66
+
67
+ def _fmt(v: Version) -> str:
68
+ return ".".join(str(p) for p in v)
69
+
70
+
71
+ def check(version: Version | None, env: Mapping[str, str]) -> list[ClaudeCodeFinding]:
72
+ """Pure check: findings for a Claude Code version and an environment."""
73
+ out: list[ClaudeCodeFinding] = []
74
+ if version is not None:
75
+ if version < (2, 1, 301):
76
+ out.append(
77
+ ClaudeCodeFinding(
78
+ "warn",
79
+ f"Claude Code {_fmt(version)} is older than 2.1.301: compaction can "
80
+ "repeat behind a gateway that reports a different model name. "
81
+ "Update Claude Code.",
82
+ )
83
+ )
84
+ elif version == (2, 1, 304):
85
+ out.append(
86
+ ClaudeCodeFinding(
87
+ "warn",
88
+ "Claude Code 2.1.304 fails every request behind a gateway that "
89
+ "rejects tool search (fixed in 2.1.305). Update Claude Code, or "
90
+ "set ENABLE_TOOL_SEARCH=false.",
91
+ )
92
+ )
93
+ else:
94
+ out.append(ClaudeCodeFinding("ok", f"Claude Code {_fmt(version)}"))
95
+
96
+ if env.get("ENABLE_TOOL_SEARCH", "").lower() in ("false", "0"):
97
+ out.append(ClaudeCodeFinding("ok", "ENABLE_TOOL_SEARCH=false"))
98
+ else:
99
+ out.append(
100
+ ClaudeCodeFinding(
101
+ "warn",
102
+ "ENABLE_TOOL_SEARCH is not false: Claude Code 2.1.304+ starts each "
103
+ "session with tool search, which only api.anthropic.com runs. "
104
+ "CodeRouter refuses it and Claude Code retries without it. Set "
105
+ "ENABLE_TOOL_SEARCH=false (coderouter vscode-init does) to skip "
106
+ "that round trip.",
107
+ )
108
+ )
109
+
110
+ if "CLAUDE_CODE_MAX_CONTEXT_TOKENS" in env:
111
+ out.append(
112
+ ClaudeCodeFinding(
113
+ "ok", f"CLAUDE_CODE_MAX_CONTEXT_TOKENS={env['CLAUDE_CODE_MAX_CONTEXT_TOKENS']}"
114
+ )
115
+ )
116
+ elif version is None or version >= (2, 1, 285):
117
+ out.append(
118
+ ClaudeCodeFinding(
119
+ "warn",
120
+ "CLAUDE_CODE_MAX_CONTEXT_TOKENS is not set: since 2.1.285 Claude Code "
121
+ "may assume a 1M window behind a gateway and compact long after a "
122
+ "local model has overflowed. Set it to your smallest local context "
123
+ "window (coderouter vscode-init --max-context-tokens N), and/or use "
124
+ "context_budget_action: reject in the profile.",
125
+ )
126
+ )
127
+
128
+ if env.get("CLAUDE_CODE_AUTO_MODE_SERVER") == "0":
129
+ out.append(ClaudeCodeFinding("ok", "CLAUDE_CODE_AUTO_MODE_SERVER=0"))
130
+ else:
131
+ out.append(
132
+ ClaudeCodeFinding(
133
+ "warn",
134
+ "CLAUDE_CODE_AUTO_MODE_SERVER is not 0: on a gateway, auto mode "
135
+ "defaults to a server-side classifier that a local backend does not "
136
+ "provide. Set CLAUDE_CODE_AUTO_MODE_SERVER=0.",
137
+ )
138
+ )
139
+ return out
140
+
141
+
142
+ def _claude_version(run: Callable[[list[str]], str] | None = None) -> tuple[Version | None, str | None]:
143
+ if run is None:
144
+ exe = shutil.which("claude")
145
+ if exe is None:
146
+ return None, "`claude` not found on PATH (version checks skipped)"
147
+
148
+ def run(cmd: list[str]) -> str:
149
+ proc = subprocess.run(cmd, capture_output=True, text=True, timeout=15, check=False)
150
+ return proc.stdout + proc.stderr
151
+
152
+ cmd = [exe, "--version"]
153
+ else:
154
+ cmd = ["claude", "--version"]
155
+ try:
156
+ text = run(cmd)
157
+ except (OSError, subprocess.SubprocessError) as exc:
158
+ return None, f"could not run `claude --version`: {exc}"
159
+ version = parse_version(text)
160
+ if version is None:
161
+ return None, f"could not parse `claude --version` output: {text.strip()[:80]!r}"
162
+ return version, None
163
+
164
+
165
+ def run_check(
166
+ env: Mapping[str, str] | None = None,
167
+ run: Callable[[list[str]], str] | None = None,
168
+ ) -> ClaudeCodeReport:
169
+ version, note = _claude_version(run)
170
+ findings = check(version, os.environ if env is None else env)
171
+ if note:
172
+ findings.insert(0, ClaudeCodeFinding("info", note))
173
+ return ClaudeCodeReport(version=version, findings=findings)
174
+
175
+
176
+ def format_report(report: ClaudeCodeReport) -> str:
177
+ lines = ["Claude Code (behind CodeRouter)"]
178
+ for f in report.findings:
179
+ mark = {"ok": "OK ", "warn": "WARN", "info": "INFO"}.get(f.level, f.level.upper())
180
+ lines.append(f" [{mark}] {f.message}")
181
+ return "\n".join(lines)
@@ -87,6 +87,25 @@ if TYPE_CHECKING:
87
87
  # ---------------------------------------------------------------------------
88
88
 
89
89
 
90
+ class ContextBudgetRejectError(Exception):
91
+ """v2.16.0: ``context_budget_action: reject`` refused an oversized request.
92
+
93
+ The ingress answers with Anthropic's overflow error (HTTP 400,
94
+ ``prompt is too long: ...``) so the client compacts the conversation
95
+ itself. Carries the numbers for the message and the log line.
96
+ """
97
+
98
+ def __init__(self, *, estimated_tokens: int, max_context_tokens: int, profile: str) -> None:
99
+ self.estimated_tokens = estimated_tokens
100
+ self.max_context_tokens = max_context_tokens
101
+ self.profile = profile
102
+ super().__init__(
103
+ f"prompt is too long: ~{estimated_tokens} tokens > "
104
+ f"{max_context_tokens} maximum (CodeRouter profile {profile!r}, "
105
+ "estimated)"
106
+ )
107
+
108
+
90
109
  @dataclass(frozen=True, slots=True)
91
110
  class ContextBudgetEstimate:
92
111
  """Result of a context-budget estimation check."""