coding-proxy 0.5.1a1__tar.gz → 0.5.1a3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/AGENTS.md +4 -4
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/PKG-INFO +1 -1
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/pyproject.toml +1 -1
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/config/config.default.yaml +6 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/convert/vendor_channels.py +125 -1
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/server/dashboard.py +11 -7
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/server/routes.py +7 -3
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/e2e/test_e2e_http.py +5 -2
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_app_routes.py +5 -4
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_vendor_channels.py +218 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/uv.lock +1 -1
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/.github/workflows/ci.yml +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/.github/workflows/coverage.yml +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/.github/workflows/release.yml +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/.gitignore +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/.pre-commit-config.yaml +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/CHANGELOG.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/CLAUDE.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/LICENSE +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/README.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/assets/dashboard-v0.4.0.png +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/assets/model-calling-v0.5.0.png +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/assets/session-v0.4.0.png +0 -0
- {coding_proxy-0.5.1a1/docs/agents → coding_proxy-0.5.1a3/docs/.agents}/browser-validation.md +0 -0
- {coding_proxy-0.5.1a1/docs/agents → coding_proxy-0.5.1a3/docs/.agents}/issue.md +0 -0
- {coding_proxy-0.5.1a1/docs/agents → coding_proxy-0.5.1a3/docs/.agents}/knowledge-map.md +0 -0
- {coding_proxy-0.5.1a1/docs/agents → coding_proxy-0.5.1a3/docs/.agents}/reference-specifications.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/docs/arch/config-reference.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/docs/arch/convert.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/docs/arch/design-patterns.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/docs/arch/routing.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/docs/arch/testing.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/docs/arch/vendors.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/docs/framework.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/docs/guide/api-reference.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/docs/guide/cli-reference.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/docs/guide/dashboard.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/docs/guide/monitoring.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/docs/guide/quickstart.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/docs/guide/vendors.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/docs/ops/ci-cd.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/docs/user-guide.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/docs/zh-CN/README.md +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/__main__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/auth/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/auth/providers/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/auth/providers/base.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/auth/providers/github.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/auth/providers/google.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/auth/runtime.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/auth/store.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/cli/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/cli/auth_commands.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/cli/banner.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/compat/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/compat/canonical.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/compat/session_store.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/config/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/config/auth_schema.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/config/loader.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/config/resiliency.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/config/routing.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/config/schema.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/config/server.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/config/session_policy.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/config/vendors.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/convert/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/convert/anthropic_to_gemini.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/convert/anthropic_to_openai.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/convert/gemini_sse_adapter.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/convert/gemini_to_anthropic.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/convert/openai_to_anthropic.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/logging/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/logging/db.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/logging/formatters.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/logging/stats.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/model/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/model/auth.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/model/compat.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/model/constants.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/model/pricing.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/model/token.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/model/vendor.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/native_api/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/native_api/config.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/native_api/extractors/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/native_api/extractors/anthropic.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/native_api/extractors/gemini.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/native_api/extractors/openai.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/native_api/handler.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/native_api/operation.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/native_api/routes.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/native_api/usage_registry.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/pricing.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/routing/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/routing/circuit_breaker.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/routing/error_classifier.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/routing/executor.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/routing/model_mapper.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/routing/quota_guard.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/routing/rate_limit.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/routing/retry.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/routing/router.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/routing/session_manager.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/routing/session_policy.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/routing/tier.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/routing/usage_parser.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/routing/usage_recorder.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/server/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/server/app.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/server/factory.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/server/responses.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/streaming/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/streaming/anthropic_compat.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/alibaba.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/anthropic.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/antigravity.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/base.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/concurrency.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/copilot.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/copilot_models.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/copilot_token_manager.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/copilot_urls.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/doubao.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/kimi.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/minimax.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/mixins.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/native_anthropic.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/token_manager.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/xiaomi.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/src/coding/proxy/vendors/zhipu.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/e2e/__init__.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/e2e/conftest.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/e2e/test_e2e_token.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/e2e/test_e2e_vendor.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_antigravity.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_auto_login.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_banner.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_circuit_breaker.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_cli_usage.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_compat.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_concurrency_monitor.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_config_init.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_config_loader.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_convert_request.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_convert_response.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_convert_sse.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_copilot.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_copilot_convert_request.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_copilot_convert_response.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_copilot_models.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_copilot_urls.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_currency.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_error_classifier.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_executor_in_flight_tracking.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_logging_dual_write.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_mixins.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_model_auth.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_model_compat.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_model_constants.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_model_mapper.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_model_pricing.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_model_token.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_model_vendor.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_native_api_base_url_override.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_native_api_extractors.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_native_api_handler.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_native_api_operation.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_native_api_routes.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_native_vendors.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_parse_usage.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_parse_usage_gemini.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_pricing.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_quota_guard.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_rate_limit.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_router_chain.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_router_executor.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_runtime_reauth.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_schema.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_session_aware.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_streaming_anthropic_compat.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_tier.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_tiers_config.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_time_range.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_token_logger.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_token_logger_native_columns.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_token_manager.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_types.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_vendor_streaming.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_vendors.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_zhipu.py +0 -0
- {coding_proxy-0.5.1a1 → coding_proxy-0.5.1a3}/tests/test_zhipu_concurrency.py +0 -0
|
@@ -39,15 +39,15 @@
|
|
|
39
39
|
3. **Link Validity**: 确保所有引用的 URL 可访问且具备明确的上下文价值;
|
|
40
40
|
4. **Testing**: 统一在 tests/ 下维护测试用例,区分单元测试(unit)和集成测试(integration),所有测试的本地运行总时间控制在 3 min 以内;
|
|
41
41
|
5. **Pre-commit Hooks**: 首次克隆仓库使用 `uv run pre-commit install` 激活本地 Git hooks,使 Ruff lint(含 auto-fix)、Ruff format 及通用代码卫生检查在每次 commit 前自动运行。若 hooks 自动修复了问题,提交会被中断,执行 `git add -p` 审阅修复内容后重新提交即可;
|
|
42
|
-
6. **Issue**: 在 [issue.md](docs
|
|
42
|
+
6. **Issue**: 在 [issue.md](docs/.agents/issue.md) 中维护你处理过的 Issue 摘要(问题描述、表因根因、处理方式、后续防范、同类问题影响与处理注意事项等),便于同类问题的跨上下文处理;注意识别相同 Issue,不要同 Issue 多处维护;
|
|
43
43
|
- **Package Management Standardization (包管理规范)**:
|
|
44
44
|
1. **Python**: 严禁使用 pip/poetry,**必须**统一使用 `uv` 进行包管理与脚本执行(如 `uv run`);
|
|
45
45
|
2. **JavaScript/TypeScript**: 严禁使用 npm/yarn,**必须**统一使用 `pnpm` 进行包管理与脚本执行;
|
|
46
46
|
- **Database Management**: 谨慎操作,数据迁移、测试等操作严禁将现有数据删除,谨慎操作数据迁移的回滚,防止数据被清理。
|
|
47
47
|
- **In-depth and close to the facts**:系统且全面地进行问题的分析,深入贴近事实,如有疑问,需先发问,不要乱做决定。
|
|
48
|
-
- **Browser Validation Protocol (浏览器验证准则)**:Agent 不得自行完成、绕过或模拟任何 OAuth / SSO 认证流程,所有登录态均来源于用户已认证的 Chrome 主 profile(真实用户登录态)。完整协议(连通性自检、凭证管理、E2E 集成、实机回归等)详见 [浏览器验证协议](./docs
|
|
48
|
+
- **Browser Validation Protocol (浏览器验证准则)**:Agent 不得自行完成、绕过或模拟任何 OAuth / SSO 认证流程,所有登录态均来源于用户已认证的 Chrome 主 profile(真实用户登录态)。完整协议(连通性自检、凭证管理、E2E 集成、实机回归等)详见 [浏览器验证协议](./docs/.agents/browser-validation.md);
|
|
49
49
|
1. **安全红线**:禁止在 Sandbox 浏览器中跳转 Google 同意屏;禁止以模拟用户或第三方账号替代真实登录态;禁止要求用户在 chat 中粘贴密码、Cookie 或验证码;
|
|
50
|
-
- **Knowledge Map (知识索引)**:项目所有文档索引统一维护在 [知识索引](./docs
|
|
50
|
+
- **Knowledge Map (知识索引)**:项目所有文档索引统一维护在 [知识索引](./docs/.agents/knowledge-map.md),并在文档目录变更时即时同步跟新;
|
|
51
51
|
- **Documentation Standards (文档规范)**:
|
|
52
52
|
1. **Visual Documentation (图文并茂)**: 对于复杂逻辑,优先 **Mermaid Visualization Norms (Mermaid 可视化规范)**,构建”图文并茂”的直观文档;
|
|
53
53
|
- **色彩语义与兼容性**:为图表节点配置具备语义辨识度的色彩,并确保在深色模式(Dark Mode)下具有极高的对比度与清晰度;
|
|
@@ -55,4 +55,4 @@
|
|
|
55
55
|
2. **语言叙事**:用语精准,叙事完备,行文专业,聚焦核心,篇幅精炼,形象具体,体现真实作用与用户吸引性,字数恰当;
|
|
56
56
|
3. **Direct Hyperlinking (直接跳转)**: 在文档中提及 Repo 内其他资源(文档/代码)时,**必须**构建可跳转的相对路径链接(如 `[Doc Name](./path.md)`),严禁使用”死文本”引用,以降低信息检索熵;
|
|
57
57
|
4. **实操截图**:文档需要引入必要的浏览器实操截图时,需自行通过默认浏览器打开相关页面,通过实操现场截图并保留到文档路径进行文档引用;
|
|
58
|
-
- **Reference Specifications (IEEE)**:为保障工程决策的可追溯性与学术严谨性,核心引用需遵循 [reference-specifications.md](docs
|
|
58
|
+
- **Reference Specifications (IEEE)**:为保障工程决策的可追溯性与学术严谨性,核心引用需遵循 [reference-specifications.md](docs/.agents/reference-specifications.md)IEEE 标准引用格式;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coding-proxy
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1a3
|
|
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.5.
|
|
3
|
+
version = "0.5.1a3"
|
|
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"
|
|
@@ -339,6 +339,12 @@ model_mapping:
|
|
|
339
339
|
# 未配置定价的模型在统计中 Cost 列显示 "-"
|
|
340
340
|
pricing:
|
|
341
341
|
# ── Anthropic Claude ──
|
|
342
|
+
- vendor: anthropic
|
|
343
|
+
model: claude-opus-4-8
|
|
344
|
+
input_cost_per_mtok: $5.0
|
|
345
|
+
output_cost_per_mtok: $25.0
|
|
346
|
+
cache_write_cost_per_mtok: $6.25
|
|
347
|
+
cache_read_cost_per_mtok: $0.50
|
|
342
348
|
- vendor: anthropic
|
|
343
349
|
model: claude-opus-4-7
|
|
344
350
|
input_cost_per_mtok: $5.0
|
|
@@ -52,6 +52,117 @@ def get_transition_channel(
|
|
|
52
52
|
# ── 共享辅助函数 ──────────────────────────────────────────────
|
|
53
53
|
|
|
54
54
|
|
|
55
|
+
def _dump_message_digest(
|
|
56
|
+
messages: list[dict[str, Any]],
|
|
57
|
+
*,
|
|
58
|
+
max_messages: int = 10,
|
|
59
|
+
label: str = "",
|
|
60
|
+
) -> None:
|
|
61
|
+
"""输出前 N 条消息的结构摘要(role + content_type_counts),用于过渡管线诊断.
|
|
62
|
+
|
|
63
|
+
仅在 DEBUG 级别输出,且仅在消息数 > 0 时才输出,避免噪声。
|
|
64
|
+
"""
|
|
65
|
+
if not messages or not logger.isEnabledFor(logging.DEBUG):
|
|
66
|
+
return
|
|
67
|
+
parts: list[str] = [f"[{label}]" if label else ""]
|
|
68
|
+
limit = min(len(messages), max_messages)
|
|
69
|
+
for idx in range(limit):
|
|
70
|
+
msg = messages[idx]
|
|
71
|
+
role = msg.get("role", "?") if isinstance(msg, dict) else "?"
|
|
72
|
+
content = msg.get("content") if isinstance(msg, dict) else None
|
|
73
|
+
if isinstance(content, list):
|
|
74
|
+
type_counts: dict[str, int] = {}
|
|
75
|
+
for b in content:
|
|
76
|
+
if isinstance(b, dict):
|
|
77
|
+
t = b.get("type", "?")
|
|
78
|
+
type_counts[t] = type_counts.get(t, 0) + 1
|
|
79
|
+
else:
|
|
80
|
+
type_counts["raw"] = type_counts.get("raw", 0) + 1
|
|
81
|
+
counts_str = ",".join(f"{t}:{c}" for t, c in sorted(type_counts.items()))
|
|
82
|
+
elif isinstance(content, str):
|
|
83
|
+
counts_str = f"str({len(content)})"
|
|
84
|
+
else:
|
|
85
|
+
counts_str = "empty"
|
|
86
|
+
parts.append(f"{idx}:{role}[{counts_str}]")
|
|
87
|
+
if len(messages) > max_messages:
|
|
88
|
+
parts.append(f"...+{len(messages) - max_messages}more")
|
|
89
|
+
logger.debug("Transition digest %s", " ".join(parts))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _validate_anthropic_pairing(
|
|
93
|
+
messages: list[dict[str, Any]],
|
|
94
|
+
*,
|
|
95
|
+
context: str = "",
|
|
96
|
+
) -> list[str]:
|
|
97
|
+
"""独立的 Anthropic tool_use/tool_result 配对自检(过渡管线末端执行).
|
|
98
|
+
|
|
99
|
+
与 ``_enforce_pairing_sanity_pass`` 不同,此函数:
|
|
100
|
+
- 不修改消息列表(纯检测)
|
|
101
|
+
- 针对每个 assistant + tool_use,精确记录下一条 user 消息中匹配/缺失的 ID
|
|
102
|
+
- 发现不一致时输出 WARNING 级别日志含 message index 与具体 ID
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
检测到的问题描述列表(空列表表示全部通过)。
|
|
106
|
+
"""
|
|
107
|
+
issues: list[str] = []
|
|
108
|
+
for i, msg in enumerate(messages):
|
|
109
|
+
if not isinstance(msg, dict) or msg.get("role") != "assistant":
|
|
110
|
+
continue
|
|
111
|
+
content = msg.get("content")
|
|
112
|
+
if not isinstance(content, list):
|
|
113
|
+
continue
|
|
114
|
+
tool_use_ids = [
|
|
115
|
+
b["id"]
|
|
116
|
+
for b in content
|
|
117
|
+
if isinstance(b, dict) and b.get("type") == "tool_use" and b.get("id")
|
|
118
|
+
]
|
|
119
|
+
if not tool_use_ids:
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
next_idx = i + 1
|
|
123
|
+
if next_idx >= len(messages):
|
|
124
|
+
issues.append(f"messages[{i}]: assistant with tool_uses at end of list")
|
|
125
|
+
continue
|
|
126
|
+
|
|
127
|
+
next_msg = messages[next_idx]
|
|
128
|
+
if not isinstance(next_msg, dict) or next_msg.get("role") != "user":
|
|
129
|
+
issues.append(
|
|
130
|
+
f"messages[{i}]: next messages[{next_idx}] is not user "
|
|
131
|
+
f"(role={next_msg.get('role') if isinstance(next_msg, dict) else '?'})"
|
|
132
|
+
)
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
user_content = next_msg.get("content")
|
|
136
|
+
if not isinstance(user_content, list):
|
|
137
|
+
user_content = []
|
|
138
|
+
|
|
139
|
+
result_ids = {
|
|
140
|
+
b["tool_use_id"]
|
|
141
|
+
for b in user_content
|
|
142
|
+
if isinstance(b, dict)
|
|
143
|
+
and b.get("type") == "tool_result"
|
|
144
|
+
and isinstance(b.get("tool_use_id"), str)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
missing = [uid for uid in tool_use_ids if uid not in result_ids]
|
|
148
|
+
if missing:
|
|
149
|
+
issue = (
|
|
150
|
+
f"messages[{i}]: {len(missing)}/{len(tool_use_ids)} tool_use(s) "
|
|
151
|
+
f"without tool_result in messages[{next_idx}]: {missing[:5]}"
|
|
152
|
+
)
|
|
153
|
+
issues.append(issue)
|
|
154
|
+
|
|
155
|
+
if issues:
|
|
156
|
+
prefix = f"[{context}] " if context else ""
|
|
157
|
+
logger.warning(
|
|
158
|
+
"Anthropic pairing validation: %s%d issue(s) found: %s",
|
|
159
|
+
prefix,
|
|
160
|
+
len(issues),
|
|
161
|
+
"; ".join(issues),
|
|
162
|
+
)
|
|
163
|
+
return issues
|
|
164
|
+
|
|
165
|
+
|
|
55
166
|
def strip_thinking_blocks(body: dict[str, Any]) -> int:
|
|
56
167
|
"""从 assistant 消息中移除 thinking/redacted_thinking 块(就地).
|
|
57
168
|
|
|
@@ -678,6 +789,7 @@ def prepare_zhipu_to_anthropic(
|
|
|
678
789
|
2. 改写 ``srvtoolu_*`` ID 与 ``server_tool_use`` 类型为标准 Anthropic 形式
|
|
679
790
|
3. 强制 tool_use/tool_result 配对(单遍正向扫描)
|
|
680
791
|
4. 剥离 thinking blocks(signature 无效)
|
|
792
|
+
5. 独立的 Anthropic 兼容性自检(纯检测,不修改,定位 enforce/sanity 未覆盖的边界 case)
|
|
681
793
|
|
|
682
794
|
所有变换均为幂等操作,安全地在已清理的请求体上重复执行。
|
|
683
795
|
|
|
@@ -686,6 +798,10 @@ def prepare_zhipu_to_anthropic(
|
|
|
686
798
|
"""
|
|
687
799
|
prepared = copy.deepcopy(body)
|
|
688
800
|
adaptations: list[str] = []
|
|
801
|
+
msgs = prepared.get("messages", [])
|
|
802
|
+
|
|
803
|
+
# ── 过渡管线诊断:变换前快照 ──
|
|
804
|
+
_dump_message_digest(msgs, label="zhipu→anthropic.before")
|
|
689
805
|
|
|
690
806
|
# Step 1: 剥离 zhipu 私有 content block 类型(如 server_tool_use_delta)
|
|
691
807
|
removed_vendor_blocks = _remove_vendor_blocks(prepared, _ZHIPU_VENDOR_BLOCK_TYPES)
|
|
@@ -696,16 +812,24 @@ def prepare_zhipu_to_anthropic(
|
|
|
696
812
|
rewritten, _ = _rewrite_srvtoolu_ids(prepared)
|
|
697
813
|
if rewritten:
|
|
698
814
|
adaptations.append(f"rewritten_{rewritten}_srvtoolu_ids")
|
|
815
|
+
_dump_message_digest(msgs, label="zhipu→anthropic.after_rewrite")
|
|
699
816
|
|
|
700
817
|
# Step 3: 强制 tool_use/tool_result 配对
|
|
701
|
-
pairing_fixes = enforce_anthropic_tool_pairing(
|
|
818
|
+
pairing_fixes = enforce_anthropic_tool_pairing(msgs)
|
|
702
819
|
if pairing_fixes:
|
|
703
820
|
adaptations.extend(pairing_fixes)
|
|
821
|
+
_dump_message_digest(msgs, label="zhipu→anthropic.after_enforce")
|
|
704
822
|
|
|
705
823
|
# Step 4: 剥离 thinking blocks(zhipu signature 无效)
|
|
706
824
|
stripped = strip_thinking_blocks(prepared)
|
|
707
825
|
if stripped:
|
|
708
826
|
adaptations.append(f"stripped_{stripped}_thinking_blocks")
|
|
827
|
+
_dump_message_digest(msgs, label="zhipu→anthropic.after_strip")
|
|
828
|
+
|
|
829
|
+
# Step 5: 独立的 Anthropic 兼容性自检(纯检测,不修改)
|
|
830
|
+
validation_issues = _validate_anthropic_pairing(msgs, context="zhipu→anthropic")
|
|
831
|
+
if validation_issues:
|
|
832
|
+
adaptations.append("anthropic_pairing_validation_issues")
|
|
709
833
|
|
|
710
834
|
return prepared, adaptations
|
|
711
835
|
|
|
@@ -89,6 +89,7 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
89
89
|
--shadow-md: 0 8px 24px rgba(0,0,0,.3);
|
|
90
90
|
--glow-blue: 0 0 0 1px rgba(88,166,255,.1), 0 8px 32px rgba(88,166,255,.04);
|
|
91
91
|
--gradient-primary: linear-gradient(135deg, #667eea, #764ba2);
|
|
92
|
+
--gap-section: 12px;
|
|
92
93
|
}
|
|
93
94
|
@keyframes fadeInUp {
|
|
94
95
|
from { opacity: 0; transform: translateY(10px); }
|
|
@@ -160,7 +161,7 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
160
161
|
display: grid;
|
|
161
162
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
162
163
|
gap: 5px;
|
|
163
|
-
margin-bottom:
|
|
164
|
+
margin-bottom: var(--gap-section);
|
|
164
165
|
}
|
|
165
166
|
.kpi-card {
|
|
166
167
|
background: rgba(18,22,30,.7);
|
|
@@ -214,13 +215,13 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
214
215
|
display: grid;
|
|
215
216
|
grid-template-columns: 1fr 2fr;
|
|
216
217
|
gap: 16px;
|
|
217
|
-
margin-bottom:
|
|
218
|
+
margin-bottom: var(--gap-section);
|
|
218
219
|
}
|
|
219
220
|
.charts-grid-2 {
|
|
220
221
|
display: grid;
|
|
221
222
|
grid-template-columns: 1fr 2fr;
|
|
222
223
|
gap: 16px;
|
|
223
|
-
margin-bottom:
|
|
224
|
+
margin-bottom: var(--gap-section);
|
|
224
225
|
}
|
|
225
226
|
.charts-grid > .card,
|
|
226
227
|
.charts-grid-2 > .card {
|
|
@@ -356,7 +357,7 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
356
357
|
/* ── 时间区间选择栏 ── */
|
|
357
358
|
.time-range-bar {
|
|
358
359
|
display: flex; align-items: center; gap: 8px;
|
|
359
|
-
margin-bottom:
|
|
360
|
+
margin-bottom: var(--gap-section); flex-wrap: wrap;
|
|
360
361
|
padding: 8px 16px;
|
|
361
362
|
background: rgba(18,22,30,.5);
|
|
362
363
|
border: 1px solid rgba(255,255,255,.04);
|
|
@@ -560,7 +561,10 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
560
561
|
|
|
561
562
|
/* ── Model Calling 实时状态 ────────────────────────── */
|
|
562
563
|
.model-calling-card {
|
|
563
|
-
margin-bottom:
|
|
564
|
+
margin-bottom: var(--gap-section);
|
|
565
|
+
}
|
|
566
|
+
.model-token-card {
|
|
567
|
+
margin-bottom: var(--gap-section);
|
|
564
568
|
}
|
|
565
569
|
.mc-empty {
|
|
566
570
|
text-align: center;
|
|
@@ -791,7 +795,7 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
791
795
|
</div>
|
|
792
796
|
|
|
793
797
|
<!-- Token 用量(按 Vendor / 模型)堆叠图 -->
|
|
794
|
-
<div class="card
|
|
798
|
+
<div class="card model-token-card">
|
|
795
799
|
<div class="card-title" id="title-model-token-timeline">近 7 天 Token 用量(按 Vendor / 模型)</div>
|
|
796
800
|
<div class="chart-with-legend">
|
|
797
801
|
<div class="chart-wrap-xl">
|
|
@@ -1346,7 +1350,7 @@ function startModelCallingPoll() {
|
|
|
1346
1350
|
}).catch(function() {});
|
|
1347
1351
|
}
|
|
1348
1352
|
tick();
|
|
1349
|
-
_mcTimer = setInterval(tick,
|
|
1353
|
+
_mcTimer = setInterval(tick, 10000);
|
|
1350
1354
|
}
|
|
1351
1355
|
function stopModelCallingPoll() {
|
|
1352
1356
|
if (_mcTimer) { clearInterval(_mcTimer); _mcTimer = null; }
|
|
@@ -8,7 +8,7 @@ from typing import Any
|
|
|
8
8
|
|
|
9
9
|
import httpx
|
|
10
10
|
from fastapi import Request, Response
|
|
11
|
-
from fastapi.responses import StreamingResponse
|
|
11
|
+
from fastapi.responses import RedirectResponse, StreamingResponse
|
|
12
12
|
|
|
13
13
|
from ..vendors.base import NoCompatibleVendorError
|
|
14
14
|
|
|
@@ -197,11 +197,15 @@ def register_health_routes(app: Any) -> None:
|
|
|
197
197
|
return {"status": "ok"}
|
|
198
198
|
|
|
199
199
|
@app.head("/")
|
|
200
|
-
|
|
201
|
-
async def root() -> Response:
|
|
200
|
+
async def root_head() -> Response:
|
|
202
201
|
"""根路径连通性探测 — Claude Code 在建连前发送 HEAD / 作为 health probe."""
|
|
203
202
|
return Response(status_code=200)
|
|
204
203
|
|
|
204
|
+
@app.get("/")
|
|
205
|
+
async def root_get() -> RedirectResponse:
|
|
206
|
+
"""GET / 重定向到 Dashboard."""
|
|
207
|
+
return RedirectResponse(url="/dashboard", status_code=307)
|
|
208
|
+
|
|
205
209
|
|
|
206
210
|
def register_status_route(app: Any, router: Any) -> None:
|
|
207
211
|
"""注册状态查询路由."""
|
|
@@ -188,13 +188,16 @@ async def test_http_health_probe(e2e_client: object) -> None:
|
|
|
188
188
|
)
|
|
189
189
|
|
|
190
190
|
get_resp = await e2e_client.get("/")
|
|
191
|
-
assert get_resp.status_code ==
|
|
191
|
+
assert get_resp.status_code == 307, f"GET / 预期 307,实际 {get_resp.status_code}"
|
|
192
|
+
assert get_resp.headers["location"] == "/dashboard"
|
|
192
193
|
|
|
193
194
|
health_resp = await e2e_client.get("/health")
|
|
194
195
|
assert health_resp.status_code == 200
|
|
195
196
|
assert health_resp.json() == {"status": "ok"}
|
|
196
197
|
|
|
197
|
-
print(
|
|
198
|
+
print(
|
|
199
|
+
"\n[E2E] HTTP health probe 成功: HEAD /=200, GET /=307→/dashboard, /health=ok"
|
|
200
|
+
)
|
|
198
201
|
|
|
199
202
|
|
|
200
203
|
@pytest.mark.e2e
|
|
@@ -35,11 +35,12 @@ def test_head_root_returns_200():
|
|
|
35
35
|
assert resp.status_code == 200
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
def
|
|
39
|
-
"""GET /
|
|
38
|
+
def test_get_root_redirects_to_dashboard():
|
|
39
|
+
"""GET / 重定向到 /dashboard."""
|
|
40
40
|
with _make_app() as client:
|
|
41
|
-
resp = client.get("/")
|
|
42
|
-
assert resp.status_code ==
|
|
41
|
+
resp = client.get("/", follow_redirects=False)
|
|
42
|
+
assert resp.status_code == 307
|
|
43
|
+
assert resp.headers["location"] == "/dashboard"
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
# ── count_tokens 透传 ────────────────────────────────────────
|
|
@@ -2225,3 +2225,221 @@ class TestNormalizeForZhipu:
|
|
|
2225
2225
|
assert result["stream"] is True
|
|
2226
2226
|
assert result["metadata"] == {"user_id": "test"}
|
|
2227
2227
|
assert adaptations == []
|
|
2228
|
+
|
|
2229
|
+
|
|
2230
|
+
class TestDumpMessageDigest:
|
|
2231
|
+
"""``_dump_message_digest`` 诊断快照函数测试."""
|
|
2232
|
+
|
|
2233
|
+
def test_outputs_nothing_on_empty_messages(self, caplog):
|
|
2234
|
+
import logging
|
|
2235
|
+
|
|
2236
|
+
from coding.proxy.convert.vendor_channels import _dump_message_digest
|
|
2237
|
+
|
|
2238
|
+
with caplog.at_level(
|
|
2239
|
+
logging.DEBUG, logger="coding.proxy.convert.vendor_channels"
|
|
2240
|
+
):
|
|
2241
|
+
_dump_message_digest([], label="test")
|
|
2242
|
+
assert "Transition digest" not in caplog.text
|
|
2243
|
+
|
|
2244
|
+
def test_outputs_structure_for_first_n_messages(self, caplog):
|
|
2245
|
+
import logging
|
|
2246
|
+
|
|
2247
|
+
from coding.proxy.convert.vendor_channels import _dump_message_digest
|
|
2248
|
+
|
|
2249
|
+
messages = [
|
|
2250
|
+
{"role": "user", "content": [{"type": "text", "text": "hi"}]},
|
|
2251
|
+
{
|
|
2252
|
+
"role": "assistant",
|
|
2253
|
+
"content": [
|
|
2254
|
+
{"type": "tool_use", "id": "toolu_1", "name": "bash", "input": {}},
|
|
2255
|
+
],
|
|
2256
|
+
},
|
|
2257
|
+
{
|
|
2258
|
+
"role": "user",
|
|
2259
|
+
"content": [
|
|
2260
|
+
{"type": "tool_result", "tool_use_id": "toolu_1", "content": "ok"},
|
|
2261
|
+
],
|
|
2262
|
+
},
|
|
2263
|
+
]
|
|
2264
|
+
with caplog.at_level(
|
|
2265
|
+
logging.DEBUG, logger="coding.proxy.convert.vendor_channels"
|
|
2266
|
+
):
|
|
2267
|
+
_dump_message_digest(messages, label="test")
|
|
2268
|
+
assert "test" in caplog.text
|
|
2269
|
+
assert "0:user" in caplog.text
|
|
2270
|
+
assert "1:assistant" in caplog.text
|
|
2271
|
+
assert "tool_use:1" in caplog.text
|
|
2272
|
+
|
|
2273
|
+
|
|
2274
|
+
class TestValidateAnthropicPairing:
|
|
2275
|
+
"""``_validate_anthropic_pairing`` 独立配对自检测试."""
|
|
2276
|
+
|
|
2277
|
+
def test_no_issues_for_correct_pairing(self):
|
|
2278
|
+
from coding.proxy.convert.vendor_channels import _validate_anthropic_pairing
|
|
2279
|
+
|
|
2280
|
+
messages = [
|
|
2281
|
+
{"role": "user", "content": "go"},
|
|
2282
|
+
{
|
|
2283
|
+
"role": "assistant",
|
|
2284
|
+
"content": [
|
|
2285
|
+
{"type": "tool_use", "id": "toolu_1", "name": "bash", "input": {}},
|
|
2286
|
+
],
|
|
2287
|
+
},
|
|
2288
|
+
{
|
|
2289
|
+
"role": "user",
|
|
2290
|
+
"content": [
|
|
2291
|
+
{"type": "tool_result", "tool_use_id": "toolu_1", "content": "ok"},
|
|
2292
|
+
],
|
|
2293
|
+
},
|
|
2294
|
+
]
|
|
2295
|
+
issues = _validate_anthropic_pairing(messages)
|
|
2296
|
+
assert issues == []
|
|
2297
|
+
|
|
2298
|
+
def test_detects_missing_tool_result(self):
|
|
2299
|
+
from coding.proxy.convert.vendor_channels import _validate_anthropic_pairing
|
|
2300
|
+
|
|
2301
|
+
messages = [
|
|
2302
|
+
{"role": "user", "content": "go"},
|
|
2303
|
+
{
|
|
2304
|
+
"role": "assistant",
|
|
2305
|
+
"content": [
|
|
2306
|
+
{"type": "tool_use", "id": "toolu_1", "name": "bash", "input": {}},
|
|
2307
|
+
],
|
|
2308
|
+
},
|
|
2309
|
+
{"role": "user", "content": [{"type": "text", "text": "no result"}]},
|
|
2310
|
+
]
|
|
2311
|
+
issues = _validate_anthropic_pairing(messages)
|
|
2312
|
+
assert len(issues) == 1
|
|
2313
|
+
assert "toolu_1" in issues[0]
|
|
2314
|
+
assert "messages[1]" in issues[0]
|
|
2315
|
+
|
|
2316
|
+
def test_detects_non_user_after_assistant_with_tool_use(self):
|
|
2317
|
+
from coding.proxy.convert.vendor_channels import _validate_anthropic_pairing
|
|
2318
|
+
|
|
2319
|
+
messages = [
|
|
2320
|
+
{"role": "user", "content": "go"},
|
|
2321
|
+
{
|
|
2322
|
+
"role": "assistant",
|
|
2323
|
+
"content": [
|
|
2324
|
+
{"type": "tool_use", "id": "toolu_1", "name": "bash", "input": {}},
|
|
2325
|
+
],
|
|
2326
|
+
},
|
|
2327
|
+
{
|
|
2328
|
+
"role": "assistant",
|
|
2329
|
+
"content": [{"type": "text", "text": "another assistant"}],
|
|
2330
|
+
},
|
|
2331
|
+
]
|
|
2332
|
+
issues = _validate_anthropic_pairing(messages)
|
|
2333
|
+
assert len(issues) == 1
|
|
2334
|
+
assert "not user" in issues[0]
|
|
2335
|
+
|
|
2336
|
+
def test_detects_assistant_with_tool_use_at_end_of_list(self):
|
|
2337
|
+
from coding.proxy.convert.vendor_channels import _validate_anthropic_pairing
|
|
2338
|
+
|
|
2339
|
+
messages = [
|
|
2340
|
+
{"role": "user", "content": "go"},
|
|
2341
|
+
{
|
|
2342
|
+
"role": "assistant",
|
|
2343
|
+
"content": [
|
|
2344
|
+
{"type": "tool_use", "id": "toolu_1", "name": "bash", "input": {}},
|
|
2345
|
+
],
|
|
2346
|
+
},
|
|
2347
|
+
]
|
|
2348
|
+
issues = _validate_anthropic_pairing(messages)
|
|
2349
|
+
assert len(issues) == 1
|
|
2350
|
+
assert "end of list" in issues[0]
|
|
2351
|
+
|
|
2352
|
+
def test_partial_missing_only_reports_missing_ids(self):
|
|
2353
|
+
from coding.proxy.convert.vendor_channels import _validate_anthropic_pairing
|
|
2354
|
+
|
|
2355
|
+
messages = [
|
|
2356
|
+
{"role": "user", "content": "go"},
|
|
2357
|
+
{
|
|
2358
|
+
"role": "assistant",
|
|
2359
|
+
"content": [
|
|
2360
|
+
{"type": "tool_use", "id": "toolu_1", "name": "bash", "input": {}},
|
|
2361
|
+
{"type": "tool_use", "id": "toolu_2", "name": "read", "input": {}},
|
|
2362
|
+
],
|
|
2363
|
+
},
|
|
2364
|
+
{
|
|
2365
|
+
"role": "user",
|
|
2366
|
+
"content": [
|
|
2367
|
+
{"type": "tool_result", "tool_use_id": "toolu_1", "content": "ok"},
|
|
2368
|
+
],
|
|
2369
|
+
},
|
|
2370
|
+
]
|
|
2371
|
+
issues = _validate_anthropic_pairing(messages)
|
|
2372
|
+
assert len(issues) == 1
|
|
2373
|
+
assert "toolu_2" in issues[0]
|
|
2374
|
+
assert "1/2" in issues[0]
|
|
2375
|
+
|
|
2376
|
+
def test_integration_with_zhipu_to_anthropic_channel(self):
|
|
2377
|
+
"""验证 prepare_zhipu_to_anthropic 在末端执行自检且 adaptations 包含标签."""
|
|
2378
|
+
from coding.proxy.convert.vendor_channels import prepare_zhipu_to_anthropic
|
|
2379
|
+
|
|
2380
|
+
body = {
|
|
2381
|
+
"model": "claude-opus-4-7",
|
|
2382
|
+
"messages": [
|
|
2383
|
+
{"role": "user", "content": "go"},
|
|
2384
|
+
{
|
|
2385
|
+
"role": "assistant",
|
|
2386
|
+
"content": [
|
|
2387
|
+
{
|
|
2388
|
+
"type": "server_tool_use",
|
|
2389
|
+
"id": "srvtoolu_01",
|
|
2390
|
+
"name": "bash",
|
|
2391
|
+
"input": {},
|
|
2392
|
+
},
|
|
2393
|
+
],
|
|
2394
|
+
},
|
|
2395
|
+
{
|
|
2396
|
+
"role": "user",
|
|
2397
|
+
"content": [
|
|
2398
|
+
{
|
|
2399
|
+
"type": "tool_result",
|
|
2400
|
+
"tool_use_id": "srvtoolu_01",
|
|
2401
|
+
"content": "ok",
|
|
2402
|
+
},
|
|
2403
|
+
],
|
|
2404
|
+
},
|
|
2405
|
+
],
|
|
2406
|
+
}
|
|
2407
|
+
result, adaptations = prepare_zhipu_to_anthropic(body)
|
|
2408
|
+
# 自检通过,不应包含 validation_issues 标签
|
|
2409
|
+
assert "anthropic_pairing_validation_issues" not in adaptations
|
|
2410
|
+
|
|
2411
|
+
def test_integration_detects_enforce_missed_issue(self):
|
|
2412
|
+
"""构造一个理论上 enforce 可能遗漏的场景,验证自检能捕获.
|
|
2413
|
+
|
|
2414
|
+
场景:两条连续 assistant 消息,第一条的 tool_result 被第二条的
|
|
2415
|
+
existing_result_ids"冒领"(相同 ID 碰撞场景的模拟)。
|
|
2416
|
+
虽然当前 enforce 实现下不太可能自然产生此场景,但自检应能捕获。
|
|
2417
|
+
"""
|
|
2418
|
+
from coding.proxy.convert.vendor_channels import (
|
|
2419
|
+
_validate_anthropic_pairing,
|
|
2420
|
+
)
|
|
2421
|
+
|
|
2422
|
+
# 手动构造一个 enforce 后仍存在配对缺陷的 body
|
|
2423
|
+
messages = [
|
|
2424
|
+
{"role": "user", "content": "go"},
|
|
2425
|
+
{
|
|
2426
|
+
"role": "assistant",
|
|
2427
|
+
"content": [
|
|
2428
|
+
{"type": "tool_use", "id": "toolu_x", "name": "bash", "input": {}},
|
|
2429
|
+
],
|
|
2430
|
+
},
|
|
2431
|
+
{
|
|
2432
|
+
"role": "user",
|
|
2433
|
+
"content": [
|
|
2434
|
+
# tool_result 缺失 tolu_x,但有不相关的 tool_result
|
|
2435
|
+
{
|
|
2436
|
+
"type": "tool_result",
|
|
2437
|
+
"tool_use_id": "toolu_other",
|
|
2438
|
+
"content": "wrong",
|
|
2439
|
+
},
|
|
2440
|
+
],
|
|
2441
|
+
},
|
|
2442
|
+
]
|
|
2443
|
+
issues = _validate_anthropic_pairing(messages)
|
|
2444
|
+
assert len(issues) == 1
|
|
2445
|
+
assert "toolu_x" in issues[0]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coding_proxy-0.5.1a1/docs/agents → coding_proxy-0.5.1a3/docs/.agents}/browser-validation.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coding_proxy-0.5.1a1/docs/agents → coding_proxy-0.5.1a3/docs/.agents}/reference-specifications.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|