coding-proxy 0.3.1a8__tar.gz → 0.3.1a10__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.1a8 → coding_proxy-0.3.1a10}/PKG-INFO +1 -1
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/pyproject.toml +1 -1
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/cli/__init__.py +95 -0
- coding_proxy-0.3.1a10/src/coding/proxy/routing/session_policy.py +116 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/server/dashboard.py +314 -47
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/server/routes.py +91 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_session_aware.py +141 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/uv.lock +1 -1
- coding_proxy-0.3.1a8/src/coding/proxy/routing/session_policy.py +0 -56
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/.github/workflows/ci.yml +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/.github/workflows/coverage.yml +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/.github/workflows/release.yml +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/.gitignore +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/.pre-commit-config.yaml +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/AGENTS.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/CHANGELOG.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/CLAUDE.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/LICENSE +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/README.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/assets/dashboard-v0.2.4.png +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/arch/config-reference.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/arch/convert.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/arch/design-patterns.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/arch/routing.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/arch/testing.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/arch/vendors.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/ci-cd.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/framework.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/guide/api-reference.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/guide/cli-reference.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/guide/dashboard.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/guide/monitoring.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/guide/quickstart.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/guide/vendors.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/issue.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/user-guide.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/zh-CN/README.md +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/__init__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/__init__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/__main__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/auth/__init__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/auth/providers/__init__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/auth/providers/base.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/auth/providers/github.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/auth/providers/google.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/auth/runtime.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/auth/store.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/cli/auth_commands.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/cli/banner.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/compat/__init__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/compat/canonical.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/compat/session_store.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/__init__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/auth_schema.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/config.default.yaml +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/loader.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/resiliency.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/routing.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/schema.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/server.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/session_policy.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/vendors.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/convert/__init__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/convert/anthropic_to_gemini.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/convert/anthropic_to_openai.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/convert/gemini_sse_adapter.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/convert/gemini_to_anthropic.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/convert/openai_to_anthropic.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/convert/vendor_channels.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/logging/__init__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/logging/db.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/logging/formatters.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/logging/stats.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/model/__init__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/model/auth.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/model/compat.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/model/constants.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/model/pricing.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/model/token.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/model/vendor.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/__init__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/config.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/extractors/__init__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/extractors/anthropic.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/extractors/gemini.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/extractors/openai.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/handler.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/operation.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/routes.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/usage_registry.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/pricing.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/__init__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/circuit_breaker.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/error_classifier.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/executor.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/model_mapper.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/quota_guard.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/rate_limit.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/retry.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/router.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/session_manager.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/tier.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/usage_parser.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/usage_recorder.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/server/__init__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/server/app.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/server/factory.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/server/responses.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/streaming/__init__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/streaming/anthropic_compat.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/__init__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/alibaba.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/anthropic.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/antigravity.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/base.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/copilot.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/copilot_models.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/copilot_token_manager.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/copilot_urls.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/doubao.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/kimi.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/minimax.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/mixins.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/native_anthropic.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/token_manager.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/xiaomi.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/zhipu.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/__init__.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_antigravity.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_app_routes.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_auto_login.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_banner.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_circuit_breaker.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_cli_usage.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_compat.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_config_init.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_config_loader.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_convert_request.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_convert_response.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_convert_sse.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_copilot.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_copilot_convert_request.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_copilot_convert_response.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_copilot_models.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_copilot_urls.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_currency.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_error_classifier.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_logging_dual_write.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_mixins.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_model_auth.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_model_compat.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_model_constants.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_model_mapper.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_model_pricing.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_model_token.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_model_vendor.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_native_api_base_url_override.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_native_api_extractors.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_native_api_handler.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_native_api_operation.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_native_api_routes.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_native_vendors.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_parse_usage.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_parse_usage_gemini.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_pricing.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_quota_guard.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_rate_limit.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_router_chain.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_router_executor.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_runtime_reauth.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_schema.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_streaming_anthropic_compat.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_tier.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_tiers_config.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_time_range.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_token_logger.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_token_logger_native_columns.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_token_manager.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_types.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_vendor_channels.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_vendor_streaming.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_vendors.py +0 -0
- {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/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.1a10
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "coding-proxy"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.1a10"
|
|
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"
|
|
@@ -30,6 +30,10 @@ logger = logging.getLogger(__name__)
|
|
|
30
30
|
# 注册 Auth 子应用
|
|
31
31
|
app.add_typer(auth_app, name="auth")
|
|
32
32
|
|
|
33
|
+
# 注册 Session 子应用
|
|
34
|
+
session_app = typer.Typer(name="session", help="管理 Session-Vendor 运行时绑定")
|
|
35
|
+
app.add_typer(session_app, name="session")
|
|
36
|
+
|
|
33
37
|
|
|
34
38
|
def _build_token_store(cfg_path: Path | None = None):
|
|
35
39
|
"""按配置解析 Token Store 路径并完成加载."""
|
|
@@ -264,6 +268,97 @@ def reset(
|
|
|
264
268
|
console.print("[red]代理服务未运行[/red]")
|
|
265
269
|
|
|
266
270
|
|
|
271
|
+
# ── Session 子命令 ───────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@session_app.command("bind")
|
|
275
|
+
def session_bind(
|
|
276
|
+
key: str = typer.Option(..., "--key", "-k", help="Session key"),
|
|
277
|
+
vendor: str = typer.Option(
|
|
278
|
+
..., "--vendor", "-v", help="绑定 vendor(逗号分隔多个)"
|
|
279
|
+
),
|
|
280
|
+
port: int = typer.Option(3392, "--port", "-p", help="代理服务端口"),
|
|
281
|
+
) -> None:
|
|
282
|
+
"""为指定 Session 绑定 vendor 优先级."""
|
|
283
|
+
import httpx as _httpx
|
|
284
|
+
|
|
285
|
+
vendors = [v.strip() for v in vendor.split(",") if v.strip()]
|
|
286
|
+
try:
|
|
287
|
+
resp = _httpx.put(
|
|
288
|
+
f"http://127.0.0.1:{port}/api/session-vendor",
|
|
289
|
+
json={"session_key": key, "vendors": vendors},
|
|
290
|
+
timeout=5,
|
|
291
|
+
)
|
|
292
|
+
if resp.status_code == 200:
|
|
293
|
+
data = resp.json()
|
|
294
|
+
console.print(
|
|
295
|
+
f"[green]绑定成功:[/] session [cyan]{key[:16]}…[/cyan] → "
|
|
296
|
+
+ " → ".join(data.get("vendors", vendors))
|
|
297
|
+
)
|
|
298
|
+
else:
|
|
299
|
+
try:
|
|
300
|
+
err = resp.json()
|
|
301
|
+
msg = err.get("error", {}).get("message", resp.text)
|
|
302
|
+
except Exception:
|
|
303
|
+
msg = resp.text
|
|
304
|
+
console.print(f"[red]绑定失败: {msg}[/red]")
|
|
305
|
+
except _httpx.ConnectError:
|
|
306
|
+
console.print("[red]代理服务未运行[/red]")
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@session_app.command("unbind")
|
|
310
|
+
def session_unbind(
|
|
311
|
+
key: str = typer.Option(..., "--key", "-k", help="Session key"),
|
|
312
|
+
port: int = typer.Option(3392, "--port", "-p", help="代理服务端口"),
|
|
313
|
+
) -> None:
|
|
314
|
+
"""解除指定 Session 的 vendor 绑定."""
|
|
315
|
+
from urllib.parse import quote
|
|
316
|
+
|
|
317
|
+
import httpx as _httpx
|
|
318
|
+
|
|
319
|
+
try:
|
|
320
|
+
resp = _httpx.delete(
|
|
321
|
+
f"http://127.0.0.1:{port}/api/session-vendor/{quote(key, safe='')}",
|
|
322
|
+
timeout=5,
|
|
323
|
+
)
|
|
324
|
+
if resp.status_code == 200:
|
|
325
|
+
console.print(f"[green]已解除绑定:[/] session [cyan]{key[:16]}…[/cyan]")
|
|
326
|
+
elif resp.status_code == 404:
|
|
327
|
+
console.print(f"[yellow]未找到绑定:[/] session [cyan]{key[:16]}…[/cyan]")
|
|
328
|
+
else:
|
|
329
|
+
console.print(f"[red]解除失败: {resp.status_code} {resp.text}[/red]")
|
|
330
|
+
except _httpx.ConnectError:
|
|
331
|
+
console.print("[red]代理服务未运行[/red]")
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@session_app.command("list")
|
|
335
|
+
def session_list(
|
|
336
|
+
port: int = typer.Option(3392, "--port", "-p", help="代理服务端口"),
|
|
337
|
+
) -> None:
|
|
338
|
+
"""列出所有运行时 Session-Vendor 绑定."""
|
|
339
|
+
import httpx as _httpx
|
|
340
|
+
|
|
341
|
+
try:
|
|
342
|
+
resp = _httpx.get(
|
|
343
|
+
f"http://127.0.0.1:{port}/api/session-vendor",
|
|
344
|
+
timeout=5,
|
|
345
|
+
)
|
|
346
|
+
if resp.status_code == 200:
|
|
347
|
+
data = resp.json()
|
|
348
|
+
bindings = data.get("bindings", [])
|
|
349
|
+
if not bindings:
|
|
350
|
+
console.print("[dim]当前无运行时绑定[/dim]")
|
|
351
|
+
return
|
|
352
|
+
for b in bindings:
|
|
353
|
+
key = b.get("session_key", "?")
|
|
354
|
+
vendors = b.get("vendors", [])
|
|
355
|
+
console.print(f" [cyan]{key[:24]}…[/cyan] → " + " → ".join(vendors))
|
|
356
|
+
else:
|
|
357
|
+
console.print(f"[red]查询失败: {resp.status_code} {resp.text}[/red]")
|
|
358
|
+
except _httpx.ConnectError:
|
|
359
|
+
console.print("[red]代理服务未运行[/red]")
|
|
360
|
+
|
|
361
|
+
|
|
267
362
|
def _resolve_config_path(config: str | Path | None = None) -> Path | None:
|
|
268
363
|
"""标准化配置路径输入."""
|
|
269
364
|
if config is None:
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Session Policy 解析引擎 — 根据 session_key + client_category 解析适用的路由策略."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import threading
|
|
7
|
+
|
|
8
|
+
from ..config.session_policy import SessionPolicy, SessionPolicyMatch
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SessionPolicyResolver:
|
|
14
|
+
"""根据 session_key + client_category 解析适用的 SessionPolicy.
|
|
15
|
+
|
|
16
|
+
设计要点:
|
|
17
|
+
- 启动时构建索引,运行时 O(1) 查找
|
|
18
|
+
- 精确匹配优先:session_key > client_category > 无策略
|
|
19
|
+
- 无侵入性:不匹配时返回 None,路由行为与现有一致
|
|
20
|
+
- 运行时可变:支持 API 动态 upsert/remove session → vendor 绑定
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, policies: list[SessionPolicy] | None = None) -> None:
|
|
24
|
+
self._policies = policies or []
|
|
25
|
+
self._key_index: dict[str, SessionPolicy] = {}
|
|
26
|
+
self._category_index: dict[str, SessionPolicy] = {}
|
|
27
|
+
self._config_key_backup: dict[str, SessionPolicy] = {}
|
|
28
|
+
self._lock = threading.Lock()
|
|
29
|
+
self._build_index()
|
|
30
|
+
|
|
31
|
+
def _build_index(self) -> None:
|
|
32
|
+
"""构建 session_key / client_category → SessionPolicy 的查找索引.
|
|
33
|
+
|
|
34
|
+
按定义顺序遍历,首次出现的 key/category 获得最高优先级。
|
|
35
|
+
"""
|
|
36
|
+
for policy in self._policies:
|
|
37
|
+
for key in policy.match.session_keys:
|
|
38
|
+
if key not in self._key_index:
|
|
39
|
+
self._key_index[key] = policy
|
|
40
|
+
if (
|
|
41
|
+
policy.match.client_category
|
|
42
|
+
and policy.match.client_category not in self._category_index
|
|
43
|
+
):
|
|
44
|
+
self._category_index[policy.match.client_category] = policy
|
|
45
|
+
|
|
46
|
+
if self._key_index or self._category_index:
|
|
47
|
+
logger.info(
|
|
48
|
+
"SessionPolicyResolver initialized: %d key rules, %d category rules",
|
|
49
|
+
len(self._key_index),
|
|
50
|
+
len(self._category_index),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def resolve(
|
|
54
|
+
self, session_key: str, client_category: str = "cc"
|
|
55
|
+
) -> SessionPolicy | None:
|
|
56
|
+
"""返回匹配的策略,优先精确 session_key 匹配,其次 category 匹配.
|
|
57
|
+
|
|
58
|
+
返回的 SessionPolicy 对象应为不可变引用;调用方不应修改其内部属性,
|
|
59
|
+
否则在并发 upsert/remove 场景下可能产生竞态。
|
|
60
|
+
"""
|
|
61
|
+
with self._lock:
|
|
62
|
+
policy = self._key_index.get(session_key)
|
|
63
|
+
if policy:
|
|
64
|
+
return policy
|
|
65
|
+
return self._category_index.get(client_category)
|
|
66
|
+
|
|
67
|
+
# ── 运行时 session → vendor 绑定 ──────────────────────────────
|
|
68
|
+
|
|
69
|
+
def upsert(self, session_key: str, tier_names: list[str]) -> SessionPolicy:
|
|
70
|
+
"""为指定 session key 创建或替换运行时 vendor 绑定.
|
|
71
|
+
|
|
72
|
+
运行时策略使用 ``runtime:`` 名称前缀,与配置文件驱动的策略区分。
|
|
73
|
+
"""
|
|
74
|
+
policy = SessionPolicy(
|
|
75
|
+
name=f"runtime:{session_key}",
|
|
76
|
+
match=SessionPolicyMatch(session_keys=[session_key]),
|
|
77
|
+
tiers=tier_names,
|
|
78
|
+
)
|
|
79
|
+
with self._lock:
|
|
80
|
+
existing = self._key_index.get(session_key)
|
|
81
|
+
if existing and not existing.name.startswith("runtime:"):
|
|
82
|
+
self._config_key_backup[session_key] = existing
|
|
83
|
+
self._key_index[session_key] = policy
|
|
84
|
+
logger.info(
|
|
85
|
+
"Session vendor binding upserted: session_key=%s → %s",
|
|
86
|
+
session_key,
|
|
87
|
+
tier_names,
|
|
88
|
+
)
|
|
89
|
+
return policy
|
|
90
|
+
|
|
91
|
+
def remove(self, session_key: str) -> bool:
|
|
92
|
+
"""删除指定 session key 的运行时 vendor 绑定.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
True 如果找到并删除了绑定,False 如果不存在。
|
|
96
|
+
"""
|
|
97
|
+
with self._lock:
|
|
98
|
+
policy = self._key_index.get(session_key)
|
|
99
|
+
if policy is None or not policy.name.startswith("runtime:"):
|
|
100
|
+
return False
|
|
101
|
+
del self._key_index[session_key]
|
|
102
|
+
# 恢复被运行时绑定覆盖的配置策略
|
|
103
|
+
backup = self._config_key_backup.pop(session_key, None)
|
|
104
|
+
if backup is not None:
|
|
105
|
+
self._key_index[session_key] = backup
|
|
106
|
+
logger.info("Session vendor binding removed: session_key=%s", session_key)
|
|
107
|
+
return True
|
|
108
|
+
|
|
109
|
+
def list_runtime_bindings(self) -> list[dict[str, str | list[str]]]:
|
|
110
|
+
"""返回所有运行时注入的绑定快照(仅 API 创建的,不含配置文件驱动的)."""
|
|
111
|
+
with self._lock:
|
|
112
|
+
return [
|
|
113
|
+
{"session_key": key, "vendors": policy.tiers}
|
|
114
|
+
for key, policy in self._key_index.items()
|
|
115
|
+
if policy.name.startswith("runtime:")
|
|
116
|
+
]
|