coding-proxy 0.5.1a6__tar.gz → 0.5.1a9__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.
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/PKG-INFO +1 -1
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/arch/design-patterns.md +2 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/pyproject.toml +1 -1
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/config/config.default.yaml +53 -35
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/config/schema.py +2 -1
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/config/session_policy.py +24 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/routing/executor.py +50 -1
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/routing/router.py +3 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/server/app.py +1 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/zhipu.py +42 -34
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_router_executor.py +290 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_zhipu.py +181 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/uv.lock +1 -1
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/.github/workflows/ci.yml +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/.github/workflows/coverage.yml +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/.github/workflows/release.yml +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/.gitignore +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/.pre-commit-config.yaml +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/AGENTS.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/CHANGELOG.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/CLAUDE.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/LICENSE +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/README.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/assets/dashboard-v0.4.0.png +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/assets/model-calling-v0.5.0.png +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/assets/session-v0.4.0.png +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/.agents/browser-validation.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/.agents/issue.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/.agents/knowledge-map.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/.agents/reference-specifications.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/arch/config-reference.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/arch/convert.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/arch/routing.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/arch/testing.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/arch/vendors.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/framework.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/guide/api-reference.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/guide/cli-reference.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/guide/dashboard.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/guide/monitoring.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/guide/quickstart.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/guide/vendors.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/ops/ci-cd.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/user-guide.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/docs/zh-CN/README.md +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/__main__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/auth/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/auth/providers/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/auth/providers/base.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/auth/providers/github.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/auth/providers/google.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/auth/runtime.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/auth/store.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/cli/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/cli/auth_commands.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/cli/banner.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/compat/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/compat/canonical.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/compat/session_store.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/config/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/config/auth_schema.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/config/loader.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/config/resiliency.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/config/routing.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/config/server.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/config/vendors.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/convert/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/convert/anthropic_to_gemini.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/convert/anthropic_to_openai.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/convert/gemini_sse_adapter.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/convert/gemini_to_anthropic.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/convert/openai_to_anthropic.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/convert/vendor_channels.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/logging/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/logging/db.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/logging/formatters.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/logging/stats.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/model/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/model/auth.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/model/compat.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/model/constants.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/model/pricing.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/model/token.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/model/vendor.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/native_api/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/native_api/config.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/native_api/extractors/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/native_api/extractors/anthropic.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/native_api/extractors/gemini.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/native_api/extractors/openai.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/native_api/handler.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/native_api/operation.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/native_api/routes.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/native_api/usage_registry.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/pricing.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/routing/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/routing/circuit_breaker.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/routing/error_classifier.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/routing/model_mapper.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/routing/quota_guard.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/routing/rate_limit.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/routing/retry.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/routing/session_manager.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/routing/session_policy.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/routing/tier.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/routing/usage_parser.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/routing/usage_recorder.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/server/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/server/dashboard.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/server/factory.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/server/responses.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/server/routes.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/streaming/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/streaming/anthropic_compat.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/alibaba.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/anthropic.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/antigravity.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/base.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/concurrency.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/copilot.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/copilot_models.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/copilot_token_manager.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/copilot_urls.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/doubao.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/kimi.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/minimax.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/mixins.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/native_anthropic.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/token_manager.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/src/coding/proxy/vendors/xiaomi.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/e2e/__init__.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/e2e/conftest.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/e2e/test_e2e_http.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/e2e/test_e2e_token.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/e2e/test_e2e_vendor.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_antigravity.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_app_routes.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_auto_login.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_banner.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_circuit_breaker.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_cli_usage.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_compat.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_concurrency_monitor.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_config_init.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_config_loader.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_convert_request.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_convert_response.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_convert_sse.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_copilot.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_copilot_convert_request.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_copilot_convert_response.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_copilot_models.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_copilot_urls.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_currency.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_error_classifier.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_executor_in_flight_tracking.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_logging_dual_write.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_mixins.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_model_auth.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_model_compat.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_model_constants.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_model_mapper.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_model_pricing.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_model_token.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_model_vendor.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_native_api_base_url_override.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_native_api_extractors.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_native_api_handler.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_native_api_operation.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_native_api_routes.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_native_vendors.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_parse_usage.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_parse_usage_gemini.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_pricing.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_quota_guard.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_rate_limit.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_router_chain.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_runtime_reauth.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_schema.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_session_aware.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_streaming_anthropic_compat.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_tier.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_tiers_config.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_time_range.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_token_logger.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_token_logger_native_columns.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_token_manager.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_types.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_vendor_channels.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_vendor_streaming.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_vendors.py +0 -0
- {coding_proxy-0.5.1a6 → coding_proxy-0.5.1a9}/tests/test_zhipu_concurrency.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coding-proxy
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1a9
|
|
4
4
|
Summary: A High-Availability, Transparent, and Smart Multi-Vendor Proxy for Claude Code. Support Claude Plans, GitHub Copilot, Google Antigravity, ZAI/GLM, MiniMax, Qwen, Xiaomi, Kimi, Doubao...
|
|
5
5
|
Project-URL: Source Code, https://github.com/ThreeFish-AI/coding-proxy
|
|
6
6
|
Project-URL: User Guide, https://github.com/ThreeFish-AI/coding-proxy/blob/master/docs/user-guide.md
|
|
@@ -541,6 +541,8 @@ $$
|
|
|
541
541
|
|
|
542
542
|
> **参数默认值**:参见 [配置参考 -- RetryConfig](./config-reference.md#elastic-params)
|
|
543
543
|
|
|
544
|
+
**Vendor 级应用(Zhipu 429/529 内部退避重试)**:[`vendors/zhipu.py`](../../src/coding/proxy/vendors/zhipu.py) 复用 `RetryConfig` / `calculate_delay()`,在 `send_message` / `send_message_stream` 内对 **429 Rate Limit(限流)** 与 **529 Overloaded(并发过载)** 两类服务端瞬态过载信号做就地指数退避重试(共用 `_BACKOFF_RETRY_STATUS = {429, 529}` 作单一事实源,max=5、1s→2s→4s→8s、Full Jitter,优先尊重 server `retry-after`)。耗尽重试后将状态码原样返回,交由上层 `should_trigger_failover`(§3.6 VendorTier)与 [CircuitBreaker](#circuit-breaker) 处理,从而降低 failover 频率。
|
|
545
|
+
|
|
544
546
|
---
|
|
545
547
|
|
|
546
548
|
## 3.13 Rate Limit Deadline Tracking(速率限制截止追踪)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "coding-proxy"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.1a9"
|
|
4
4
|
description = "A High-Availability, Transparent, and Smart Multi-Vendor Proxy for Claude Code. Support Claude Plans, GitHub Copilot, Google Antigravity, ZAI/GLM, MiniMax, Qwen, Xiaomi, Kimi, Doubao..."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -228,51 +228,51 @@ failover:
|
|
|
228
228
|
# === 模型映射 ===
|
|
229
229
|
model_mapping:
|
|
230
230
|
# GitHub Copilot 可用模型(通过 /api/copilot/models 诊断获取)
|
|
231
|
-
- pattern: "claude-sonnet-.*"
|
|
232
|
-
vendors: ["copilot"]
|
|
233
|
-
target: "claude-sonnet-4.6"
|
|
234
|
-
is_regex: true
|
|
235
231
|
- pattern: "claude-opus-.*"
|
|
236
232
|
vendors: ["copilot"]
|
|
237
233
|
target: "claude-opus-4.6"
|
|
238
234
|
is_regex: true
|
|
235
|
+
- pattern: "claude-sonnet-.*"
|
|
236
|
+
vendors: ["copilot"]
|
|
237
|
+
target: "claude-sonnet-4.6"
|
|
238
|
+
is_regex: true
|
|
239
239
|
- pattern: "claude-haiku-.*"
|
|
240
240
|
vendors: ["copilot"]
|
|
241
241
|
target: "claude-haiku-4.5"
|
|
242
242
|
is_regex: true
|
|
243
243
|
# Google Antigravity 模型映射
|
|
244
|
-
- pattern: "claude-sonnet-.*"
|
|
245
|
-
vendors: ["antigravity"]
|
|
246
|
-
target: "Claude Sonnet 4.6"
|
|
247
|
-
is_regex: true
|
|
248
244
|
- pattern: "claude-opus-.*"
|
|
249
245
|
vendors: ["antigravity"]
|
|
250
246
|
target: "Claude Opus 4.6 Thinking"
|
|
251
247
|
is_regex: true
|
|
248
|
+
- pattern: "claude-sonnet-.*"
|
|
249
|
+
vendors: ["antigravity"]
|
|
250
|
+
target: "Claude Sonnet 4.6"
|
|
251
|
+
is_regex: true
|
|
252
252
|
- pattern: "claude-haiku-.*"
|
|
253
253
|
vendors: ["antigravity"]
|
|
254
254
|
target: "Gemini 3 Flash"
|
|
255
255
|
is_regex: true
|
|
256
256
|
# 智谱 GLM 映射(官方原生 Anthropic 兼容端点)
|
|
257
257
|
# glm-5v-turbo 完整承载 Claude Code 满血能力(tool calling / thinking / 多模态)
|
|
258
|
-
- pattern: "claude-
|
|
258
|
+
- pattern: "claude-opus-.*"
|
|
259
259
|
vendors: ["zhipu"]
|
|
260
|
-
target: "glm-
|
|
260
|
+
target: "glm-5.2"
|
|
261
261
|
is_regex: true
|
|
262
|
-
- pattern: "claude-
|
|
262
|
+
- pattern: "claude-sonnet-.*"
|
|
263
263
|
vendors: ["zhipu"]
|
|
264
264
|
target: "glm-5.1"
|
|
265
265
|
is_regex: true
|
|
266
266
|
- pattern: "claude-haiku-.*"
|
|
267
267
|
vendors: ["zhipu"]
|
|
268
|
-
target: "glm-
|
|
268
|
+
target: "glm-5.1"
|
|
269
269
|
is_regex: true
|
|
270
270
|
# MiniMax 模型映射(原生 Anthropic 兼容端点)
|
|
271
|
-
- pattern: "claude-
|
|
271
|
+
- pattern: "claude-opus-.*"
|
|
272
272
|
vendors: ["minimax"]
|
|
273
273
|
target: "minimax-m2.7"
|
|
274
274
|
is_regex: true
|
|
275
|
-
- pattern: "claude-
|
|
275
|
+
- pattern: "claude-sonnet-.*"
|
|
276
276
|
vendors: ["minimax"]
|
|
277
277
|
target: "minimax-m2.7"
|
|
278
278
|
is_regex: true
|
|
@@ -281,11 +281,11 @@ model_mapping:
|
|
|
281
281
|
target: "minimax-m2.7"
|
|
282
282
|
is_regex: true
|
|
283
283
|
# 阿里 Qwen 模型映射(原生 Anthropic 兼容端点)
|
|
284
|
-
- pattern: "claude-
|
|
284
|
+
- pattern: "claude-opus-.*"
|
|
285
285
|
vendors: ["alibaba"]
|
|
286
286
|
target: "qwen3.6-plus"
|
|
287
287
|
is_regex: true
|
|
288
|
-
- pattern: "claude-
|
|
288
|
+
- pattern: "claude-sonnet-.*"
|
|
289
289
|
vendors: ["alibaba"]
|
|
290
290
|
target: "qwen3.6-plus"
|
|
291
291
|
is_regex: true
|
|
@@ -294,11 +294,11 @@ model_mapping:
|
|
|
294
294
|
target: "qwen3.6-plus"
|
|
295
295
|
is_regex: true
|
|
296
296
|
# 小米 MiMo 模型映射(原生 Anthropic 兼容端点)
|
|
297
|
-
- pattern: "claude-
|
|
297
|
+
- pattern: "claude-opus-.*"
|
|
298
298
|
vendors: ["xiaomi"]
|
|
299
299
|
target: "mimo-v2-pro"
|
|
300
300
|
is_regex: true
|
|
301
|
-
- pattern: "claude-
|
|
301
|
+
- pattern: "claude-sonnet-.*"
|
|
302
302
|
vendors: ["xiaomi"]
|
|
303
303
|
target: "mimo-v2-pro"
|
|
304
304
|
is_regex: true
|
|
@@ -307,11 +307,11 @@ model_mapping:
|
|
|
307
307
|
target: "mimo-v2-pro"
|
|
308
308
|
is_regex: true
|
|
309
309
|
# Kimi 模型映射(原生 Anthropic 兼容端点)
|
|
310
|
-
- pattern: "claude-
|
|
310
|
+
- pattern: "claude-opus-.*"
|
|
311
311
|
vendors: ["kimi"]
|
|
312
312
|
target: "kimi-k2.5"
|
|
313
313
|
is_regex: true
|
|
314
|
-
- pattern: "claude-
|
|
314
|
+
- pattern: "claude-sonnet-.*"
|
|
315
315
|
vendors: ["kimi"]
|
|
316
316
|
target: "kimi-k2.5"
|
|
317
317
|
is_regex: true
|
|
@@ -339,6 +339,12 @@ model_mapping:
|
|
|
339
339
|
# 未配置定价的模型在统计中 Cost 列显示 "-"
|
|
340
340
|
pricing:
|
|
341
341
|
# ── Anthropic Claude ──
|
|
342
|
+
- vendor: anthropic
|
|
343
|
+
model: claude-fable-5
|
|
344
|
+
input_cost_per_mtok: $10.0
|
|
345
|
+
output_cost_per_mtok: $50.0
|
|
346
|
+
cache_write_cost_per_mtok: $12.50
|
|
347
|
+
cache_read_cost_per_mtok: $1.00
|
|
342
348
|
- vendor: anthropic
|
|
343
349
|
model: claude-opus-4-8
|
|
344
350
|
input_cost_per_mtok: $5.0
|
|
@@ -527,20 +533,10 @@ pricing:
|
|
|
527
533
|
cache_read_cost_per_mtok: $0.05
|
|
528
534
|
# ── 智谱 GLM ──
|
|
529
535
|
- vendor: zhipu
|
|
530
|
-
model: glm-
|
|
531
|
-
input_cost_per_mtok: ¥
|
|
532
|
-
output_cost_per_mtok: ¥
|
|
533
|
-
cache_read_cost_per_mtok: ¥
|
|
534
|
-
- vendor: zhipu
|
|
535
|
-
model: glm-4.7 # 待区分长短上下文定价
|
|
536
|
-
input_cost_per_mtok: ¥2.00
|
|
537
|
-
output_cost_per_mtok: ¥8.00
|
|
538
|
-
cache_read_cost_per_mtok: ¥0.40
|
|
539
|
-
- vendor: zhipu
|
|
540
|
-
model: glm-5v-turbo # 待区分长短上下文定价
|
|
541
|
-
input_cost_per_mtok: ¥5.00
|
|
542
|
-
output_cost_per_mtok: ¥22.00
|
|
543
|
-
cache_read_cost_per_mtok: ¥1.20
|
|
536
|
+
model: glm-5.2 # 待区分长短上下文定价
|
|
537
|
+
input_cost_per_mtok: ¥6.00
|
|
538
|
+
output_cost_per_mtok: ¥24.00
|
|
539
|
+
cache_read_cost_per_mtok: ¥1.30
|
|
544
540
|
- vendor: zhipu
|
|
545
541
|
model: glm-5.1 # 待区分长短上下文定价
|
|
546
542
|
input_cost_per_mtok: ¥6.00
|
|
@@ -556,6 +552,21 @@ pricing:
|
|
|
556
552
|
input_cost_per_mtok: ¥4.00
|
|
557
553
|
output_cost_per_mtok: ¥18.00
|
|
558
554
|
cache_read_cost_per_mtok: ¥1.00
|
|
555
|
+
- vendor: zhipu
|
|
556
|
+
model: glm-5v-turbo # 待区分长短上下文定价
|
|
557
|
+
input_cost_per_mtok: ¥5.00
|
|
558
|
+
output_cost_per_mtok: ¥22.00
|
|
559
|
+
cache_read_cost_per_mtok: ¥1.20
|
|
560
|
+
- vendor: zhipu
|
|
561
|
+
model: glm-4.7 # 待区分长短上下文定价
|
|
562
|
+
input_cost_per_mtok: ¥2.00
|
|
563
|
+
output_cost_per_mtok: ¥8.00
|
|
564
|
+
cache_read_cost_per_mtok: ¥0.40
|
|
565
|
+
- vendor: zhipu
|
|
566
|
+
model: glm-4.5-air # 待区分长短上下文定价
|
|
567
|
+
input_cost_per_mtok: ¥0.80
|
|
568
|
+
output_cost_per_mtok: ¥2.00
|
|
569
|
+
cache_read_cost_per_mtok: ¥0.16
|
|
559
570
|
# ── MiniMax ──
|
|
560
571
|
- vendor: minimax
|
|
561
572
|
model: minimax-m2.7
|
|
@@ -685,4 +696,11 @@ native_api:
|
|
|
685
696
|
# tiers: ["copilot", "anthropic", "zhipu"]
|
|
686
697
|
#
|
|
687
698
|
# 未配置时(默认),所有 Session 使用全局 tiers 顺序。
|
|
688
|
-
session_policies:
|
|
699
|
+
session_policies:
|
|
700
|
+
policies: []
|
|
701
|
+
# 标题前缀 → 供应商自动绑定。
|
|
702
|
+
# 当 Session 标题以指定前缀开头时,自动将该 Session 绑定到对应供应商。
|
|
703
|
+
# 匹配规则按列表顺序求值,首次匹配生效。
|
|
704
|
+
title_vendor_bindings:
|
|
705
|
+
- prefix: "# 目标"
|
|
706
|
+
vendor: "zhipu"
|
|
@@ -44,7 +44,7 @@ from .routing import ( # noqa: F401
|
|
|
44
44
|
|
|
45
45
|
# ── 子模块 re-export ────────────────────────────────────────────
|
|
46
46
|
from .server import DatabaseConfig, LoggingConfig, ServerConfig # noqa: F401
|
|
47
|
-
from .session_policy import SessionPoliciesConfig # noqa: F401
|
|
47
|
+
from .session_policy import SessionPoliciesConfig, TitleVendorBinding # noqa: F401
|
|
48
48
|
from .vendors import ( # noqa: F401
|
|
49
49
|
AlibabaConfig,
|
|
50
50
|
AnthropicConfig,
|
|
@@ -350,4 +350,5 @@ __all__ = [
|
|
|
350
350
|
"NativeApiConfig",
|
|
351
351
|
# session policy
|
|
352
352
|
"SessionPoliciesConfig",
|
|
353
|
+
"TitleVendorBinding",
|
|
353
354
|
]
|
|
@@ -50,6 +50,22 @@ class SessionPolicy(BaseModel):
|
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
class TitleVendorBinding(BaseModel):
|
|
54
|
+
"""标题前缀 → 供应商自动绑定规则."""
|
|
55
|
+
|
|
56
|
+
prefix: str = Field(
|
|
57
|
+
min_length=1,
|
|
58
|
+
description=(
|
|
59
|
+
"标题前缀匹配模式(大小写敏感的 startswith 匹配)。"
|
|
60
|
+
"禁止空字符串——空前缀会匹配所有标题,导致全量误绑定。"
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
vendor: str = Field(
|
|
64
|
+
min_length=1,
|
|
65
|
+
description="匹配后绑定的目标供应商名称",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
53
69
|
class SessionPoliciesConfig(BaseModel):
|
|
54
70
|
"""顶层 Session 策略配置容器."""
|
|
55
71
|
|
|
@@ -57,3 +73,11 @@ class SessionPoliciesConfig(BaseModel):
|
|
|
57
73
|
default_factory=list,
|
|
58
74
|
description="Session 路由策略列表,按定义顺序求值,首次匹配生效",
|
|
59
75
|
)
|
|
76
|
+
title_vendor_bindings: list[TitleVendorBinding] = Field(
|
|
77
|
+
default_factory=list,
|
|
78
|
+
description=(
|
|
79
|
+
"标题前缀 → 供应商自动绑定规则。"
|
|
80
|
+
"当 Session 标题以指定前缀开头时,自动绑定到对应供应商。"
|
|
81
|
+
"匹配规则按列表顺序求值,首次匹配生效。"
|
|
82
|
+
),
|
|
83
|
+
)
|
|
@@ -11,10 +11,13 @@ import logging
|
|
|
11
11
|
import re
|
|
12
12
|
import time
|
|
13
13
|
from collections.abc import AsyncIterator
|
|
14
|
-
from typing import Any
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
15
15
|
|
|
16
16
|
import httpx
|
|
17
17
|
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from ..config.session_policy import TitleVendorBinding
|
|
20
|
+
|
|
18
21
|
from ..vendors.base import (
|
|
19
22
|
NoCompatibleVendorError,
|
|
20
23
|
RequestCapabilities,
|
|
@@ -610,6 +613,7 @@ class _RouteExecutor:
|
|
|
610
613
|
session_manager: RouteSessionManager,
|
|
611
614
|
reauth_coordinator: Any | None = None,
|
|
612
615
|
session_policy_resolver: SessionPolicyResolver | None = None,
|
|
616
|
+
title_vendor_bindings: list[TitleVendorBinding] | None = None,
|
|
613
617
|
) -> None:
|
|
614
618
|
self._router = router
|
|
615
619
|
self._tiers = tiers
|
|
@@ -617,6 +621,8 @@ class _RouteExecutor:
|
|
|
617
621
|
self._session_mgr = session_manager
|
|
618
622
|
self._reauth_coordinator = reauth_coordinator
|
|
619
623
|
self._policy_resolver = session_policy_resolver or SessionPolicyResolver()
|
|
624
|
+
self._title_vendor_bindings = title_vendor_bindings or []
|
|
625
|
+
self._validate_title_vendor_bindings()
|
|
620
626
|
|
|
621
627
|
# Tier 名称 → OAuth provider 名称的映射
|
|
622
628
|
self._tier_provider_map: dict[str, str] = {
|
|
@@ -624,6 +630,26 @@ class _RouteExecutor:
|
|
|
624
630
|
"antigravity": "google",
|
|
625
631
|
}
|
|
626
632
|
|
|
633
|
+
def _validate_title_vendor_bindings(self) -> None:
|
|
634
|
+
"""启动期校验标题绑定引用的 vendor 均存在,缺失则告警.
|
|
635
|
+
|
|
636
|
+
与手动绑定 API(拒绝未知 vendor)的语义对齐:此处不硬失败,
|
|
637
|
+
仅记录警告——避免单条误配置阻断整个代理启动;运行时
|
|
638
|
+
`_resolve_effective_tiers` 会静默跳过未知 vendor 回退默认顺序。
|
|
639
|
+
"""
|
|
640
|
+
if not self._title_vendor_bindings:
|
|
641
|
+
return
|
|
642
|
+
valid = {t.name for t in self._tiers}
|
|
643
|
+
for binding in self._title_vendor_bindings:
|
|
644
|
+
if binding.vendor not in valid:
|
|
645
|
+
logger.warning(
|
|
646
|
+
"title_vendor_bindings 引用了未知 vendor %r(前缀 %r);"
|
|
647
|
+
"可用 vendor: %s。该绑定将在运行时被静默跳过。",
|
|
648
|
+
binding.vendor,
|
|
649
|
+
binding.prefix,
|
|
650
|
+
sorted(valid),
|
|
651
|
+
)
|
|
652
|
+
|
|
627
653
|
# ── 公开执行入口 ──────────────────────────────────────
|
|
628
654
|
|
|
629
655
|
def _resolve_effective_tiers(self, session_key: str) -> list[VendorTier]:
|
|
@@ -650,6 +676,27 @@ class _RouteExecutor:
|
|
|
650
676
|
seen.add(tier.name)
|
|
651
677
|
return ordered
|
|
652
678
|
|
|
679
|
+
def _apply_title_based_policy(self, session_key: str, title: str) -> None:
|
|
680
|
+
"""根据 Session 标题前缀自动绑定供应商.
|
|
681
|
+
|
|
682
|
+
当标题以预配置的前缀开头时,通过 SessionPolicyResolver.upsert()
|
|
683
|
+
将该 Session 绑定到指定供应商,后续请求无需再走默认路由。
|
|
684
|
+
|
|
685
|
+
仅在新 Session 首次提取标题时调用,避免覆盖手动绑定的策略。
|
|
686
|
+
"""
|
|
687
|
+
if not title or not self._title_vendor_bindings:
|
|
688
|
+
return
|
|
689
|
+
for binding in self._title_vendor_bindings:
|
|
690
|
+
if title.startswith(binding.prefix):
|
|
691
|
+
self._policy_resolver.upsert(session_key, [binding.vendor])
|
|
692
|
+
logger.info(
|
|
693
|
+
"Session title prefix %r matched → auto-bind to %s (session=%s)",
|
|
694
|
+
binding.prefix,
|
|
695
|
+
binding.vendor,
|
|
696
|
+
session_key[:12],
|
|
697
|
+
)
|
|
698
|
+
return
|
|
699
|
+
|
|
653
700
|
def _prepare_body_for_tier(
|
|
654
701
|
self,
|
|
655
702
|
body: dict[str, Any],
|
|
@@ -748,6 +795,7 @@ class _RouteExecutor:
|
|
|
748
795
|
await self._recorder.set_session_title(
|
|
749
796
|
canonical_request.session_key, title
|
|
750
797
|
)
|
|
798
|
+
self._apply_title_based_policy(canonical_request.session_key, title)
|
|
751
799
|
else:
|
|
752
800
|
# 延迟标题补写: 若 session 尚无标题,尝试从当前请求中提取并回写。
|
|
753
801
|
title = _extract_session_title(canonical_request)
|
|
@@ -934,6 +982,7 @@ class _RouteExecutor:
|
|
|
934
982
|
await self._recorder.set_session_title(
|
|
935
983
|
canonical_request.session_key, title
|
|
936
984
|
)
|
|
985
|
+
self._apply_title_based_policy(canonical_request.session_key, title)
|
|
937
986
|
else:
|
|
938
987
|
# 延迟标题补写: 若 session 尚无标题,尝试从当前请求中提取并回写。
|
|
939
988
|
title = _extract_session_title(canonical_request)
|
|
@@ -14,6 +14,7 @@ from collections.abc import AsyncIterator
|
|
|
14
14
|
from typing import TYPE_CHECKING, Any
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
|
+
from ..config.session_policy import TitleVendorBinding
|
|
17
18
|
from ..pricing import PricingTable
|
|
18
19
|
|
|
19
20
|
from .executor import _RouteExecutor
|
|
@@ -38,6 +39,7 @@ class RequestRouter:
|
|
|
38
39
|
reauth_coordinator: Any | None = None,
|
|
39
40
|
compat_session_store: CompatSessionStore | None = None,
|
|
40
41
|
session_policy_resolver: SessionPolicyResolver | None = None,
|
|
42
|
+
title_vendor_bindings: list[TitleVendorBinding] | None = None,
|
|
41
43
|
) -> None:
|
|
42
44
|
if not tiers:
|
|
43
45
|
raise ValueError("至少需要一个供应商层级")
|
|
@@ -56,6 +58,7 @@ class RequestRouter:
|
|
|
56
58
|
session_manager=self._session_mgr,
|
|
57
59
|
reauth_coordinator=reauth_coordinator,
|
|
58
60
|
session_policy_resolver=session_policy_resolver,
|
|
61
|
+
title_vendor_bindings=title_vendor_bindings,
|
|
59
62
|
)
|
|
60
63
|
|
|
61
64
|
def set_pricing_table(self, table: PricingTable) -> None:
|
|
@@ -161,6 +161,7 @@ def create_app(config: ProxyConfig | None = None) -> FastAPI:
|
|
|
161
161
|
reauth_coordinator,
|
|
162
162
|
compat_session_store,
|
|
163
163
|
session_policy_resolver=SessionPolicyResolver(config.session_policies.policies),
|
|
164
|
+
title_vendor_bindings=config.session_policies.title_vendor_bindings,
|
|
164
165
|
)
|
|
165
166
|
|
|
166
167
|
app = FastAPI(title="coding-proxy", version=__version__, lifespan=lifespan)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""智谱 GLM 供应商 — 原生 Anthropic 兼容端点代理(兼容转换 + 429 重试).
|
|
1
|
+
"""智谱 GLM 供应商 — 原生 Anthropic 兼容端点代理(兼容转换 + 429/529 重试).
|
|
2
2
|
|
|
3
3
|
官方端点 (https://open.bigmodel.cn/api/anthropic) 支持大部分
|
|
4
4
|
Anthropic Messages API 协议,本模块做以下适配:
|
|
@@ -13,7 +13,8 @@ Anthropic Messages API 协议,本模块做以下适配:
|
|
|
13
13
|
- reasoning_effort 参数:静默忽略
|
|
14
14
|
- metadata 字段:暂不处理(待进一步诊断确认兼容性)
|
|
15
15
|
|
|
16
|
-
额外提供 429
|
|
16
|
+
额外提供 429/529 专用重试挽回机制:
|
|
17
|
+
- 429 Rate Limit(限流)与 529 Overloaded(并发过载)共用同一退避策略
|
|
17
18
|
- max_attempt = 5(1 初始 + 4 重试)
|
|
18
19
|
- 指数退避 + Full Jitter(1s → 2s → 4s → 8s)
|
|
19
20
|
- 优先尊重 server retry-after header
|
|
@@ -56,14 +57,20 @@ _RATE_LIMIT_RETRY = RetryConfig(
|
|
|
56
57
|
jitter=True,
|
|
57
58
|
)
|
|
58
59
|
|
|
60
|
+
# 触发指数退避重试的瞬态状态码(单一事实源)
|
|
61
|
+
# 429 Rate Limit(限流);529 Overloaded(并发过载)
|
|
62
|
+
# 二者均为服务端瞬态过载信号,适用同一退避挽回策略。
|
|
63
|
+
_BACKOFF_RETRY_STATUS = frozenset({429, 529})
|
|
64
|
+
|
|
59
65
|
|
|
60
66
|
class ZhipuVendor(NativeAnthropicVendor):
|
|
61
|
-
"""智谱 GLM 原生 Anthropic 兼容端点供应商(薄透传 + 429 重试挽回).
|
|
67
|
+
"""智谱 GLM 原生 Anthropic 兼容端点供应商(薄透传 + 429/529 重试挽回).
|
|
62
68
|
|
|
63
69
|
通过官方 /api/anthropic 端点转发请求,
|
|
64
70
|
仅替换模型名和认证头,其余原样透传。
|
|
65
71
|
|
|
66
|
-
429 Rate Limit
|
|
72
|
+
429 Rate Limit(限流)/ 529 Overloaded(并发过载)时自动重试
|
|
73
|
+
(指数退避),降低 failover 频率。
|
|
67
74
|
并发限流由 BaseVendor._concurrency_controller 统一管控。
|
|
68
75
|
"""
|
|
69
76
|
|
|
@@ -124,24 +131,25 @@ class ZhipuVendor(NativeAnthropicVendor):
|
|
|
124
131
|
|
|
125
132
|
return body, new_headers
|
|
126
133
|
|
|
127
|
-
# ── 非流式:429 重试
|
|
134
|
+
# ── 非流式:429/529 重试 ────────────────────────────────
|
|
128
135
|
|
|
129
136
|
async def send_message(
|
|
130
137
|
self,
|
|
131
138
|
request_body: dict[str, Any],
|
|
132
139
|
headers: dict[str, str],
|
|
133
140
|
) -> VendorResponse:
|
|
134
|
-
"""非流式请求,429 时自动重试."""
|
|
141
|
+
"""非流式请求,429/529 时自动重试."""
|
|
135
142
|
max_attempts = self._rl_retry.max_attempts
|
|
136
143
|
|
|
137
144
|
for attempt in range(max_attempts):
|
|
138
145
|
resp = await super().send_message(request_body, headers)
|
|
139
|
-
if resp.status_code
|
|
146
|
+
if resp.status_code not in _BACKOFF_RETRY_STATUS:
|
|
140
147
|
return resp
|
|
141
148
|
|
|
142
149
|
if attempt == max_attempts - 1:
|
|
143
150
|
logger.warning(
|
|
144
|
-
"Zhipu
|
|
151
|
+
"Zhipu %d overload exhausted after %d attempts",
|
|
152
|
+
resp.status_code,
|
|
145
153
|
max_attempts,
|
|
146
154
|
)
|
|
147
155
|
return resp
|
|
@@ -150,7 +158,8 @@ class ZhipuVendor(NativeAnthropicVendor):
|
|
|
150
158
|
resp.response_headers, attempt
|
|
151
159
|
)
|
|
152
160
|
logger.info(
|
|
153
|
-
"Zhipu
|
|
161
|
+
"Zhipu %d overload, retry %d/%d in %.1fms",
|
|
162
|
+
resp.status_code,
|
|
154
163
|
attempt + 1,
|
|
155
164
|
max_attempts - 1,
|
|
156
165
|
delay,
|
|
@@ -159,16 +168,16 @@ class ZhipuVendor(NativeAnthropicVendor):
|
|
|
159
168
|
|
|
160
169
|
return resp # pragma: no cover
|
|
161
170
|
|
|
162
|
-
# ── 流式:429 重试
|
|
171
|
+
# ── 流式:429/529 重试 ──────────────────────────────────
|
|
163
172
|
|
|
164
173
|
async def send_message_stream(
|
|
165
174
|
self,
|
|
166
175
|
request_body: dict[str, Any],
|
|
167
176
|
headers: dict[str, str],
|
|
168
177
|
) -> AsyncIterator[bytes]:
|
|
169
|
-
"""流式请求,429 时自动重试.
|
|
178
|
+
"""流式请求,429/529 时自动重试.
|
|
170
179
|
|
|
171
|
-
安全性:429 在 BaseVendor.send_message_stream 中于
|
|
180
|
+
安全性:429/529 在 BaseVendor.send_message_stream 中于
|
|
172
181
|
status code 检查阶段即 raise(在任何 chunk yield 之前),
|
|
173
182
|
因此重试不会导致已发出数据不一致。
|
|
174
183
|
"""
|
|
@@ -176,25 +185,33 @@ class ZhipuVendor(NativeAnthropicVendor):
|
|
|
176
185
|
|
|
177
186
|
for attempt in range(max_attempts):
|
|
178
187
|
try:
|
|
179
|
-
# 429 在 status code 检查阶段即 raise(在任何 chunk 之前),
|
|
188
|
+
# 429/529 在 status code 检查阶段即 raise(在任何 chunk 之前),
|
|
180
189
|
# 因此 __anext__ 安全:要么拿到首个 chunk,要么抛异常。
|
|
181
190
|
ait = super().send_message_stream(request_body, headers)
|
|
182
191
|
head = await ait.__anext__()
|
|
183
192
|
except StopAsyncIteration:
|
|
184
193
|
return
|
|
185
194
|
except httpx.HTTPStatusError as exc:
|
|
186
|
-
if
|
|
195
|
+
if (
|
|
196
|
+
exc.response is None
|
|
197
|
+
or exc.response.status_code not in _BACKOFF_RETRY_STATUS
|
|
198
|
+
):
|
|
187
199
|
raise
|
|
200
|
+
status_code = exc.response.status_code
|
|
188
201
|
if attempt == max_attempts - 1:
|
|
189
202
|
logger.warning(
|
|
190
|
-
"Zhipu
|
|
203
|
+
"Zhipu %d stream overload exhausted after %d attempts",
|
|
204
|
+
status_code,
|
|
191
205
|
max_attempts,
|
|
192
206
|
)
|
|
193
207
|
raise
|
|
194
208
|
|
|
195
|
-
delay = self.
|
|
209
|
+
delay = self._compute_retry_delay_from_headers(
|
|
210
|
+
exc.response.headers, attempt
|
|
211
|
+
)
|
|
196
212
|
logger.info(
|
|
197
|
-
"Zhipu
|
|
213
|
+
"Zhipu %d stream overload, retry %d/%d in %.1fms",
|
|
214
|
+
status_code,
|
|
198
215
|
attempt + 1,
|
|
199
216
|
max_attempts - 1,
|
|
200
217
|
delay,
|
|
@@ -215,24 +232,15 @@ class ZhipuVendor(NativeAnthropicVendor):
|
|
|
215
232
|
headers: dict[str, str] | None,
|
|
216
233
|
attempt: int,
|
|
217
234
|
) -> float:
|
|
218
|
-
"""计算重试延迟(毫秒),优先使用 server retry-after.
|
|
219
|
-
rl_info = parse_rate_limit_headers(headers, 429, None)
|
|
220
|
-
server_delay_s = compute_effective_retry_seconds(rl_info)
|
|
221
|
-
if server_delay_s is not None:
|
|
222
|
-
return min(server_delay_s * 1000, self._rl_retry.max_delay_ms)
|
|
223
|
-
return calculate_delay(attempt, self._rl_retry)
|
|
235
|
+
"""计算重试延迟(毫秒),优先使用 server retry-after.
|
|
224
236
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
"""
|
|
231
|
-
rl_info = parse_rate_limit_headers(
|
|
232
|
-
response.headers,
|
|
233
|
-
response.status_code,
|
|
234
|
-
response.text[:500] if response.text else None,
|
|
235
|
-
)
|
|
237
|
+
非流式与流式 429/529 重试共用此方法(单一延迟逻辑)。
|
|
238
|
+
以"限流退避"语义(429)解析 header:``parse_rate_limit_headers``
|
|
239
|
+
仅对 429/403 解析 retry-after,故此处固定传 429,
|
|
240
|
+
使 529 也能尊重 server retry-after,与 429 行为一致。
|
|
241
|
+
无 server 信号时回退到指数退避 + Full Jitter。
|
|
242
|
+
"""
|
|
243
|
+
rl_info = parse_rate_limit_headers(headers, 429, None)
|
|
236
244
|
server_delay_s = compute_effective_retry_seconds(rl_info)
|
|
237
245
|
if server_delay_s is not None:
|
|
238
246
|
return min(server_delay_s * 1000, self._rl_retry.max_delay_ms)
|