coderouter-cli 2.10.0__tar.gz → 2.11.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.
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/CHANGELOG.md +292 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/PKG-INFO +2 -1
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/README.en.md +3 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/README.md +1 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/adapters/anthropic_native.py +23 -5
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/cli.py +49 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/config/loader.py +65 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/config/schemas.py +228 -12
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/doctor.py +98 -19
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/doctor_apply.py +72 -3
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/gguf_introspect.py +132 -25
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/guards/drift_detection.py +13 -1
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/ingress/launcher_routes.py +289 -22
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/launcher_devices.py +200 -4
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/launcher_speculative.py +5 -1
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/launcher_swap.py +11 -2
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/routing/fallback.py +14 -2
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/translation/anthropic.py +14 -1
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/translation/convert.py +7 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/translation/tool_repair.py +193 -76
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/vscode_init.py +4 -2
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/README.md +2 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/launcher-quickstart.en.md +2 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/launcher-quickstart.md +2 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/launcher.en.md +114 -1
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/launcher.md +114 -1
- coderouter_cli-2.11.1/docs/designs/launcher-multi-build.md +479 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/examples/README.md +1 -1
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/examples/providers.llamacpp-vllm.yaml +127 -10
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/pyproject.toml +1 -1
- coderouter_cli-2.11.1/tests/test_config_cwd_warning.py +268 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_doctor.py +278 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_doctor_apply.py +281 -0
- coderouter_cli-2.11.1/tests/test_examples_yaml_roundtrip.py +150 -0
- coderouter_cli-2.11.1/tests/test_gguf_introspect.py +292 -0
- coderouter_cli-2.11.1/tests/test_import_hygiene.py +118 -0
- coderouter_cli-2.11.1/tests/test_launcher_backend_variants.py +429 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_launcher_devices_routes.py +23 -0
- coderouter_cli-2.11.1/tests/test_launcher_variant_config.py +387 -0
- coderouter_cli-2.11.1/tests/test_launcher_variant_gui.py +222 -0
- coderouter_cli-2.11.1/tests/test_launcher_variant_routes.py +411 -0
- coderouter_cli-2.11.1/tests/test_launcher_variant_sweep.py +270 -0
- coderouter_cli-2.11.1/tests/test_stop_reason_forward_compat.py +179 -0
- coderouter_cli-2.11.1/tests/test_tool_repair_differential.py +326 -0
- coderouter_cli-2.11.1/tests/test_tool_repair_scanners.py +169 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_vscode_init.py +0 -3
- coderouter_cli-2.10.0/tests/test_gguf_introspect.py +0 -164
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/.gitignore +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/LICENSE +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/__init__.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/__main__.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/adapters/__init__.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/adapters/base.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/adapters/openai_compat.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/adapters/registry.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/cli_stats.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/config/__init__.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/config/capability_registry.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/config/env_file.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/cost.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/data/__init__.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/data/model-capabilities.yaml +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/env_security.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/errors.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/guards/__init__.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/guards/_fingerprint.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/guards/backend_health.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/guards/context_budget.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/guards/continuous_probe.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/guards/drift_actions.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/guards/memory_budget.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/guards/memory_pressure.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/guards/self_healing.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/guards/tool_loop.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/hardware.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/ingress/__init__.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/ingress/anthropic_routes.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/ingress/app.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/ingress/dashboard_routes.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/ingress/metrics_routes.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/ingress/openai_routes.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/language_tax.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/logging.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/metrics/__init__.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/metrics/collector.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/metrics/prometheus.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/output_filters.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/plugins/__init__.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/plugins/base.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/plugins/loader.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/plugins/registry.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/routing/__init__.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/routing/adaptive.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/routing/auto_router.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/routing/budget.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/routing/capability.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/state/__init__.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/state/audit_log.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/state/replay.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/state/request_log.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/state/store.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/state/suggest_rules.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/token_estimation.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/token_estimation_accurate.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/coderouter/translation/__init__.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/README.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/assets/dashboard-demo.png +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/claude-code-llamacpp-vllm.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/claude-code-llamacpp-vllm.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/external-agents.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/external-agents.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/gguf_dl.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/gguf_dl.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/hf-ollama-models.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/hf-ollama-models.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/install-backends.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/install-backends.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/llamacpp-direct.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/llamacpp-direct.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/lmstudio-direct.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/lmstudio-direct.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/backends/verify-ollama-0.23.1.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/concepts/architecture.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/concepts/architecture.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/concepts/context-budget.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/concepts/context-budget.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/concepts/continuous-probing.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/concepts/continuous-probing.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/concepts/drift-detection.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/concepts/drift-detection.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/concepts/partial-stitch.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/concepts/partial-stitch.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/designs/agent-cli-plugin-extraction.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/designs/external-agents-adapter.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/designs/launcher-model-swap.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/designs/orchestration-companion.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/designs/v1.5-dashboard-mockup.html +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/designs/v1.6-auto-router-verification.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/designs/v1.6-auto-router.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/guides/free-tier-guide.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/guides/free-tier-guide.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/guides/language-tax.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/guides/language-tax.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/guides/remote-access.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/guides/remote-access.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/guides/security.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/guides/security.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/guides/subagent-routing.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/guides/subagent-routing.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/guides/troubleshooting.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/guides/troubleshooting.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/guides/usage-guide.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/guides/usage-guide.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/guides/vscode.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/low-memory-integration.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/low-memory-integration.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/openrouter-roster/CHANGES.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/openrouter-roster/README.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/openrouter-roster/latest.json +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/retrospectives/v0.4.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/retrospectives/v0.5-verify.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/retrospectives/v0.5.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/retrospectives/v0.6.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/retrospectives/v0.7.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/retrospectives/v1.0-verify.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/retrospectives/v1.0.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/start/quickstart.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/start/quickstart.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/start/when-do-i-need-coderouter.en.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/docs/start/when-do-i-need-coderouter.md +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/examples/.env.example +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/examples/providers-agent-cli.yaml +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/examples/providers-multiagent.yaml +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/examples/providers.context-budget-test.yaml +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/examples/providers.nvidia-nim.yaml +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/examples/providers.ollama-auto-custom.yaml +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/examples/providers.ollama-auto.yaml +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/examples/providers.ollama-free-chain.yaml +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/examples/providers.raspberrypi.yaml +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/examples/providers.swap.yaml +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/examples/providers.yaml +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/scripts/demo_traffic.sh +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/scripts/openrouter_roster_diff.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/scripts/smoke_v2_2.sh +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/scripts/verify-providers.yaml +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/scripts/verify_ollama_0_23.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/scripts/verify_v0_5.sh +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/scripts/verify_v1_0.sh +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/__init__.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/conftest.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_adapter_anthropic.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_agent_cli.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_audit_log.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_auto_router.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_auto_router_cjk.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_backend_health.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_budget.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_capability.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_capability_degraded_payload.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_capability_registry.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_capability_registry_cache_control.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_capability_tool_choice.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_claude_code_suitability.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_cli.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_cli_bind_warning.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_cli_stats.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_config.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_config_shim_actions.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_context_budget.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_continuous_probe.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_dashboard_endpoint.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_doctor_cache_probe.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_drift_actions.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_drift_detection.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_drift_detection_integration.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_env_file.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_env_security.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_errors.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_examples_yaml.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fallback.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fallback_anthropic.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fallback_cache_control.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fallback_cache_observed.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fallback_cache_strip.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fallback_empty_response.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fallback_misconfig_warn.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fallback_paid_gate.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fallback_thinking.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fallback_tool_choice_emulate.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fix_h1_prometheus_drift.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fix_h2_h6_translation.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fix_h3_client_reuse.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fix_h4_h5_guards.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fix_h7_probe_restore.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fix_h8_launcher_auth.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fix_mA_routing.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fix_mB_adapters.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fix_mC_convert.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fix_mD_logging_metrics.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fix_mE_config_validation.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_fix_mF_ingress.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_guards_tool_loop.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_hardware.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_ingress_anthropic.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_ingress_count_tokens.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_ingress_profile.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_language_tax.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_language_tax_integration.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_launcher_config_bench.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_launcher_devices.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_launcher_devices_detect.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_launcher_gui_devices.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_launcher_gui_readiness.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_launcher_mtp.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_launcher_mtp_fallback.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_launcher_provider_sync.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_launcher_readiness_restart.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_launcher_swap.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_launcher_swap_review.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_memory_budget.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_memory_pressure.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_metrics_cache.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_metrics_collector.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_metrics_cost.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_metrics_endpoint.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_metrics_jsonl.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_metrics_prometheus.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_metrics_prometheus_cache.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_models_passthrough.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_openai_compat.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_openrouter_roster_diff.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_output_filters.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_output_filters_adapters.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_partial_stitch.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_plugin_adapter.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_plugins_integration.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_plugins_loader.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_plugins_registry.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_reasoning_strip.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_repair_byte_fallback.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_request_log.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_role_normalization.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_routing_adaptive.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_self_healing.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_setup_sh.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_state_store.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_token_estimation.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_token_estimation_accurate.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_tokens_saved_metric.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_tool_repair.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_tool_repair_lenient.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_tool_repair_r4.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_toolrepair_bench.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_translation_anthropic.py +0 -0
- {coderouter_cli-2.10.0 → coderouter_cli-2.11.1}/tests/test_translation_reverse.py +0 -0
|
@@ -9,6 +9,298 @@ are kept verbatim where the Japanese text itself is the subject).
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## [v2.11.1] — 2026-08-04 (second-round review: correctness & hardening)
|
|
13
|
+
|
|
14
|
+
Seven fixes from a full-source review of the 36k-line package, plus two
|
|
15
|
+
deprecation warnings that pre-announce the behaviour changes queued for
|
|
16
|
+
v2.12.0. **No breaking changes**: no config-schema field was removed or
|
|
17
|
+
narrowed, no CLI flag changed meaning, and every default is unchanged.
|
|
18
|
+
The two new warnings do not alter behaviour — they only tell you which
|
|
19
|
+
of your settings will need editing before v2.12.0.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **Brace scanning in `tool_repair` was quadratic and blocked the event
|
|
24
|
+
loop for minutes.** `_find_balanced_json_objects` and
|
|
25
|
+
`_find_candidate_object_spans` restarted a full scan to end-of-text at
|
|
26
|
+
every `{`, so an assistant reply carrying unclosed braces — the
|
|
27
|
+
ordinary shape of a deep nested JSON truncated by `max_tokens` — cost
|
|
28
|
+
O(k·n). Measured on the old code: 3.997 s for 8 KiB of `{`, 22.199 s
|
|
29
|
+
for a 48 KiB reply, 139.019 s for 48 KiB of bare `{`. Both scanners
|
|
30
|
+
are called synchronously from `to_anthropic_response`, which
|
|
31
|
+
`FallbackEngine._generate_anthropic_impl` / `._stream_anthropic_impl`
|
|
32
|
+
invoke from inside `async` handlers, so the whole server stalled for
|
|
33
|
+
the duration — every concurrent request, not just the offending one.
|
|
34
|
+
Replaced with a single right-to-left sweep that builds a depth-0
|
|
35
|
+
closing-brace table (`table[j]` = the first depth-0 `}` reached when
|
|
36
|
+
scanning from offset `j` outside any string); the answer for an
|
|
37
|
+
opening brace at `q` is `table[q+1]`, which is by definition the
|
|
38
|
+
fresh-restart semantics the old code had. Same inputs now take
|
|
39
|
+
0.004 s / 0.016 s / 0.024 s. The strict (`"` only) and lenient (`'`
|
|
40
|
+
too) quoting rules live in separate tables and cannot bleed into each
|
|
41
|
+
other. Scan output is unchanged — see Tests. Added
|
|
42
|
+
`_MAX_BARE_SCAN_CHARS` (256 KiB); above it the bare-JSON pass is
|
|
43
|
+
skipped with a `tool-repair-input-too-large` warning while the fenced
|
|
44
|
+
/ XML / R4c passes still run. The two `to_anthropic_response` call
|
|
45
|
+
sites moved to `asyncio.to_thread`; the function itself stays
|
|
46
|
+
synchronous (`tool_repair.py`, `routing/fallback.py`).
|
|
47
|
+
- **`doctor --apply` dropped `output_filters` a user had configured.**
|
|
48
|
+
The probe emitted only the filters that were *missing*, but
|
|
49
|
+
`deep_merge_dicts` replaces lists wholesale, so applying
|
|
50
|
+
`output_filters: [strip_thinking]` to a provider already carrying
|
|
51
|
+
`[strip_stop_markers, my_filter]` left it with `[strip_thinking]`
|
|
52
|
+
alone. `strip_tool_call_xml` and `repair_byte_fallback` were dropped
|
|
53
|
+
100% of the time, since no probe ever recommends them. The patch now
|
|
54
|
+
carries the union — existing chain first, missing filters appended.
|
|
55
|
+
Order is load-bearing (`OutputFilterChain.feed` applies left to
|
|
56
|
+
right), so the union never sorts; `deep_merge_dicts` semantics are
|
|
57
|
+
untouched (`doctor.py`).
|
|
58
|
+
- **`doctor --check-model` could write `capabilities: tools: false` for
|
|
59
|
+
a model that supports tools.** The tool-call probe judged on
|
|
60
|
+
`content[:200]` — a display-oriented truncation — while the probe
|
|
61
|
+
budget is 256 tokens, or 1024 for thinking models (roughly 3–4 k
|
|
62
|
+
chars). A model that emits a preamble before its tool JSON, which is
|
|
63
|
+
exactly what thinking models do, fell through to the "nothing
|
|
64
|
+
tool-shaped at all" branch and got a `tools: false` patch that
|
|
65
|
+
`--apply` then wrote. This re-created, by a different route, the
|
|
66
|
+
false positive the v1.8.3 budget increase was meant to remove.
|
|
67
|
+
Detection now reads the full body up to `_TOOL_PROBE_SCAN_CHARS`
|
|
68
|
+
(16 KiB) and the 200-char slice is display-only; the detail line
|
|
69
|
+
reports how many chars were actually scanned (`doctor.py`).
|
|
70
|
+
- **Stray `stop_reason` values from Anthropic were treated as malformed
|
|
71
|
+
responses.** `AnthropicResponse.stop_reason` was a closed `Literal`,
|
|
72
|
+
and `ConfigDict(extra="allow")` does not widen a declared field, so
|
|
73
|
+
`pause_turn` (server-tool turns), `refusal` and
|
|
74
|
+
`model_context_window_exceeded` raised `ValidationError`.
|
|
75
|
+
`AnthropicAdapter` converts that to a retryable `AdapterError`, so a
|
|
76
|
+
perfectly good — and already billed — reply was discarded and the
|
|
77
|
+
chain fell through to the next provider, or to
|
|
78
|
+
`NoProvidersAvailableError` if it was the last. Streaming was
|
|
79
|
+
unaffected (`AnthropicStreamEvent` keeps its payload as a plain
|
|
80
|
+
dict), so only non-streaming requests were hit. The field is now
|
|
81
|
+
`str | None`, deliberately open: Anthropic has added stop reasons
|
|
82
|
+
three times and a closed `Literal` reproduces this bug on the next
|
|
83
|
+
one. `_REVERSE_FINISH_REASON_MAP` gained `pause_turn` → `stop`,
|
|
84
|
+
`refusal` → `content_filter`, `model_context_window_exceeded` →
|
|
85
|
+
`length`. While here, `drift_detection._EXPECTED_STOP` gained
|
|
86
|
+
`stop_sequence` — it was counting a documented, ordinary stop reason
|
|
87
|
+
as an anomaly (`translation/anthropic.py`, `translation/convert.py`,
|
|
88
|
+
`guards/drift_detection.py`).
|
|
89
|
+
- **`import coderouter.translation` failed on its own.** The package
|
|
90
|
+
reached `adapters` → `registry` → `anthropic_native`, which imported
|
|
91
|
+
`translation.convert` back at module scope. `pytest tests/` passed
|
|
92
|
+
only because alphabetical collection imports `test_adapter_anthropic`
|
|
93
|
+
first and primes `coderouter.adapters`; running
|
|
94
|
+
`pytest tests/test_tool_repair.py` on its own was a collection error.
|
|
95
|
+
The three `convert` symbols moved into the `generate()` / `stream()`
|
|
96
|
+
bodies that use them (`adapters/anthropic_native.py`).
|
|
97
|
+
|
|
98
|
+
### Security
|
|
99
|
+
|
|
100
|
+
- **`doctor --apply` silently rewrote `providers.yaml` while reporting
|
|
101
|
+
"already up to date", then destroyed the original in `.bak`.** The
|
|
102
|
+
write was gated on `diff_text` — "does a ruamel re-dump differ from
|
|
103
|
+
what is on disk" — not on whether a patch changed anything. ruamel
|
|
104
|
+
re-flows quoted scalars at 80 columns and renders an explicit `null`
|
|
105
|
+
as an empty scalar, so a fully idempotent re-run still produced a
|
|
106
|
+
diff and still wrote. The CLI meanwhile took the `is_no_op` early
|
|
107
|
+
return and printed `All N patch(es) already applied`, showing neither
|
|
108
|
+
the diff nor a backup line. The *next* `--apply` then copied that
|
|
109
|
+
already-reformatted text over `providers.yaml.bak`, which is
|
|
110
|
+
deliberately un-timestamped and single-slot — so the only copy of the
|
|
111
|
+
operator's original was gone. The shipped `examples/providers.yaml`
|
|
112
|
+
is affected (five `api_key_env: null` lines), as is
|
|
113
|
+
`examples/providers.llamacpp-vllm.yaml` (two `binary: null`). The
|
|
114
|
+
write is now gated on the merge result per target file; a target
|
|
115
|
+
whose merges were all no-ops reports an empty diff and parks the
|
|
116
|
+
cosmetic delta in `ApplyResult.reformat_only`, surfaced under
|
|
117
|
+
`--dry-run` only. The dumper also sets `width = 4096`, as defence in
|
|
118
|
+
depth. Every exit path of the apply command — including both
|
|
119
|
+
exception returns — now states how many files were written.
|
|
120
|
+
**If you ran `doctor --apply` on v2.11.0 or earlier, compare
|
|
121
|
+
`providers.yaml` against `providers.yaml.bak` once.** The
|
|
122
|
+
reformatting is semantically equivalent YAML, but the first `--apply`
|
|
123
|
+
after upgrading will show the un-wrapping alongside your intended
|
|
124
|
+
change; that is expected and happens once (`doctor_apply.py`,
|
|
125
|
+
`cli.py`).
|
|
126
|
+
- **A malformed GGUF file could hang or crash the launcher, including
|
|
127
|
+
from the HTTP route.** `_skip_value` recursed once per array nesting
|
|
128
|
+
level at 12 bytes per level, so a ~12 KB file raised `RecursionError`
|
|
129
|
+
— which is not `GGUFParseError`, so it went straight through
|
|
130
|
+
`try_read_gguf_metadata`'s handler. Separately, array elements were
|
|
131
|
+
skipped with `fh.seek(size, 1)` per element with no end-of-file
|
|
132
|
+
check: a 49-byte file declaring `count = 1<<24` spun 16.7 M times,
|
|
133
|
+
took 7.94 s, and returned a `GGUFInfo` rather than raising. Both are
|
|
134
|
+
reachable from `POST /api/launcher/start`, and
|
|
135
|
+
`launcher_speculative.find_draft_companion` reads *every* `.gguf` in
|
|
136
|
+
the configured model directories, so one bad file poisons the scan.
|
|
137
|
+
`_skip_value` is now iterative over an explicit work stack —
|
|
138
|
+
`RecursionError` is structurally unreachable — with
|
|
139
|
+
`_MAX_ARRAY_DEPTH = 8` retained as an independent bound (real GGUF
|
|
140
|
+
arrays are depth 1; llama.cpp's writer emits nothing deeper). Every
|
|
141
|
+
skip validates against the file size before seeking, and fixed-width
|
|
142
|
+
scalar arrays are skipped in one bulk seek: a 150 k-element `u32`
|
|
143
|
+
array went from 0.0624 s to 0.000102 s, and the 49-byte case from
|
|
144
|
+
7.94 s to 0.000043 s with a `GGUFParseError`. `p.open()` is now
|
|
145
|
+
wrapped for the stat/open TOCTOU, and `try_read_gguf_metadata`
|
|
146
|
+
catches `OSError` / `RecursionError` / `MemoryError` as well
|
|
147
|
+
(`gguf_introspect.py`).
|
|
148
|
+
|
|
149
|
+
### Changed
|
|
150
|
+
|
|
151
|
+
- **Deprecation warning: `providers.yaml` discovered in the current
|
|
152
|
+
directory.** When neither `--config` nor `CODEROUTER_CONFIG` resolves
|
|
153
|
+
and the file actually loaded came from `./providers.yaml`, a
|
|
154
|
+
`cwd-config-loaded` warning is logged once per process naming the
|
|
155
|
+
path. In v2.12.0 this search step becomes opt-in behind
|
|
156
|
+
`CODEROUTER_ALLOW_CWD_CONFIG`, because starting the server in an
|
|
157
|
+
untrusted directory currently means loading that directory's
|
|
158
|
+
execution-bearing settings. Behaviour is unchanged in this release
|
|
159
|
+
(`config/loader.py`).
|
|
160
|
+
- **Deprecation warning: shell syntax in `restart_command`.** A value
|
|
161
|
+
containing `&&`, `||`, `|`, `;`, `>`, `<`, `$(` or a backtick, or
|
|
162
|
+
starting with `~/` or `FOO=bar`, now logs
|
|
163
|
+
`restart-command-shell-syntax` at config load. In v2.12.0 the command
|
|
164
|
+
will be `shlex.split` and run with `shell=False`, so those forms
|
|
165
|
+
break — and `pkill x && x` breaks *silently*, with `&&` passed to
|
|
166
|
+
`pkill` as an argument. Wrap such commands in a script, or use an
|
|
167
|
+
explicit `/bin/sh -c "..."`. This is a warning only; nothing is
|
|
168
|
+
rejected and execution is unchanged (`config/schemas.py`).
|
|
169
|
+
|
|
170
|
+
### Tests
|
|
171
|
+
|
|
172
|
+
- 6 new files, 184 new cases (`test_tool_repair_differential.py`,
|
|
173
|
+
`test_tool_repair_scanners.py`, `test_examples_yaml_roundtrip.py`,
|
|
174
|
+
`test_import_hygiene.py`, `test_stop_reason_forward_compat.py`,
|
|
175
|
+
`test_config_cwd_warning.py`, plus additions to `test_doctor.py`,
|
|
176
|
+
`test_doctor_apply.py` and `test_gguf_introspect.py`). Suite: 2307
|
|
177
|
+
passed, 1 skipped, ruff clean.
|
|
178
|
+
- The scanner rewrite is pinned by a differential test that carries the
|
|
179
|
+
old quadratic algorithm as a reference implementation and compares
|
|
180
|
+
both scanners across five random alphabets × 2500 inputs each, plus
|
|
181
|
+
the full `benchmarks/tool-repair` corpus end to end. A naive global
|
|
182
|
+
single-pass rewrite — the obvious way to linearise this, and the way
|
|
183
|
+
that looks correct — diverges on 6870 of 12500 strict cases and 6977
|
|
184
|
+
lenient, because an apostrophe in ordinary prose (`I'll`, `don't`)
|
|
185
|
+
opens a single-quoted string that swallows every following brace. The
|
|
186
|
+
differential test was verified to catch exactly that.
|
|
187
|
+
- Each fix above has a regression test confirmed to fail against the
|
|
188
|
+
pre-fix tree, including
|
|
189
|
+
`test_cli_no_op_message_states_nothing_written` (the "up to date"
|
|
190
|
+
message printed while writing),
|
|
191
|
+
`test_no_op_apply_leaves_example_byte_identical` (the shipped
|
|
192
|
+
examples), `test_array_count_past_eof_raises_immediately` (raises,
|
|
193
|
+
and inside 0.5 s) and `test_lenient_survives_prose_apostrophe`.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## [v2.11.0] — 2026-07-28 (launcher backend variants)
|
|
198
|
+
|
|
199
|
+
### Added
|
|
200
|
+
|
|
201
|
+
- **Backend variants — pick which llama.cpp build to launch.**
|
|
202
|
+
`launcher.backends` now accepts keys of the form `<base>-<variant>`
|
|
203
|
+
(`llama.cpp-cuda`, `llama.cpp-vulkan`, `llama.cpp-rocm`), each naming
|
|
204
|
+
an additional build of the same backend with its own `binary` path.
|
|
205
|
+
Declared variants appear as extra entries in the Launcher's backend
|
|
206
|
+
select (GUI and Web) marked `⚙`, and each one gets its own
|
|
207
|
+
`--list-devices` probe, its own `option_profiles`, and can be pinned
|
|
208
|
+
per model in `launcher.swap`. Motivation: on a machine with mixed
|
|
209
|
+
GPUs the visible devices differ per build — the CUDA build enumerates
|
|
210
|
+
`CUDA0`/`CUDA1` (RTX 5090 + 3090) while the Vulkan build also
|
|
211
|
+
enumerates `Vulkan2` (Radeon 8060S) and the ROCm build only the
|
|
212
|
+
Radeon — so the best build varies by model. Previously `binary` held
|
|
213
|
+
a single path and switching meant editing `providers.yaml` and
|
|
214
|
+
restarting. Docs: `docs/backends/launcher.md` "特化ビルドの切り替え",
|
|
215
|
+
design: `docs/designs/launcher-multi-build.md`.
|
|
216
|
+
- **`option_profiles` inheritance for variants.** A variant key
|
|
217
|
+
inherits the base backend's presets and appends its own; a preset
|
|
218
|
+
whose `name` collides replaces the inherited one *in place* (order
|
|
219
|
+
stays stable, no duplicate at the tail). Shared presets no longer
|
|
220
|
+
need duplicating under every variant key. Shared implementation:
|
|
221
|
+
`launcher_devices.resolve_option_profiles` (used by the Web routes,
|
|
222
|
+
the Tk GUI and `launcher_swap`).
|
|
223
|
+
- **Cross-build bench sweep.** With two or more llama.cpp builds
|
|
224
|
+
declared, the sweep panel shows a "⚙ ビルド横断" button that probes
|
|
225
|
+
every build and generates configurations labelled
|
|
226
|
+
`cuda / CUDA0 単体`, `vulkan / Vulkan2 単体`, … Running the sweep
|
|
227
|
+
launches the same model on each build in turn, so one sweep answers
|
|
228
|
+
"which build is fastest for this model". `SweepStep.backend` /
|
|
229
|
+
`SweepConfigItem.backend` carry the per-step build; `None` keeps the
|
|
230
|
+
previous plan-wide behaviour. New
|
|
231
|
+
`launcher_devices.build_cross_variant_sweep_configs`. Configurations
|
|
232
|
+
never mix devices across builds — one process runs one executable.
|
|
233
|
+
- **Device-id namespace guard.** Device ids are build-specific
|
|
234
|
+
(`CUDA0` and `Vulkan0` are not the same GPU). Switching backend in
|
|
235
|
+
either launcher now clears the device selection, and
|
|
236
|
+
`POST /api/launcher/start` / `sweep/start` reject ids that do not
|
|
237
|
+
exist in the chosen build with a 400 instead of letting
|
|
238
|
+
`--device CUDA0` reach a Vulkan build and fail at startup. Skipped
|
|
239
|
+
when `--list-devices` itself fails (best-effort, per the
|
|
240
|
+
`hardware.py` invariant). New `launcher_devices.foreign_device_ids`,
|
|
241
|
+
which compares the backend *prefix* (`backend_of`) rather than the
|
|
242
|
+
exact id — the mismatch worth catching is the wrong build, not a
|
|
243
|
+
number gap, and prefix matching stays correct if a future
|
|
244
|
+
`--list-devices` format change makes `parse_list_devices` miss a line.
|
|
245
|
+
|
|
246
|
+
### Changed
|
|
247
|
+
|
|
248
|
+
- **BREAKING: `launcher.backends` keys are now validated.** A key must
|
|
249
|
+
be `llama.cpp` / `vllm` / `mlx` or one of those with a
|
|
250
|
+
`-<variant>` suffix (variant matching `[a-z0-9][a-z0-9._-]*`).
|
|
251
|
+
Previously any key was accepted and silently ignored, because the
|
|
252
|
+
backend list was a hard-coded set of three; a typo such as
|
|
253
|
+
`llamacpp:` therefore had no effect and no warning. It is now a
|
|
254
|
+
config-load error. Check key spelling if startup fails after
|
|
255
|
+
upgrading.
|
|
256
|
+
- **A backend variant must set `binary`.** Allowing it to be omitted
|
|
257
|
+
would fall back to the base default on PATH, meaning an operator who
|
|
258
|
+
selected `llama.cpp-cuda` could silently get the plain build — the
|
|
259
|
+
hardest failure of this feature to notice. Rejected at config load.
|
|
260
|
+
Base keys keep `binary` optional (PATH resolution), unchanged.
|
|
261
|
+
- **The Launcher's backend list is now config-derived.** Both launchers
|
|
262
|
+
build the select from the three base backends plus whatever variants
|
|
263
|
+
`launcher.backends` declares, instead of a hard-coded list. Declaring
|
|
264
|
+
no variants yields exactly the previous three entries, so specialized
|
|
265
|
+
builds stay invisible to operators who have not opted in.
|
|
266
|
+
- `SwapModelSpec.backend` relaxed from
|
|
267
|
+
`Literal["llama.cpp","vllm","mlx"]` to a validated `str` so it can
|
|
268
|
+
name a variant; the three previous values still validate unchanged.
|
|
269
|
+
When a variant is named, config load verifies it is declared in
|
|
270
|
+
`launcher.backends`.
|
|
271
|
+
- `GET /api/launcher/backends` entries gained `base` and `variant`
|
|
272
|
+
keys. All pre-existing keys (`resolved` / `configured` / `default` /
|
|
273
|
+
`is_custom` / `found`) are unchanged.
|
|
274
|
+
|
|
275
|
+
### Fixed
|
|
276
|
+
|
|
277
|
+
- **Backend-name branches are normalized through a single helper.**
|
|
278
|
+
Backend name drove behaviour in ten places, several of which would
|
|
279
|
+
have failed *open* for a variant name rather than raising. Two
|
|
280
|
+
mattered: `_MODEL_FLAGS.get(backend, frozenset())` would have
|
|
281
|
+
returned an empty set and **disabled the H8 model-override guard**
|
|
282
|
+
(letting `options` / `extra_args` re-specify `-m` and load an
|
|
283
|
+
arbitrary model), and `_backend_ready`'s
|
|
284
|
+
`backend in ("llama.cpp","vllm")` would have degraded readiness to a
|
|
285
|
+
bare TCP connect — re-introducing the bug readiness gating was added
|
|
286
|
+
to fix (provider registered before the model finished loading). All
|
|
287
|
+
branches now go through `launcher_devices.base_backend()`, in
|
|
288
|
+
`launcher_routes.py`, `launcher_speculative.py`, `launcher_gui.py`
|
|
289
|
+
and the inline SPA JS. `_assert_no_model_override` additionally
|
|
290
|
+
became fail-closed: an unrecognized base uses the union of every
|
|
291
|
+
banned flag set rather than an empty one.
|
|
292
|
+
|
|
293
|
+
### Tests
|
|
294
|
+
|
|
295
|
+
- 4 new files, 150 new cases (`test_launcher_backend_variants.py`,
|
|
296
|
+
`test_launcher_variant_config.py`, `test_launcher_variant_routes.py`,
|
|
297
|
+
`test_launcher_variant_sweep.py`, `test_launcher_variant_gui.py`).
|
|
298
|
+
The fail-open branches above each have a 1:1 regression test, plus
|
|
299
|
+
byte-identical-argv and unchanged-API-payload tests for configs
|
|
300
|
+
without variants. Suite: 2098 passed, ruff clean.
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
12
304
|
## [v2.10.0] — 2026-07-20 (VSCode workspace scaffolder)
|
|
13
305
|
|
|
14
306
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coderouter-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.11.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
|
|
@@ -224,6 +224,7 @@ providers:
|
|
|
224
224
|
| **ログビューア** | 各プロセスの stdout/stderr をブラウザ内でリアルタイム確認 |
|
|
225
225
|
| **provider 自動同期** (v2.7.4) | 起動したバックエンドを provider として自動登録(`launcher-llamacpp-8085` 等)。providers.yaml 無編集で `X-CodeRouter-Profile: launcher` からルーティング可能。メモリ内のみ・serve と同寿命 |
|
|
226
226
|
| **モデル名パススルー** (v2.7.4) | `model: ""` の provider は `/v1/models` が上流のロード中モデル ID(gguf 名)をそのまま返す。gguf を差し替えても config 編集不要 — 外部ベンチからモデルを識別できる |
|
|
227
|
+
| **特化ビルドの切り替え** (v2.11.0) | `llama.cpp-cuda` / `-vulkan` / `-rocm` を `backends` に登録すると、起動ごとにどのビルドの `llama-server` を使うか選べる。ビルド別にデバイス検出・option_profiles・ベンチスイープが独立(書かなければ従来どおり) |
|
|
227
228
|
|
|
228
229
|
```yaml
|
|
229
230
|
# providers.yaml に追記するだけで有効になる
|
|
@@ -69,6 +69,8 @@ ANTHROPIC_BASE_URL=http://localhost:8088 ANTHROPIC_AUTH_TOKEN=dummy claude
|
|
|
69
69
|
|
|
70
70
|
That's it. Claude Code works as usual, but your local Ollama is answering behind the scenes.
|
|
71
71
|
|
|
72
|
+
**From VSCode's integrated terminal**, avoid the env-var footgun with `coderouter vscode-init`. Run it once at your project root and it merges `terminal.integrated.env.*` into `.vscode/settings.json`, so `claude` in VSCode's terminal just works from then on. Cheat-sheet snippets for Cline / Roo Code / Continue.dev are printed at the end of the same run → [VSCode integration guide](./docs/guides/vscode.md) (JP only for now).
|
|
73
|
+
|
|
72
74
|
---
|
|
73
75
|
|
|
74
76
|
## Do you need it?
|
|
@@ -217,6 +219,7 @@ More detail → [Usage guide](./docs/guides/usage-guide.en.md) · [Architecture]
|
|
|
217
219
|
| Run for free | [Free-tier guide](./docs/guides/free-tier-guide.en.md) |
|
|
218
220
|
| Launch llama.cpp / vllm via GUI | [Launcher guide](./docs/backends/launcher.md) |
|
|
219
221
|
| Measure & avoid the language tax | [Language Tax guide](./docs/guides/language-tax.en.md) |
|
|
222
|
+
| Use from VSCode / Cline / Continue | [VSCode integration guide](./docs/guides/vscode.md) (JP) |
|
|
220
223
|
| Reach it safely from another machine | [Remote access guide](./docs/guides/remote-access.en.md) |
|
|
221
224
|
| Stuck? | [Troubleshooting](./docs/guides/troubleshooting.en.md) |
|
|
222
225
|
| Understand the design | [Architecture](./docs/concepts/architecture.md) |
|
|
@@ -179,6 +179,7 @@ providers:
|
|
|
179
179
|
| **ログビューア** | 各プロセスの stdout/stderr をブラウザ内でリアルタイム確認 |
|
|
180
180
|
| **provider 自動同期** (v2.7.4) | 起動したバックエンドを provider として自動登録(`launcher-llamacpp-8085` 等)。providers.yaml 無編集で `X-CodeRouter-Profile: launcher` からルーティング可能。メモリ内のみ・serve と同寿命 |
|
|
181
181
|
| **モデル名パススルー** (v2.7.4) | `model: ""` の provider は `/v1/models` が上流のロード中モデル ID(gguf 名)をそのまま返す。gguf を差し替えても config 編集不要 — 外部ベンチからモデルを識別できる |
|
|
182
|
+
| **特化ビルドの切り替え** (v2.11.0) | `llama.cpp-cuda` / `-vulkan` / `-rocm` を `backends` に登録すると、起動ごとにどのビルドの `llama-server` を使うか選べる。ビルド別にデバイス検出・option_profiles・ベンチスイープが独立(書かなければ従来どおり) |
|
|
182
183
|
|
|
183
184
|
```yaml
|
|
184
185
|
# providers.yaml に追記するだけで有効になる
|
|
@@ -53,11 +53,16 @@ from coderouter.translation.anthropic import (
|
|
|
53
53
|
AnthropicResponse,
|
|
54
54
|
AnthropicStreamEvent,
|
|
55
55
|
)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
)
|
|
56
|
+
|
|
57
|
+
# NOTE: `coderouter.translation.convert` is intentionally NOT imported here
|
|
58
|
+
# at module scope (see H-7). The import chain is:
|
|
59
|
+
# translation/__init__.py -> convert.py -> adapters/__init__.py
|
|
60
|
+
# -> registry.py -> anthropic_native.py -> translation.convert (again)
|
|
61
|
+
# A top-level import here closes that cycle and makes standalone imports of
|
|
62
|
+
# `coderouter.translation.*` fail with a partial-initialization ImportError
|
|
63
|
+
# unless `coderouter.adapters` happened to be imported first. The three
|
|
64
|
+
# symbols used from `convert` are imported lazily inside `generate()` /
|
|
65
|
+
# `stream()` instead, which breaks the cycle without changing behavior.
|
|
61
66
|
|
|
62
67
|
logger = get_logger(__name__)
|
|
63
68
|
|
|
@@ -210,6 +215,12 @@ class AnthropicAdapter(BaseAdapter):
|
|
|
210
215
|
``to_chat_response``. The ``coderouter_provider`` tag is preserved
|
|
211
216
|
on both sides.
|
|
212
217
|
"""
|
|
218
|
+
# Local import to avoid a circular import at module load time (H-7):
|
|
219
|
+
# convert.py transitively imports coderouter.adapters, which imports
|
|
220
|
+
# this module. Importing convert.py here (call time, not load time)
|
|
221
|
+
# keeps `coderouter.translation.*` importable standalone.
|
|
222
|
+
from coderouter.translation.convert import to_anthropic_request, to_chat_response
|
|
223
|
+
|
|
213
224
|
anth_req = to_anthropic_request(request)
|
|
214
225
|
anth_resp = await self.generate_anthropic(anth_req, overrides=overrides)
|
|
215
226
|
chat_resp = to_chat_response(anth_resp)
|
|
@@ -241,6 +252,13 @@ class AnthropicAdapter(BaseAdapter):
|
|
|
241
252
|
- Anthropic ``event: error`` → translator raises
|
|
242
253
|
AdapterError(retryable=False), same treatment as above.
|
|
243
254
|
"""
|
|
255
|
+
# Local import to avoid a circular import at module load time (H-7);
|
|
256
|
+
# see the matching comment in generate() above.
|
|
257
|
+
from coderouter.translation.convert import (
|
|
258
|
+
stream_anthropic_to_chat_chunks,
|
|
259
|
+
to_anthropic_request,
|
|
260
|
+
)
|
|
261
|
+
|
|
244
262
|
anth_req = to_anthropic_request(request)
|
|
245
263
|
events = self.stream_anthropic(anth_req, overrides=overrides)
|
|
246
264
|
async for chunk in stream_anthropic_to_chat_chunks(events, provider_name=self.name):
|
|
@@ -624,6 +624,24 @@ def _resolve_config_path(explicit: str | None) -> Path:
|
|
|
624
624
|
return candidates[-1]
|
|
625
625
|
|
|
626
626
|
|
|
627
|
+
def _write_verdict_line(*, write: bool, files_written: int) -> None:
|
|
628
|
+
"""Print the one-line "did we touch the disk?" verdict.
|
|
629
|
+
|
|
630
|
+
Called on **every** exit path of :func:`_run_apply_or_dry_run`.
|
|
631
|
+
The v1.x regression this guards against: an ``--apply`` run that
|
|
632
|
+
silently rewrote providers.yaml while the summary said "already
|
|
633
|
+
applied" and printed neither a diff nor a ``Backup:`` line. Making
|
|
634
|
+
the statement unconditional means no future early-return can go
|
|
635
|
+
quiet about a write again.
|
|
636
|
+
"""
|
|
637
|
+
if not write:
|
|
638
|
+
print(" 0 files written (dry-run).")
|
|
639
|
+
elif files_written == 0:
|
|
640
|
+
print(" 0 files written — no file contents changed.")
|
|
641
|
+
else:
|
|
642
|
+
print(f" {files_written} file(s) written.")
|
|
643
|
+
|
|
644
|
+
|
|
627
645
|
def _run_apply_or_dry_run(
|
|
628
646
|
*,
|
|
629
647
|
report: object,
|
|
@@ -655,9 +673,13 @@ def _run_apply_or_dry_run(
|
|
|
655
673
|
)
|
|
656
674
|
except MissingDependencyError as exc:
|
|
657
675
|
print(f"doctor --apply: {exc}", file=sys.stderr)
|
|
676
|
+
_write_verdict_line(write=write, files_written=0)
|
|
658
677
|
return 1
|
|
659
678
|
except DoctorApplyError as exc:
|
|
679
|
+
# Every merge happens before the first byte is written, so an
|
|
680
|
+
# abort here always leaves the disk untouched.
|
|
660
681
|
print(f"doctor --apply: {exc}", file=sys.stderr)
|
|
682
|
+
_write_verdict_line(write=write, files_written=0)
|
|
661
683
|
return 1
|
|
662
684
|
|
|
663
685
|
label = "Apply" if write else "Dry-run"
|
|
@@ -680,6 +702,8 @@ def _run_apply_or_dry_run(
|
|
|
680
702
|
f" All {result.no_op_patches} patch(es) already applied "
|
|
681
703
|
f"— providers.yaml is up to date."
|
|
682
704
|
)
|
|
705
|
+
_write_verdict_line(write=write, files_written=len(result.written_paths))
|
|
706
|
+
_print_reformat_notice(result, write=write)
|
|
683
707
|
return 0
|
|
684
708
|
|
|
685
709
|
print(
|
|
@@ -694,16 +718,41 @@ def _run_apply_or_dry_run(
|
|
|
694
718
|
print()
|
|
695
719
|
print(diff, end="" if diff.endswith("\n") else "\n")
|
|
696
720
|
|
|
721
|
+
_write_verdict_line(write=write, files_written=len(result.written_paths))
|
|
697
722
|
if write:
|
|
698
723
|
for orig, bak in result.backups.items():
|
|
699
724
|
print(f" Backup: {orig} → {bak}")
|
|
700
725
|
else:
|
|
726
|
+
_print_reformat_notice(result, write=write)
|
|
701
727
|
print()
|
|
702
728
|
print(" (dry-run — no files were modified. Re-run with --apply to write.)")
|
|
703
729
|
|
|
704
730
|
return 0
|
|
705
731
|
|
|
706
732
|
|
|
733
|
+
def _print_reformat_notice(result: object, *, write: bool) -> None:
|
|
734
|
+
"""Dry-run-only heads-up about cosmetic re-serialization deltas.
|
|
735
|
+
|
|
736
|
+
``reformat_only`` holds targets whose re-dump differs from disk
|
|
737
|
+
without any patch having changed a value — today that is explicit
|
|
738
|
+
``key: null`` scalars, which ruamel re-emits as empty scalars.
|
|
739
|
+
These bytes are never written, so the note is purely so an operator
|
|
740
|
+
comparing "the diff I expected" with "the file I have" is not
|
|
741
|
+
surprised. Suppressed under ``--apply``, where nothing about them
|
|
742
|
+
is actionable.
|
|
743
|
+
"""
|
|
744
|
+
reformat = getattr(result, "reformat_only", None) or {}
|
|
745
|
+
if write or not reformat:
|
|
746
|
+
return
|
|
747
|
+
print(
|
|
748
|
+
f" note: {len(reformat)} file(s) would re-serialize with cosmetic "
|
|
749
|
+
"differences (e.g. explicit `null` → empty scalar). These are NOT "
|
|
750
|
+
"written — --apply only rewrites files whose values actually changed."
|
|
751
|
+
)
|
|
752
|
+
for path in sorted(reformat):
|
|
753
|
+
print(f" - {path}")
|
|
754
|
+
|
|
755
|
+
|
|
707
756
|
|
|
708
757
|
|
|
709
758
|
def _run_audit(args: argparse.Namespace) -> int:
|
|
@@ -7,6 +7,17 @@ Search order (first hit wins):
|
|
|
7
7
|
4. ~/.coderouter/providers.yaml
|
|
8
8
|
|
|
9
9
|
Secrets are resolved by reading the env var named by `api_key_env`.
|
|
10
|
+
|
|
11
|
+
.. note::
|
|
12
|
+
**[Unreleased] deprecation notice (planned v2.12.0):** step 3 above
|
|
13
|
+
(implicit CWD discovery) is slated to become opt-in, gated behind
|
|
14
|
+
``CODEROUTER_ALLOW_CWD_CONFIG``. Landing that change without warning
|
|
15
|
+
first would silently break anyone currently relying on "run from the
|
|
16
|
+
directory with providers.yaml in it" with no diagnostic. Until that
|
|
17
|
+
lands, :func:`load_config` emits a one-time ``cwd-config-loaded``
|
|
18
|
+
warning whenever step 3 is the one that actually resolved — never
|
|
19
|
+
when an explicit ``--config`` / ``CODEROUTER_CONFIG`` path was used,
|
|
20
|
+
even if it happens to point at the same file.
|
|
10
21
|
"""
|
|
11
22
|
|
|
12
23
|
from __future__ import annotations
|
|
@@ -17,6 +28,14 @@ from pathlib import Path
|
|
|
17
28
|
import yaml
|
|
18
29
|
|
|
19
30
|
from coderouter.config.schemas import CodeRouterConfig
|
|
31
|
+
from coderouter.logging import get_logger
|
|
32
|
+
|
|
33
|
+
logger = get_logger(__name__)
|
|
34
|
+
|
|
35
|
+
# Guards the one-time [Unreleased]/v2.12.0 CWD-config deprecation warning
|
|
36
|
+
# (see module docstring) so a process that calls load_config() repeatedly
|
|
37
|
+
# (tests, hot-reload, multiple create_app() calls) only logs it once.
|
|
38
|
+
_cwd_config_warning_emitted = False
|
|
20
39
|
|
|
21
40
|
|
|
22
41
|
def _candidate_paths(explicit: str | os.PathLike[str] | None) -> list[Path]:
|
|
@@ -30,6 +49,51 @@ def _candidate_paths(explicit: str | os.PathLike[str] | None) -> list[Path]:
|
|
|
30
49
|
return paths
|
|
31
50
|
|
|
32
51
|
|
|
52
|
+
def _warn_if_cwd_config(
|
|
53
|
+
chosen: Path, *, explicit: str | os.PathLike[str] | None
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Emit the one-time [Unreleased]/v2.12.0 CWD-config deprecation warning.
|
|
56
|
+
|
|
57
|
+
Only fires when ``chosen`` was resolved via the *implicit* CWD-search
|
|
58
|
+
step. Compares ``chosen`` against the *literal* explicit / env
|
|
59
|
+
candidate paths (not merely whether those inputs were set) so that:
|
|
60
|
+
|
|
61
|
+
* an explicit ``--config`` (or ``CODEROUTER_CONFIG``) path that
|
|
62
|
+
happens to coincide with ``./providers.yaml`` stays quiet — an
|
|
63
|
+
explicit choice, however coincidental, is not the thing being
|
|
64
|
+
deprecated; but
|
|
65
|
+
* an explicit path/env var that was set but did NOT resolve (file
|
|
66
|
+
missing, so the search fell through to CWD) still warns, because
|
|
67
|
+
the CWD step is what actually served the config in that case.
|
|
68
|
+
"""
|
|
69
|
+
global _cwd_config_warning_emitted
|
|
70
|
+
if _cwd_config_warning_emitted:
|
|
71
|
+
return
|
|
72
|
+
cwd_path = Path.cwd() / "providers.yaml"
|
|
73
|
+
if chosen != cwd_path:
|
|
74
|
+
return
|
|
75
|
+
if explicit and Path(explicit) == chosen:
|
|
76
|
+
return
|
|
77
|
+
if (env_path := os.environ.get("CODEROUTER_CONFIG")) and Path(env_path) == chosen:
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
_cwd_config_warning_emitted = True
|
|
81
|
+
logger.warning(
|
|
82
|
+
"cwd-config-loaded",
|
|
83
|
+
extra={
|
|
84
|
+
"path": str(chosen),
|
|
85
|
+
"hint": (
|
|
86
|
+
f"loaded providers.yaml from the current working "
|
|
87
|
+
f"directory ({chosen}). Starting in v2.12.0, this "
|
|
88
|
+
"implicit CWD discovery becomes opt-in (gated behind "
|
|
89
|
+
"CODEROUTER_ALLOW_CWD_CONFIG) — pass --config or set "
|
|
90
|
+
"CODEROUTER_CONFIG to pin the file explicitly and avoid "
|
|
91
|
+
"a startup break when that lands."
|
|
92
|
+
),
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
33
97
|
def load_config(path: str | os.PathLike[str] | None = None) -> CodeRouterConfig:
|
|
34
98
|
"""Load providers.yaml + apply ALLOW_PAID env override."""
|
|
35
99
|
candidates = _candidate_paths(path)
|
|
@@ -40,6 +104,7 @@ def load_config(path: str | os.PathLike[str] | None = None) -> CodeRouterConfig:
|
|
|
40
104
|
f"providers.yaml not found. Searched:\n {searched}\n"
|
|
41
105
|
f"Hint: copy examples/providers.yaml to ~/.coderouter/providers.yaml"
|
|
42
106
|
)
|
|
107
|
+
_warn_if_cwd_config(chosen, explicit=path)
|
|
43
108
|
|
|
44
109
|
with chosen.open("r", encoding="utf-8") as f:
|
|
45
110
|
raw = yaml.safe_load(f) or {}
|