coding-proxy 0.4.1a10__tar.gz → 0.4.1a11__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.4.1a10 → coding_proxy-0.4.1a11}/PKG-INFO +1 -1
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/agents/issue.md +8 -4
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/pyproject.toml +1 -1
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/convert/vendor_channels.py +4 -2
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/routing/executor.py +99 -28
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/server/dashboard.py +163 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/concurrency.py +6 -1
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/zhipu.py +97 -6
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_vendors.py +25 -2
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_zhipu.py +44 -3
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/uv.lock +1 -1
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/.github/workflows/ci.yml +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/.github/workflows/coverage.yml +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/.github/workflows/release.yml +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/.gitignore +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/.pre-commit-config.yaml +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/AGENTS.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/CHANGELOG.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/CLAUDE.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/LICENSE +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/README.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/assets/dashboard-v0.4.0.png +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/assets/session-v0.4.0.png +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/agents/browser-validation.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/agents/knowledge-map.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/agents/reference-specifications.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/arch/config-reference.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/arch/convert.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/arch/design-patterns.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/arch/routing.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/arch/testing.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/arch/vendors.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/framework.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/guide/api-reference.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/guide/cli-reference.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/guide/dashboard.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/guide/monitoring.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/guide/quickstart.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/guide/vendors.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/ops/ci-cd.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/user-guide.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/docs/zh-CN/README.md +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/__main__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/auth/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/auth/providers/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/auth/providers/base.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/auth/providers/github.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/auth/providers/google.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/auth/runtime.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/auth/store.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/cli/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/cli/auth_commands.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/cli/banner.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/compat/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/compat/canonical.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/compat/session_store.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/config/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/config/auth_schema.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/config/config.default.yaml +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/config/loader.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/config/resiliency.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/config/routing.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/config/schema.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/config/server.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/config/session_policy.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/config/vendors.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/convert/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/convert/anthropic_to_gemini.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/convert/anthropic_to_openai.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/convert/gemini_sse_adapter.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/convert/gemini_to_anthropic.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/convert/openai_to_anthropic.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/logging/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/logging/db.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/logging/formatters.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/logging/stats.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/model/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/model/auth.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/model/compat.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/model/constants.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/model/pricing.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/model/token.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/model/vendor.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/native_api/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/native_api/config.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/native_api/extractors/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/native_api/extractors/anthropic.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/native_api/extractors/gemini.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/native_api/extractors/openai.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/native_api/handler.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/native_api/operation.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/native_api/routes.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/native_api/usage_registry.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/pricing.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/routing/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/routing/circuit_breaker.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/routing/error_classifier.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/routing/model_mapper.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/routing/quota_guard.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/routing/rate_limit.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/routing/retry.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/routing/router.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/routing/session_manager.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/routing/session_policy.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/routing/tier.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/routing/usage_parser.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/routing/usage_recorder.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/server/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/server/app.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/server/factory.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/server/responses.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/server/routes.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/streaming/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/streaming/anthropic_compat.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/alibaba.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/anthropic.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/antigravity.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/base.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/copilot.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/copilot_models.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/copilot_token_manager.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/copilot_urls.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/doubao.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/kimi.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/minimax.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/mixins.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/native_anthropic.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/token_manager.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/src/coding/proxy/vendors/xiaomi.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/e2e/__init__.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/e2e/conftest.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/e2e/test_e2e_http.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/e2e/test_e2e_token.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/e2e/test_e2e_vendor.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_antigravity.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_app_routes.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_auto_login.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_banner.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_circuit_breaker.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_cli_usage.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_compat.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_config_init.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_config_loader.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_convert_request.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_convert_response.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_convert_sse.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_copilot.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_copilot_convert_request.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_copilot_convert_response.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_copilot_models.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_copilot_urls.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_currency.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_error_classifier.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_logging_dual_write.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_mixins.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_model_auth.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_model_compat.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_model_constants.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_model_mapper.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_model_pricing.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_model_token.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_model_vendor.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_native_api_base_url_override.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_native_api_extractors.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_native_api_handler.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_native_api_operation.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_native_api_routes.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_native_vendors.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_parse_usage.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_parse_usage_gemini.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_pricing.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_quota_guard.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_rate_limit.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_router_chain.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_router_executor.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_runtime_reauth.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_schema.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_session_aware.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_streaming_anthropic_compat.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_tier.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_tiers_config.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_time_range.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_token_logger.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_token_logger_native_columns.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_token_manager.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_types.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_vendor_channels.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/tests/test_vendor_streaming.py +0 -0
- {coding_proxy-0.4.1a10 → coding_proxy-0.4.1a11}/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.4.
|
|
3
|
+
Version: 0.4.1a11
|
|
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
|
|
@@ -253,15 +253,19 @@ INFO Tier anthropic message succeeded (took over from failed tier: zhipu)
|
|
|
253
253
|
|
|
254
254
|
`is_semantic_rejection` 检测到 zhipu 返回 `invalid_request_error + 1210` 含「API 调用参数有误」中文标记,判定为语义拒绝,跳过下一层 tier。1210 是智谱官方错误码,[官方文档](https://docs.bigmodel.cn/cn/api/api-code) 定义为「参数格式/类型不符规范」(区别于 1213「必需字段缺失」、1214「字段参数非法」)。
|
|
255
255
|
|
|
256
|
-
|
|
256
|
+
**根因(已定位,修复中)**
|
|
257
257
|
|
|
258
|
-
PR #
|
|
258
|
+
PR #247 (Step 1 v2) 部署后,2026-05-26 16:30–16:31 的诊断日志显示 8 次连续拒绝**全部携带 `thinking={"type": "adaptive"}`**(Anthropic Claude 4.x 新增的参数类型),而同一时段其他会话的请求持续成功。之前 curl 测试仅验证了 `{"type": "enabled"}`,未覆盖 `adaptive` 类型。GLM 可能不支持此特定类型值,导致 [1210] 参数校验失败。
|
|
259
259
|
|
|
260
260
|
**处理方式(分阶段)**
|
|
261
261
|
|
|
262
262
|
- **Step 1(PR #244,已合并)**:在 `executor.py::_build_semantic_rejection_diagnostic` 中输出 thinking / cache_control 相关字段 — 但证据反转,覆盖不足以定位真因。
|
|
263
|
-
- **Step 1 v2
|
|
264
|
-
- **Step 2
|
|
263
|
+
- **Step 1 v2(PR #247,已合并)**:扩展诊断函数覆盖 `system_kind|blocks(+cc)` / `tools` / `tool_choice` / 采样参数 / `stream` / `metadata_keys` / `content_types` / `body_bytes` 等维度。所有项「仅存在时输出」以控制日志噪声。配套 14 个单元测试(`TestBuildSemanticRejectionDiagnostic`)覆盖各字段组合。
|
|
264
|
+
- **Step 2(进行中)**:基于 Step 1 v2 的日志证据,在 `ZhipuVendor._prepare_request` 中实现 **兼容转换**(而非移除):
|
|
265
|
+
- `thinking.type="adaptive"` → `{"type": "enabled", "budget_tokens": 16000}`(保留 thinking 能力)
|
|
266
|
+
- 新增 `_build_zhipu_request_snapshot` 诊断快照,同时覆盖成功/失败请求,建立可对比证据链
|
|
267
|
+
- 扩展语义拒绝日志的错误体截断限制(200 → 500 字符),保留完整字段级诊断
|
|
268
|
+
- `metadata` 暂不处理(待进一步诊断确认兼容性)
|
|
265
269
|
|
|
266
270
|
**后续防范**
|
|
267
271
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "coding-proxy"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.1a11"
|
|
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"
|
|
@@ -369,8 +369,10 @@ def _strip_cache_control(body: dict[str, Any]) -> int:
|
|
|
369
369
|
|
|
370
370
|
# ── zhipu 共享清洗函数 ──────────────────────────────────────────
|
|
371
371
|
|
|
372
|
-
#
|
|
373
|
-
#
|
|
372
|
+
# 跨供应商转换时主动剥离的顶层参数。
|
|
373
|
+
# 首选 tier 场景的 thinking.type=adaptive 兼容转换由
|
|
374
|
+
# ZhipuVendor._prepare_request 处理(转换为 enabled + budget,保留功能),
|
|
375
|
+
# 此处仅负责 failover 路径的全量剥离(跨供应商 thinking signature 失效)。
|
|
374
376
|
_ZHIPU_UNSUPPORTED_PARAMS: frozenset[str] = frozenset(
|
|
375
377
|
{"thinking", "extended_thinking", "reasoning_effort"}
|
|
376
378
|
)
|
|
@@ -129,50 +129,116 @@ def _build_semantic_rejection_diagnostic(body: dict[str, Any]) -> str:
|
|
|
129
129
|
|
|
130
130
|
在 semantic rejection 日志中附加请求体的可疑参数快照,
|
|
131
131
|
用于定位供应商参数校验失败的具体祸根参数。
|
|
132
|
+
|
|
133
|
+
覆盖范围:
|
|
134
|
+
* 模型 / messages 数(baseline)
|
|
135
|
+
* thinking 系列顶层参数 + history thinking_blocks 数
|
|
136
|
+
* system 形态(string / blocks,含 cache_control 计数)
|
|
137
|
+
* tools 数量 + tool_choice 形态
|
|
138
|
+
* 采样参数(max_tokens / temperature / top_p / top_k / stop_sequences)
|
|
139
|
+
* stream / metadata 形态
|
|
140
|
+
* cache_control 存在性
|
|
141
|
+
* messages.content 类型分布
|
|
142
|
+
* 请求体大小估算(json.dumps 字节数)
|
|
132
143
|
"""
|
|
133
144
|
parts: list[str] = []
|
|
134
|
-
|
|
145
|
+
|
|
146
|
+
# ── 模型 + 消息数(baseline,始终输出)──
|
|
147
|
+
parts.append(f"model={body.get('model', 'N/A')}")
|
|
148
|
+
parts.append(f"messages={len(body.get('messages', []))}")
|
|
149
|
+
|
|
150
|
+
# ── 顶层 thinking 系列参数 ──
|
|
135
151
|
for key in ("thinking", "extended_thinking", "reasoning_effort"):
|
|
136
152
|
if key in body:
|
|
137
153
|
val = body[key]
|
|
138
154
|
parts.append(f"{key}={val!r:.80}")
|
|
139
|
-
|
|
155
|
+
|
|
156
|
+
# ── system 形态 ──
|
|
157
|
+
system = body.get("system")
|
|
158
|
+
if isinstance(system, str):
|
|
159
|
+
parts.append(f"system_kind=string(len={len(system)})")
|
|
160
|
+
elif isinstance(system, list):
|
|
161
|
+
cc_count = sum(
|
|
162
|
+
1 for item in system if isinstance(item, dict) and "cache_control" in item
|
|
163
|
+
)
|
|
164
|
+
if cc_count:
|
|
165
|
+
parts.append(f"system_blocks={len(system)},cc={cc_count}")
|
|
166
|
+
else:
|
|
167
|
+
parts.append(f"system_blocks={len(system)}")
|
|
168
|
+
|
|
169
|
+
# ── tools 与 tool_choice ──
|
|
170
|
+
tools = body.get("tools")
|
|
171
|
+
if isinstance(tools, list):
|
|
172
|
+
parts.append(f"tools={len(tools)}")
|
|
173
|
+
tool_choice = body.get("tool_choice")
|
|
174
|
+
if tool_choice is not None:
|
|
175
|
+
parts.append(f"tool_choice={tool_choice!r:.60}")
|
|
176
|
+
|
|
177
|
+
# ── 采样参数(仅存在时输出)──
|
|
178
|
+
for key in ("max_tokens", "temperature", "top_p", "top_k"):
|
|
179
|
+
if key in body:
|
|
180
|
+
parts.append(f"{key}={body[key]!r:.40}")
|
|
181
|
+
stop_sequences = body.get("stop_sequences")
|
|
182
|
+
if isinstance(stop_sequences, list) and stop_sequences:
|
|
183
|
+
parts.append(f"stop_sequences={len(stop_sequences)}")
|
|
184
|
+
|
|
185
|
+
# ── stream / metadata ──
|
|
186
|
+
if "stream" in body:
|
|
187
|
+
parts.append(f"stream={body['stream']}")
|
|
188
|
+
metadata = body.get("metadata")
|
|
189
|
+
if isinstance(metadata, dict) and metadata:
|
|
190
|
+
parts.append(f"metadata_keys={len(metadata)}")
|
|
191
|
+
|
|
192
|
+
# ── 会话历史中的 thinking blocks 与 content_types 分布 ──
|
|
140
193
|
thinking_count = 0
|
|
194
|
+
content_type_counts: dict[str, int] = {}
|
|
141
195
|
for msg in body.get("messages", []):
|
|
142
196
|
content = msg.get("content")
|
|
197
|
+
if isinstance(content, str):
|
|
198
|
+
content_type_counts["string"] = content_type_counts.get("string", 0) + 1
|
|
199
|
+
continue
|
|
143
200
|
if not isinstance(content, list):
|
|
144
201
|
continue
|
|
145
202
|
for block in content:
|
|
146
|
-
if isinstance(block, dict)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
):
|
|
203
|
+
if not isinstance(block, dict):
|
|
204
|
+
continue
|
|
205
|
+
btype = block.get("type")
|
|
206
|
+
if isinstance(btype, str):
|
|
207
|
+
content_type_counts[btype] = content_type_counts.get(btype, 0) + 1
|
|
208
|
+
if btype in ("thinking", "redacted_thinking"):
|
|
150
209
|
thinking_count += 1
|
|
151
210
|
if thinking_count:
|
|
152
211
|
parts.append(f"thinking_blocks_in_history={thinking_count}")
|
|
153
|
-
|
|
212
|
+
if content_type_counts:
|
|
213
|
+
type_repr = ",".join(f"{k}:{v}" for k, v in sorted(content_type_counts.items()))
|
|
214
|
+
parts.append(f"content_types={{{type_repr}}}")
|
|
215
|
+
|
|
216
|
+
# ── cache_control 存在检测(messages / tools,不含 system 因已单独统计)──
|
|
154
217
|
has_cc = False
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if isinstance(item, dict) and "cache_control" in item:
|
|
167
|
-
has_cc = True
|
|
168
|
-
break
|
|
218
|
+
sections: list[Any] = []
|
|
219
|
+
for m in body.get("messages", []):
|
|
220
|
+
if isinstance(m.get("content"), list):
|
|
221
|
+
sections.append(m["content"])
|
|
222
|
+
if isinstance(body.get("tools"), list):
|
|
223
|
+
sections.append(body["tools"])
|
|
224
|
+
for section in sections:
|
|
225
|
+
for item in section:
|
|
226
|
+
if isinstance(item, dict) and "cache_control" in item:
|
|
227
|
+
has_cc = True
|
|
228
|
+
break
|
|
169
229
|
if has_cc:
|
|
170
230
|
break
|
|
171
231
|
if has_cc:
|
|
172
232
|
parts.append("cache_control_fields=present")
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
233
|
+
|
|
234
|
+
# ── 请求体大小估算 ──
|
|
235
|
+
try:
|
|
236
|
+
body_bytes = len(json.dumps(body, ensure_ascii=False).encode("utf-8"))
|
|
237
|
+
parts.append(f"body_bytes={body_bytes}")
|
|
238
|
+
except (TypeError, ValueError):
|
|
239
|
+
# 极少数情况下 body 含非可序列化对象,跳过
|
|
240
|
+
pass
|
|
241
|
+
|
|
176
242
|
return f" [{', '.join(parts)}]" if parts else ""
|
|
177
243
|
|
|
178
244
|
|
|
@@ -860,12 +926,15 @@ class _RouteExecutor:
|
|
|
860
926
|
|
|
861
927
|
if not is_last and is_semantic:
|
|
862
928
|
diagnostic = _build_semantic_rejection_diagnostic(body)
|
|
929
|
+
# zhipu 等供应商的错误体含字段级诊断(如 [1210] 错误码 + request_id),
|
|
930
|
+
# 500 字符足以覆盖完整错误体,避免截断丢失关键细节
|
|
931
|
+
err_msg = (resp.error_message or "N/A")[:500]
|
|
863
932
|
logger.warning(
|
|
864
933
|
"Tier %s semantic rejection (type=%s, msg=%s)%s, "
|
|
865
934
|
"trying next tier without recording failure",
|
|
866
935
|
tier.name,
|
|
867
936
|
resp.error_type or resp.status_code,
|
|
868
|
-
|
|
937
|
+
err_msg,
|
|
869
938
|
diagnostic,
|
|
870
939
|
)
|
|
871
940
|
failed_tier_name = tier.name
|
|
@@ -1100,14 +1169,16 @@ class _RouteExecutor:
|
|
|
1100
1169
|
if semantic_rejection and not is_last:
|
|
1101
1170
|
if request_body is not None:
|
|
1102
1171
|
diagnostic = _build_semantic_rejection_diagnostic(request_body)
|
|
1172
|
+
stream_err_msg = (
|
|
1173
|
+
error.get("message") if isinstance(error, dict) else "N/A"
|
|
1174
|
+
)
|
|
1175
|
+
# 扩展至 500 字符以保留完整字段级诊断信息
|
|
1103
1176
|
logger.warning(
|
|
1104
1177
|
"Tier %s stream semantic rejection (type=%s, msg=%s)%s, "
|
|
1105
1178
|
"trying next tier without recording failure",
|
|
1106
1179
|
tier.name,
|
|
1107
1180
|
error.get("type") if isinstance(error, dict) else None,
|
|
1108
|
-
|
|
1109
|
-
:200
|
|
1110
|
-
],
|
|
1181
|
+
stream_err_msg[:500],
|
|
1111
1182
|
diagnostic,
|
|
1112
1183
|
)
|
|
1113
1184
|
return True, tier.name, exc
|
|
@@ -557,6 +557,89 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
557
557
|
.tab-btn:focus-visible { outline: 2px solid var(--accent-blue); outline-offset: 2px; }
|
|
558
558
|
.tab-pane { display: none; }
|
|
559
559
|
.tab-pane.active { display: block; }
|
|
560
|
+
|
|
561
|
+
/* ── Model Calling 实时状态 ────────────────────────── */
|
|
562
|
+
.model-calling-card {
|
|
563
|
+
margin-bottom: 5px;
|
|
564
|
+
}
|
|
565
|
+
.mc-empty {
|
|
566
|
+
text-align: center;
|
|
567
|
+
color: var(--text-muted);
|
|
568
|
+
padding: 16px 0;
|
|
569
|
+
font-size: 13px;
|
|
570
|
+
}
|
|
571
|
+
.mc-grid {
|
|
572
|
+
display: grid;
|
|
573
|
+
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
574
|
+
gap: 8px;
|
|
575
|
+
}
|
|
576
|
+
.mc-model-row {
|
|
577
|
+
display: flex;
|
|
578
|
+
align-items: center;
|
|
579
|
+
gap: 10px;
|
|
580
|
+
padding: 8px 12px;
|
|
581
|
+
background: var(--bg-secondary);
|
|
582
|
+
border-radius: var(--radius-sm);
|
|
583
|
+
border: 1px solid var(--border-subtle);
|
|
584
|
+
}
|
|
585
|
+
.mc-model-name {
|
|
586
|
+
font-family: 'JetBrains Mono', monospace;
|
|
587
|
+
font-size: 12px;
|
|
588
|
+
color: var(--text-primary);
|
|
589
|
+
min-width: 140px;
|
|
590
|
+
white-space: nowrap;
|
|
591
|
+
overflow: hidden;
|
|
592
|
+
text-overflow: ellipsis;
|
|
593
|
+
}
|
|
594
|
+
.mc-bar-wrap {
|
|
595
|
+
flex: 1;
|
|
596
|
+
min-width: 60px;
|
|
597
|
+
height: 6px;
|
|
598
|
+
background: rgba(255,255,255,.06);
|
|
599
|
+
border-radius: 3px;
|
|
600
|
+
overflow: hidden;
|
|
601
|
+
}
|
|
602
|
+
.mc-bar-fill {
|
|
603
|
+
height: 100%;
|
|
604
|
+
border-radius: 3px;
|
|
605
|
+
transition: width .3s ease, background .3s ease;
|
|
606
|
+
}
|
|
607
|
+
.mc-bar-fill.mc-low { background: var(--accent-green); }
|
|
608
|
+
.mc-bar-fill.mc-mid { background: var(--accent-yellow); }
|
|
609
|
+
.mc-bar-fill.mc-high { background: var(--accent-red); }
|
|
610
|
+
.mc-stats {
|
|
611
|
+
display: flex;
|
|
612
|
+
align-items: center;
|
|
613
|
+
gap: 6px;
|
|
614
|
+
font-size: 11px;
|
|
615
|
+
font-family: 'JetBrains Mono', monospace;
|
|
616
|
+
color: var(--text-muted);
|
|
617
|
+
white-space: nowrap;
|
|
618
|
+
}
|
|
619
|
+
.mc-badge {
|
|
620
|
+
display: inline-flex;
|
|
621
|
+
align-items: center;
|
|
622
|
+
padding: 1px 6px;
|
|
623
|
+
border-radius: 4px;
|
|
624
|
+
font-size: 10px;
|
|
625
|
+
font-weight: 600;
|
|
626
|
+
font-family: 'JetBrains Mono', monospace;
|
|
627
|
+
}
|
|
628
|
+
.mc-badge-pending {
|
|
629
|
+
background: rgba(251,146,60,.15);
|
|
630
|
+
color: #fb923c;
|
|
631
|
+
}
|
|
632
|
+
.mc-badge-active {
|
|
633
|
+
background: rgba(74,222,128,.12);
|
|
634
|
+
color: #4ade80;
|
|
635
|
+
}
|
|
636
|
+
.mc-vendor-tag {
|
|
637
|
+
font-size: 10px;
|
|
638
|
+
color: var(--text-muted);
|
|
639
|
+
background: rgba(255,255,255,.06);
|
|
640
|
+
padding: 1px 6px;
|
|
641
|
+
border-radius: 3px;
|
|
642
|
+
}
|
|
560
643
|
</style>
|
|
561
644
|
</head>
|
|
562
645
|
<body>
|
|
@@ -626,6 +709,14 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
626
709
|
</div>
|
|
627
710
|
</div>
|
|
628
711
|
|
|
712
|
+
<!-- Model Calling 实时状态 -->
|
|
713
|
+
<div class="card model-calling-card" id="model-calling-card">
|
|
714
|
+
<div class="card-title">📡 Model Calling 实时状态</div>
|
|
715
|
+
<div class="model-calling-wrap" id="model-calling-wrap">
|
|
716
|
+
<div class="mc-empty">加载中…</div>
|
|
717
|
+
</div>
|
|
718
|
+
</div>
|
|
719
|
+
|
|
629
720
|
<!-- 供应商状态 + 请求量趋势折线图 -->
|
|
630
721
|
<div class="charts-grid">
|
|
631
722
|
<div class="card">
|
|
@@ -1134,6 +1225,74 @@ function updateVendorStatus(status) {
|
|
|
1134
1225
|
}).join('');
|
|
1135
1226
|
}
|
|
1136
1227
|
|
|
1228
|
+
// ── Model Calling 实时状态 ────────────────────────────────
|
|
1229
|
+
function updateModelCalling(status) {
|
|
1230
|
+
var wrap = document.getElementById('model-calling-wrap');
|
|
1231
|
+
if (!wrap) return;
|
|
1232
|
+
var tiers = status.tiers || [];
|
|
1233
|
+
|
|
1234
|
+
// 收集所有带 concurrency 诊断的模型
|
|
1235
|
+
var models = [];
|
|
1236
|
+
for (var i = 0; i < tiers.length; i++) {
|
|
1237
|
+
var tier = tiers[i];
|
|
1238
|
+
var diag = tier.diagnostics || {};
|
|
1239
|
+
var conc = diag.concurrency;
|
|
1240
|
+
if (!conc) continue;
|
|
1241
|
+
var names = Object.keys(conc);
|
|
1242
|
+
for (var j = 0; j < names.length; j++) {
|
|
1243
|
+
var model = names[j];
|
|
1244
|
+
var d = conc[model];
|
|
1245
|
+
models.push({
|
|
1246
|
+
vendor: tier.name,
|
|
1247
|
+
model: model,
|
|
1248
|
+
limit: d.limit || 0,
|
|
1249
|
+
in_use: d.in_use || 0,
|
|
1250
|
+
available: d.available || 0,
|
|
1251
|
+
pending: d.pending || 0,
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
if (!models.length) {
|
|
1257
|
+
wrap.innerHTML = '<div class="mc-empty">无活跃模型调用</div>';
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
var html = '<div class="mc-grid">';
|
|
1262
|
+
for (var k = 0; k < models.length; k++) {
|
|
1263
|
+
var m = models[k];
|
|
1264
|
+
var pct = m.limit > 0 ? Math.round((m.in_use / m.limit) * 100) : 0;
|
|
1265
|
+
var barClass = pct <= 50 ? 'mc-low' : (pct <= 80 ? 'mc-mid' : 'mc-high');
|
|
1266
|
+
|
|
1267
|
+
html += '<div class="mc-model-row">'
|
|
1268
|
+
+ '<span class="mc-model-name">' + escapeHtml(m.vendor + '/' + m.model) + '</span>'
|
|
1269
|
+
+ '<div class="mc-bar-wrap"><div class="mc-bar-fill ' + barClass + '" style="width:' + pct + '%"></div></div>'
|
|
1270
|
+
+ '<div class="mc-stats">'
|
|
1271
|
+
+ '<span class="mc-badge mc-badge-active">' + m.in_use + '/' + m.limit + '</span>'
|
|
1272
|
+
+ (m.pending > 0 ? '<span class="mc-badge mc-badge-pending">⏳ ' + m.pending + '</span>' : '')
|
|
1273
|
+
+ '</div>'
|
|
1274
|
+
+ '</div>';
|
|
1275
|
+
}
|
|
1276
|
+
html += '</div>';
|
|
1277
|
+
wrap.innerHTML = html;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// Model Calling 独立短间隔轮询
|
|
1281
|
+
var _mcTimer = null;
|
|
1282
|
+
function startModelCallingPoll() {
|
|
1283
|
+
stopModelCallingPoll();
|
|
1284
|
+
function tick() {
|
|
1285
|
+
fetchJSON('/api/status').then(function(status) {
|
|
1286
|
+
updateModelCalling(status);
|
|
1287
|
+
}).catch(function() {});
|
|
1288
|
+
}
|
|
1289
|
+
tick();
|
|
1290
|
+
_mcTimer = setInterval(tick, 5000);
|
|
1291
|
+
}
|
|
1292
|
+
function stopModelCallingPoll() {
|
|
1293
|
+
if (_mcTimer) { clearInterval(_mcTimer); _mcTimer = null; }
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1137
1296
|
// ── 按 tiers 顺序排序 vendor 列表 ─────────────────────────
|
|
1138
1297
|
function sortByTierOrder(vendors, tierOrder) {
|
|
1139
1298
|
if (!tierOrder || !tierOrder.length) return vendors.sort();
|
|
@@ -1713,6 +1872,7 @@ async function refreshOverview() {
|
|
|
1713
1872
|
|
|
1714
1873
|
updateKPI(summary);
|
|
1715
1874
|
updateVendorStatus(status);
|
|
1875
|
+
updateModelCalling(status);
|
|
1716
1876
|
updateChartTitles(days);
|
|
1717
1877
|
|
|
1718
1878
|
const rows = timeline.rows || [];
|
|
@@ -1788,6 +1948,8 @@ function switchTab(name) {
|
|
|
1788
1948
|
currentTab = name;
|
|
1789
1949
|
applyTabState(name);
|
|
1790
1950
|
syncTabUrl(name);
|
|
1951
|
+
// Model Calling 轮询随页签切换启停
|
|
1952
|
+
if (name === 'overview') { startModelCallingPoll(); } else { stopModelCallingPoll(); }
|
|
1791
1953
|
refresh();
|
|
1792
1954
|
}
|
|
1793
1955
|
|
|
@@ -1807,6 +1969,7 @@ function switchTab(name) {
|
|
|
1807
1969
|
}).catch(function(){});
|
|
1808
1970
|
refresh(); // 仅加载初始页签的数据
|
|
1809
1971
|
setInterval(refresh, 600000); // 每 10 分钟刷新当前页签
|
|
1972
|
+
if (initial === 'overview') startModelCallingPoll();
|
|
1810
1973
|
})();
|
|
1811
1974
|
</script>
|
|
1812
1975
|
</body>
|
|
@@ -67,10 +67,15 @@ class ModelConcurrencyLimiter:
|
|
|
67
67
|
limit = self._config.get_limit(model)
|
|
68
68
|
# asyncio.Semaphore 内部 _value 表示剩余可用槽位
|
|
69
69
|
available = sem._value # noqa: SLF001 — 公开 API 未暴露
|
|
70
|
+
in_use = max(limit - available, 0)
|
|
71
|
+
# _waiters 为正在排队等待的协程集合,无等待者时为 None
|
|
72
|
+
waiters = getattr(sem, "_waiters", None) # noqa: SLF001
|
|
73
|
+
pending = len(waiters) if waiters else 0
|
|
70
74
|
snapshot[model] = {
|
|
71
75
|
"limit": limit,
|
|
72
|
-
"in_use":
|
|
76
|
+
"in_use": in_use,
|
|
73
77
|
"available": max(available, 0),
|
|
78
|
+
"pending": pending,
|
|
74
79
|
}
|
|
75
80
|
return snapshot
|
|
76
81
|
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
"""智谱 GLM 供应商 — 原生 Anthropic
|
|
1
|
+
"""智谱 GLM 供应商 — 原生 Anthropic 兼容端点代理(兼容转换 + 429 重试).
|
|
2
2
|
|
|
3
|
-
官方端点 (https://open.bigmodel.cn/api/anthropic)
|
|
4
|
-
Anthropic Messages API
|
|
3
|
+
官方端点 (https://open.bigmodel.cn/api/anthropic) 支持大部分
|
|
4
|
+
Anthropic Messages API 协议,本模块做以下适配:
|
|
5
5
|
1. 模型名映射(Claude -> GLM)
|
|
6
6
|
2. 认证头替换(x-api-key)
|
|
7
|
+
3. 首选 tier 参数兼容转换(_prepare_request)
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
- thinking
|
|
9
|
+
实测验证 GLM 对 Anthropic 扩展参数的处理方式:
|
|
10
|
+
- thinking.type="enabled":原生支持(GLM 有自己的 thinking 机制)
|
|
11
|
+
- thinking.type="adaptive":不支持,触发 [1210] 参数错误 → 转换为 enabled + budget
|
|
10
12
|
- cache_control 字段:静默忽略(GLM 使用隐式自动缓存)
|
|
11
13
|
- reasoning_effort 参数:静默忽略
|
|
12
|
-
|
|
14
|
+
- metadata 字段:暂不处理(待进一步诊断确认兼容性)
|
|
13
15
|
|
|
14
16
|
额外提供 429 Rate Limit 专用重试挽回机制:
|
|
15
17
|
- max_attempt = 5(1 初始 + 4 重试)
|
|
@@ -20,6 +22,7 @@ Anthropic Messages API 协议,本模块仅做两项最小适配:
|
|
|
20
22
|
from __future__ import annotations
|
|
21
23
|
|
|
22
24
|
import asyncio
|
|
25
|
+
import json
|
|
23
26
|
import logging
|
|
24
27
|
from collections.abc import AsyncIterator
|
|
25
28
|
from typing import Any
|
|
@@ -76,6 +79,49 @@ class ZhipuVendor(NativeAnthropicVendor):
|
|
|
76
79
|
else None
|
|
77
80
|
)
|
|
78
81
|
|
|
82
|
+
# ── 首选 tier 参数兼容转换 ────────────────────────────────
|
|
83
|
+
|
|
84
|
+
# adaptive thinking → enabled 的默认预算(Anthropic 推荐的 adaptive 等价值)
|
|
85
|
+
_ADAPTIVE_THINKING_BUDGET = 16000
|
|
86
|
+
|
|
87
|
+
async def _prepare_request(
|
|
88
|
+
self,
|
|
89
|
+
request_body: dict[str, Any],
|
|
90
|
+
headers: dict[str, Any],
|
|
91
|
+
) -> tuple[dict[str, Any], dict[str, str]]:
|
|
92
|
+
"""深拷贝 + 模型映射 + 认证头替换 + GLM 兼容转换.
|
|
93
|
+
|
|
94
|
+
当 zhipu 作为首选 tier 时(source_vendor=None),请求体来自原始客户端,
|
|
95
|
+
不经过跨供应商转换通道。此处对已知的 GLM 不兼容参数做兼容转换(而非移除),
|
|
96
|
+
保留完整的 CC (Claude Code) 功能特性。
|
|
97
|
+
"""
|
|
98
|
+
body, new_headers = await super()._prepare_request(request_body, headers)
|
|
99
|
+
|
|
100
|
+
adaptations: list[str] = []
|
|
101
|
+
|
|
102
|
+
# thinking.type="adaptive" 是 Anthropic Claude 4.x 新增的类型,
|
|
103
|
+
# GLM 不支持此类型值,会触发 [1210] 参数错误。
|
|
104
|
+
# 转换为 enabled + budget 保留 thinking 能力。
|
|
105
|
+
thinking = body.get("thinking")
|
|
106
|
+
if isinstance(thinking, dict) and thinking.get("type") == "adaptive":
|
|
107
|
+
body["thinking"] = {
|
|
108
|
+
"type": "enabled",
|
|
109
|
+
"budget_tokens": self._ADAPTIVE_THINKING_BUDGET,
|
|
110
|
+
}
|
|
111
|
+
adaptations.append(
|
|
112
|
+
f"converted_thinking_adaptive→enabled"
|
|
113
|
+
f"(budget={self._ADAPTIVE_THINKING_BUDGET})"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if adaptations:
|
|
117
|
+
logger.debug(
|
|
118
|
+
"ZhipuVendor first-tier compat: %s%s",
|
|
119
|
+
", ".join(adaptations),
|
|
120
|
+
_build_zhipu_request_snapshot(body),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return body, new_headers
|
|
124
|
+
|
|
79
125
|
# ── 非流式:429 重试 ────────────────────────────────────
|
|
80
126
|
|
|
81
127
|
async def send_message(
|
|
@@ -206,6 +252,15 @@ class ZhipuVendor(NativeAnthropicVendor):
|
|
|
206
252
|
return None
|
|
207
253
|
return await self._concurrency_limiter.acquire(mapped_model)
|
|
208
254
|
|
|
255
|
+
# ── 诊断信息 ─────────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
def get_diagnostics(self) -> dict[str, Any]:
|
|
258
|
+
"""返回供应商运行时诊断信息,包含每模型并发状态."""
|
|
259
|
+
diagnostics = super().get_diagnostics()
|
|
260
|
+
if self._concurrency_limiter is not None:
|
|
261
|
+
diagnostics["concurrency"] = self._concurrency_limiter.get_diagnostics()
|
|
262
|
+
return diagnostics
|
|
263
|
+
|
|
209
264
|
# ── 延迟计算 ────────────────────────────────────────────
|
|
210
265
|
|
|
211
266
|
def _compute_retry_delay_from_headers(
|
|
@@ -239,3 +294,39 @@ class ZhipuVendor(NativeAnthropicVendor):
|
|
|
239
294
|
|
|
240
295
|
# 向后兼容别名
|
|
241
296
|
ZhipuBackend = ZhipuVendor
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _build_zhipu_request_snapshot(body: dict[str, Any]) -> str:
|
|
300
|
+
"""构建发往 zhipu 请求的轻量参数快照,用于诊断日志.
|
|
301
|
+
|
|
302
|
+
输出格式与 executor._build_semantic_rejection_diagnostic 一致,
|
|
303
|
+
使成功请求和失败请求的日志可直接 diff 对比,定位差异维度。
|
|
304
|
+
|
|
305
|
+
仅在转换发生时输出(DEBUG 级别),避免常态化日志噪声。
|
|
306
|
+
"""
|
|
307
|
+
parts: list[str] = []
|
|
308
|
+
parts.append(f"messages={len(body.get('messages', []))}")
|
|
309
|
+
|
|
310
|
+
thinking = body.get("thinking")
|
|
311
|
+
if isinstance(thinking, dict):
|
|
312
|
+
parts.append(f"thinking_type={thinking.get('type', 'unknown')}")
|
|
313
|
+
|
|
314
|
+
metadata = body.get("metadata")
|
|
315
|
+
if isinstance(metadata, dict) and metadata:
|
|
316
|
+
parts.append(f"metadata_keys={len(metadata)}")
|
|
317
|
+
|
|
318
|
+
tools = body.get("tools")
|
|
319
|
+
if isinstance(tools, list):
|
|
320
|
+
parts.append(f"tools={len(tools)}")
|
|
321
|
+
|
|
322
|
+
system = body.get("system")
|
|
323
|
+
if isinstance(system, list):
|
|
324
|
+
parts.append(f"system_blocks={len(system)}")
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
body_bytes = len(json.dumps(body, ensure_ascii=False).encode("utf-8"))
|
|
328
|
+
parts.append(f"body_bytes={body_bytes}")
|
|
329
|
+
except (TypeError, ValueError):
|
|
330
|
+
pass
|
|
331
|
+
|
|
332
|
+
return f" [{', '.join(parts)}]" if parts else ""
|
|
@@ -396,7 +396,7 @@ async def test_zhipu_prepare_request_preserves_metadata():
|
|
|
396
396
|
|
|
397
397
|
@pytest.mark.asyncio
|
|
398
398
|
async def test_zhipu_prepare_request_preserves_thinking():
|
|
399
|
-
"""ZhipuVendor._prepare_request 应原样保留 thinking
|
|
399
|
+
"""ZhipuVendor._prepare_request 应原样保留 thinking.type=enabled(GLM 原生支持)."""
|
|
400
400
|
mapper = ModelMapper([])
|
|
401
401
|
zhipu_vendor = ZhipuVendor(ZhipuConfig(api_key="sk-test"), mapper)
|
|
402
402
|
body = {
|
|
@@ -405,12 +405,35 @@ async def test_zhipu_prepare_request_preserves_thinking():
|
|
|
405
405
|
"thinking": {"type": "enabled", "budget_tokens": 10000},
|
|
406
406
|
}
|
|
407
407
|
prepared_body, _ = await zhipu_vendor._prepare_request(body, {})
|
|
408
|
-
# thinking 原样透传(GLM
|
|
408
|
+
# thinking.type=enabled 原样透传(GLM 原生支持)
|
|
409
409
|
assert prepared_body["thinking"] == {"type": "enabled", "budget_tokens": 10000}
|
|
410
410
|
# 原始 body 不应被修改
|
|
411
411
|
assert body["thinking"]["budget_tokens"] == 10000
|
|
412
412
|
|
|
413
413
|
|
|
414
|
+
@pytest.mark.asyncio
|
|
415
|
+
async def test_zhipu_prepare_request_converts_thinking_adaptive():
|
|
416
|
+
"""ZhipuVendor._prepare_request 应将 thinking.type=adaptive 转换为 enabled+budget.
|
|
417
|
+
|
|
418
|
+
GLM 不支持 adaptive 类型,转换为已确认安全的 enabled + budget_tokens 格式,
|
|
419
|
+
保留 thinking 能力不被阉割。
|
|
420
|
+
"""
|
|
421
|
+
mapper = ModelMapper([])
|
|
422
|
+
zhipu_vendor = ZhipuVendor(ZhipuConfig(api_key="sk-test"), mapper)
|
|
423
|
+
body = {
|
|
424
|
+
"model": "claude-opus-4-7",
|
|
425
|
+
"messages": [],
|
|
426
|
+
"thinking": {"type": "adaptive"},
|
|
427
|
+
}
|
|
428
|
+
prepared_body, _ = await zhipu_vendor._prepare_request(body, {})
|
|
429
|
+
|
|
430
|
+
# adaptive 应被转换为 enabled + budget
|
|
431
|
+
assert prepared_body["thinking"]["type"] == "enabled"
|
|
432
|
+
assert prepared_body["thinking"]["budget_tokens"] == 16000
|
|
433
|
+
# 原始 body 不应被修改
|
|
434
|
+
assert body["thinking"] == {"type": "adaptive"}
|
|
435
|
+
|
|
436
|
+
|
|
414
437
|
@pytest.mark.asyncio
|
|
415
438
|
async def test_zhipu_prepare_request_preserves_anthropic_beta_header():
|
|
416
439
|
zhipu_vendor = ZhipuVendor(ZhipuConfig(api_key="sk-test"), ModelMapper([]))
|