coding-proxy 0.3.1a7__tar.gz → 0.3.1a8__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.3.1a7 → coding_proxy-0.3.1a8}/PKG-INFO +1 -1
- coding_proxy-0.3.1a8/docs/issue.md +47 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/pyproject.toml +1 -1
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/config/config.default.yaml +28 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/config/schema.py +18 -1
- coding_proxy-0.3.1a8/src/coding/proxy/config/session_policy.py +59 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/convert/vendor_channels.py +52 -406
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/logging/db.py +77 -3
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/model/vendor.py +0 -2
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/routing/error_classifier.py +5 -13
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/routing/executor.py +53 -38
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/routing/router.py +3 -0
- coding_proxy-0.3.1a8/src/coding/proxy/routing/session_policy.py +56 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/routing/usage_recorder.py +2 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/server/app.py +6 -1
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/server/dashboard.py +160 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_error_classifier.py +0 -72
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_model_vendor.py +1 -4
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_router_executor.py +23 -351
- coding_proxy-0.3.1a8/tests/test_session_aware.py +451 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_types.py +0 -1
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_vendor_channels.py +395 -1549
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_vendors.py +0 -28
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/uv.lock +1 -1
- coding_proxy-0.3.1a7/docs/issue.md +0 -215
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/.github/workflows/ci.yml +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/.github/workflows/coverage.yml +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/.github/workflows/release.yml +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/.gitignore +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/.pre-commit-config.yaml +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/AGENTS.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/CHANGELOG.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/CLAUDE.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/LICENSE +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/README.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/assets/dashboard-v0.2.4.png +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/docs/arch/config-reference.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/docs/arch/convert.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/docs/arch/design-patterns.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/docs/arch/routing.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/docs/arch/testing.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/docs/arch/vendors.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/docs/ci-cd.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/docs/framework.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/docs/guide/api-reference.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/docs/guide/cli-reference.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/docs/guide/dashboard.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/docs/guide/monitoring.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/docs/guide/quickstart.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/docs/guide/vendors.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/docs/user-guide.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/docs/zh-CN/README.md +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/__main__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/auth/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/auth/providers/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/auth/providers/base.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/auth/providers/github.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/auth/providers/google.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/auth/runtime.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/auth/store.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/cli/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/cli/auth_commands.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/cli/banner.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/compat/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/compat/canonical.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/compat/session_store.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/config/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/config/auth_schema.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/config/loader.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/config/resiliency.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/config/routing.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/config/server.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/config/vendors.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/convert/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/convert/anthropic_to_gemini.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/convert/anthropic_to_openai.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/convert/gemini_sse_adapter.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/convert/gemini_to_anthropic.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/convert/openai_to_anthropic.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/logging/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/logging/formatters.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/logging/stats.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/model/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/model/auth.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/model/compat.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/model/constants.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/model/pricing.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/model/token.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/native_api/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/native_api/config.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/native_api/extractors/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/native_api/extractors/anthropic.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/native_api/extractors/gemini.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/native_api/extractors/openai.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/native_api/handler.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/native_api/operation.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/native_api/routes.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/native_api/usage_registry.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/pricing.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/routing/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/routing/circuit_breaker.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/routing/model_mapper.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/routing/quota_guard.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/routing/rate_limit.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/routing/retry.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/routing/session_manager.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/routing/tier.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/routing/usage_parser.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/server/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/server/factory.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/server/responses.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/server/routes.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/streaming/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/streaming/anthropic_compat.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/alibaba.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/anthropic.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/antigravity.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/base.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/copilot.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/copilot_models.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/copilot_token_manager.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/copilot_urls.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/doubao.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/kimi.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/minimax.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/mixins.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/native_anthropic.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/token_manager.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/xiaomi.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/src/coding/proxy/vendors/zhipu.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/__init__.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_antigravity.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_app_routes.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_auto_login.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_banner.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_circuit_breaker.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_cli_usage.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_compat.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_config_init.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_config_loader.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_convert_request.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_convert_response.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_convert_sse.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_copilot.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_copilot_convert_request.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_copilot_convert_response.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_copilot_models.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_copilot_urls.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_currency.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_logging_dual_write.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_mixins.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_model_auth.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_model_compat.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_model_constants.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_model_mapper.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_model_pricing.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_model_token.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_native_api_base_url_override.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_native_api_extractors.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_native_api_handler.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_native_api_operation.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_native_api_routes.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_native_vendors.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_parse_usage.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_parse_usage_gemini.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_pricing.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_quota_guard.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_rate_limit.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_router_chain.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_runtime_reauth.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_schema.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_streaming_anthropic_compat.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_tier.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_tiers_config.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_time_range.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_token_logger.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_token_logger_native_columns.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_token_manager.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_vendor_streaming.py +0 -0
- {coding_proxy-0.3.1a7 → coding_proxy-0.3.1a8}/tests/test_zhipu.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coding-proxy
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1a8
|
|
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
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Issue 处理档案
|
|
2
|
+
|
|
3
|
+
> 维护已处理过的 Issue 摘要(问题描述、表因根因、处理方式、后续防范、同类问题影响与处理注意事项),便于同类问题的跨上下文处理。识别相同 Issue 时应在原条目追加复盘,避免同 Issue 多处维护。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## streaming usage parse failed: 'NoneType' object has no attribute 'get'
|
|
8
|
+
|
|
9
|
+
**问题描述**
|
|
10
|
+
|
|
11
|
+
OpenAI 兼容 SSE 流式响应过程中,单次请求日志反复刷出数十条 WARNING:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
WARNING streaming usage parse failed: 'NoneType' object has no attribute 'get'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
警告本身被上层 `try/except` 吞掉不影响主链路,但日志噪声严重,且每帧都丢失了 usage 累加。
|
|
18
|
+
|
|
19
|
+
**表因**
|
|
20
|
+
|
|
21
|
+
`StreamingUsageAccumulator.feed` 调用 `parse_usage_from_chunk` 解析 SSE chunk 时抛出 `AttributeError`。
|
|
22
|
+
|
|
23
|
+
**根因**
|
|
24
|
+
|
|
25
|
+
`src/coding/proxy/routing/usage_parser.py::parse_usage_from_chunk` 中 Anthropic message_start 与 Anthropic message_delta / OpenAI 两条分支都使用了脆弱的判空模式:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
if "usage" in data: # 仅判断 key 存在
|
|
29
|
+
u = data["usage"] # 但值可能是 null
|
|
30
|
+
u.get("output_tokens", 0) # AttributeError
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
部分上游(含某些 OpenAI 兼容供应商)在中间 chunk 显式发送 `"usage": null` 占位帧,`in` 检查通过但取出的是 `None`。
|
|
34
|
+
|
|
35
|
+
**处理方式**
|
|
36
|
+
|
|
37
|
+
将两处 guard 统一改为 `u = container.get("usage"); if isinstance(u, dict):`,既排除缺省也排除 null,并顺手移除内部冗余的 `if isinstance(u, dict):` 包装层(已被外层 guard 覆盖)。同时新增三个回归用例覆盖 `data.usage = null` / `message.usage = null` / null 帧后跟有效帧三种场景。
|
|
38
|
+
|
|
39
|
+
**后续防范**
|
|
40
|
+
|
|
41
|
+
- 解析外部 SSE / JSON 结构时, 不要单独使用 `if key in data` 作为安全 guard, 应统一采用 `value = data.get(key); if isinstance(value, dict):` 的双重保护, 同时排除缺省与显式 null。
|
|
42
|
+
- 对 try/except 包裹的 WARNING 路径要保持警觉: 异常被吞不代表无害,重复刷屏的同类警告往往暗示防御性 guard 过窄,需要回溯至根因修复,而非依赖 except 兜底。
|
|
43
|
+
|
|
44
|
+
**同类问题影响与处理注意事项**
|
|
45
|
+
|
|
46
|
+
- 本仓库内 `parse_usage_from_chunk` 的 Gemini `usageMetadata` 分支 (line ~219) 已经使用 `isinstance(um, dict)` 防御, 不受影响, 可作为参考实现。
|
|
47
|
+
- 检查其他解析器 (如 routing / vendor adapter 层) 是否还有 `if "key" in data: v = data["key"]; v.get(...)` 这种模式, 必要时同步加固。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "coding-proxy"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.1a8"
|
|
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"
|
|
@@ -644,3 +644,31 @@ native_api:
|
|
|
644
644
|
base_url: "https://api.anthropic.com"
|
|
645
645
|
timeout_ms: 300000
|
|
646
646
|
connect_timeout_ms: 15000
|
|
647
|
+
|
|
648
|
+
# === Session 级别路由策略(可选)===
|
|
649
|
+
#
|
|
650
|
+
# 为特定 Session 或客户端类别定制 vendor 优先级顺序。
|
|
651
|
+
# 匹配策略按定义顺序求值,首次匹配生效。
|
|
652
|
+
#
|
|
653
|
+
# 支持的匹配条件(OR 语义,满足任一即匹配):
|
|
654
|
+
# session_keys: 精确匹配的 session key 列表
|
|
655
|
+
# client_category: 按客户端类别匹配(⚠️ 预留字段,当前版本暂未生效,后续版本支持)
|
|
656
|
+
#
|
|
657
|
+
# tiers: 覆盖全局 tier 顺序的供应商优先级列表(未提及的 vendor 保持在末尾)
|
|
658
|
+
#
|
|
659
|
+
# 示例 1:为特定 session 绑定专属 vendor 组合
|
|
660
|
+
# session_policies:
|
|
661
|
+
# - name: "vip-session"
|
|
662
|
+
# match:
|
|
663
|
+
# session_keys: ["my-specific-session-id"]
|
|
664
|
+
# tiers: ["anthropic", "copilot", "zhipu"]
|
|
665
|
+
#
|
|
666
|
+
# 示例 2:Claude Code 请求优先走 Copilot
|
|
667
|
+
# session_policies:
|
|
668
|
+
# - name: "claude-code-preferred"
|
|
669
|
+
# match:
|
|
670
|
+
# client_category: "cc"
|
|
671
|
+
# tiers: ["copilot", "anthropic", "zhipu"]
|
|
672
|
+
#
|
|
673
|
+
# 未配置时(默认),所有 Session 使用全局 tiers 顺序。
|
|
674
|
+
session_policies: []
|
|
@@ -44,6 +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
48
|
from .vendors import ( # noqa: F401
|
|
48
49
|
AlibabaConfig,
|
|
49
50
|
AnthropicConfig,
|
|
@@ -152,11 +153,19 @@ class ProxyConfig(BaseModel):
|
|
|
152
153
|
"三个 provider 默认 enabled=False,显式启用才暴露 /api/{provider}/* 端点。"
|
|
153
154
|
),
|
|
154
155
|
)
|
|
156
|
+
# Session 级别路由策略
|
|
157
|
+
session_policies: SessionPoliciesConfig = Field(
|
|
158
|
+
default_factory=SessionPoliciesConfig,
|
|
159
|
+
description=(
|
|
160
|
+
"Session 级别的路由策略配置。"
|
|
161
|
+
"可为特定 Session 或客户端类别定制 vendor 优先级顺序。"
|
|
162
|
+
),
|
|
163
|
+
)
|
|
155
164
|
|
|
156
165
|
@model_validator(mode="before")
|
|
157
166
|
@classmethod
|
|
158
167
|
def _migrate_legacy_fields(cls, data: Any) -> Any:
|
|
159
|
-
"""向后兼容迁移(legacy flat 格式 → vendors
|
|
168
|
+
"""向后兼容迁移(legacy flat 格式 → vendors 列表格式)+ session_policies 规范化.
|
|
160
169
|
|
|
161
170
|
迁移规则:
|
|
162
171
|
1. ``anthropic`` / ``zhipu`` 字段名自动映射为 ``primary`` / ``fallback``
|
|
@@ -165,6 +174,12 @@ class ProxyConfig(BaseModel):
|
|
|
165
174
|
if not isinstance(data, dict):
|
|
166
175
|
return data
|
|
167
176
|
|
|
177
|
+
# session_policies 规范化:YAML 中 session_policies: [] 解析为 list,
|
|
178
|
+
# 需转为 dict 包装以匹配 SessionPoliciesConfig 模型
|
|
179
|
+
sp = data.get("session_policies")
|
|
180
|
+
if isinstance(sp, list):
|
|
181
|
+
data["session_policies"] = {"policies": sp}
|
|
182
|
+
|
|
168
183
|
# 1. 字段别名迁移
|
|
169
184
|
if "anthropic" in data and "primary" not in data:
|
|
170
185
|
data["primary"] = data.pop("anthropic")
|
|
@@ -331,4 +346,6 @@ __all__ = [
|
|
|
331
346
|
"AlibabaConfig",
|
|
332
347
|
# native api passthrough
|
|
333
348
|
"NativeApiConfig",
|
|
349
|
+
# session policy
|
|
350
|
+
"SessionPoliciesConfig",
|
|
334
351
|
]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Session Policy 配置模型 — 为特定 Session 定制路由行为."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SessionPolicyMatch(BaseModel):
|
|
9
|
+
"""策略匹配条件 — 满足任一条件即匹配(OR 语义)."""
|
|
10
|
+
|
|
11
|
+
session_keys: list[str] = Field(
|
|
12
|
+
default_factory=list,
|
|
13
|
+
description="精确匹配的 session key 列表",
|
|
14
|
+
)
|
|
15
|
+
client_category: str | None = Field(
|
|
16
|
+
default=None,
|
|
17
|
+
description=(
|
|
18
|
+
"按客户端类别匹配('cc' 或 'api')。"
|
|
19
|
+
"⚠️ 预留字段,当前路由执行链路未传入 client_category,"
|
|
20
|
+
"配置此条件不会生效。后续版本将支持。"
|
|
21
|
+
),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SessionQuotaConfig(BaseModel):
|
|
26
|
+
"""Per-session 资源配额(架构预留)."""
|
|
27
|
+
|
|
28
|
+
token_budget: int = Field(
|
|
29
|
+
default=0,
|
|
30
|
+
description="时间窗口内的 token 预算上限",
|
|
31
|
+
)
|
|
32
|
+
window_hours: float = Field(
|
|
33
|
+
default=24.0,
|
|
34
|
+
description="滚动时间窗口(小时)",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SessionPolicy(BaseModel):
|
|
39
|
+
"""单条 Session 路由策略."""
|
|
40
|
+
|
|
41
|
+
name: str = Field(description="策略名称(用于日志与排障)")
|
|
42
|
+
match: SessionPolicyMatch = Field(description="匹配条件")
|
|
43
|
+
tiers: list[str] = Field(
|
|
44
|
+
default_factory=list,
|
|
45
|
+
description="覆盖全局 tier 顺序的供应商优先级列表",
|
|
46
|
+
)
|
|
47
|
+
quota: SessionQuotaConfig | None = Field(
|
|
48
|
+
default=None,
|
|
49
|
+
description="Per-session 资源配额(预留)",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SessionPoliciesConfig(BaseModel):
|
|
54
|
+
"""顶层 Session 策略配置容器."""
|
|
55
|
+
|
|
56
|
+
policies: list[SessionPolicy] = Field(
|
|
57
|
+
default_factory=list,
|
|
58
|
+
description="Session 路由策略列表,按定义顺序求值,首次匹配生效",
|
|
59
|
+
)
|