coding-proxy 0.3.1a5__tar.gz → 0.3.1a6__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.1a5 → coding_proxy-0.3.1a6}/AGENTS.md +1 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/PKG-INFO +1 -1
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/issue.md +41 -13
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/pyproject.toml +1 -1
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/vendor_channels.py +24 -60
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_router_executor.py +12 -9
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_vendor_channels.py +64 -85
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/uv.lock +1 -1
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/.github/workflows/ci.yml +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/.github/workflows/coverage.yml +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/.github/workflows/release.yml +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/.gitignore +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/.pre-commit-config.yaml +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/CHANGELOG.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/CLAUDE.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/LICENSE +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/README.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/assets/dashboard-v0.2.4.png +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/arch/config-reference.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/arch/convert.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/arch/design-patterns.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/arch/routing.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/arch/testing.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/arch/vendors.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/ci-cd.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/framework.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/guide/api-reference.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/guide/cli-reference.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/guide/dashboard.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/guide/monitoring.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/guide/quickstart.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/guide/vendors.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/user-guide.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/zh-CN/README.md +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/__main__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/providers/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/providers/base.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/providers/github.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/providers/google.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/runtime.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/store.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/cli/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/cli/auth_commands.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/cli/banner.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/compat/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/compat/canonical.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/compat/session_store.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/auth_schema.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/config.default.yaml +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/loader.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/resiliency.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/routing.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/schema.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/server.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/vendors.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/anthropic_to_gemini.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/anthropic_to_openai.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/gemini_sse_adapter.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/gemini_to_anthropic.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/openai_to_anthropic.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/logging/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/logging/db.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/logging/formatters.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/logging/stats.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/model/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/model/auth.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/model/compat.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/model/constants.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/model/pricing.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/model/token.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/model/vendor.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/config.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/extractors/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/extractors/anthropic.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/extractors/gemini.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/extractors/openai.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/handler.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/operation.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/routes.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/usage_registry.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/pricing.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/circuit_breaker.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/error_classifier.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/executor.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/model_mapper.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/quota_guard.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/rate_limit.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/retry.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/router.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/session_manager.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/tier.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/usage_parser.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/usage_recorder.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/server/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/server/app.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/server/dashboard.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/server/factory.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/server/responses.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/server/routes.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/streaming/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/streaming/anthropic_compat.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/alibaba.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/anthropic.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/antigravity.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/base.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/copilot.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/copilot_models.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/copilot_token_manager.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/copilot_urls.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/doubao.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/kimi.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/minimax.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/mixins.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/native_anthropic.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/token_manager.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/xiaomi.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/zhipu.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/__init__.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_antigravity.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_app_routes.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_auto_login.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_banner.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_circuit_breaker.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_cli_usage.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_compat.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_config_init.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_config_loader.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_convert_request.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_convert_response.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_convert_sse.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_copilot.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_copilot_convert_request.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_copilot_convert_response.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_copilot_models.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_copilot_urls.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_currency.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_error_classifier.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_logging_dual_write.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_mixins.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_model_auth.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_model_compat.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_model_constants.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_model_mapper.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_model_pricing.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_model_token.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_model_vendor.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_native_api_base_url_override.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_native_api_extractors.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_native_api_handler.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_native_api_operation.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_native_api_routes.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_native_vendors.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_parse_usage.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_parse_usage_gemini.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_pricing.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_quota_guard.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_rate_limit.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_router_chain.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_runtime_reauth.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_schema.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_streaming_anthropic_compat.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_tier.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_tiers_config.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_time_range.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_token_logger.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_token_logger_native_columns.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_token_manager.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_types.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_vendor_streaming.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_vendors.py +0 -0
- {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_zhipu.py +0 -0
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
1. **Python**: 严禁使用 pip/poetry,**必须**统一使用 `uv` 进行包管理与脚本执行(如 `uv run`);
|
|
53
53
|
2. **JavaScript/TypeScript**: 严禁使用 npm/yarn,**必须**统一使用 `pnpm` 进行包管理与脚本执行。
|
|
54
54
|
- **Database Management**: 谨慎操作,数据迁移、测试等操作严禁将现有数据删除,谨慎操作数据迁移的回滚,防止数据被清理。
|
|
55
|
+
- **In-depth and close to the facts**:系统且全面地进行问题的分析,深入贴近事实,如有疑问,需先发问,不要乱做决定。
|
|
55
56
|
|
|
56
57
|
## Documentation Standards (文档规范)
|
|
57
58
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coding-proxy
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1a6
|
|
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
|
|
@@ -152,30 +152,58 @@ zhipu GLM-5 在处理含 `tool_result` 块的会话时持续返回 500 错误,
|
|
|
152
152
|
|
|
153
153
|
```
|
|
154
154
|
WARNING zhipu stream error: status=500 body='...message":"\'ClaudeContentBlockToolResult\' object has no attribute \'id\'"}'
|
|
155
|
-
WARNING Tier zhipu zhipu tool_result format error (500), treating as format incompatibility without circuit breaker penalty
|
|
156
|
-
INFO Failover: zhipu → copilot (reason: HTTP 500)
|
|
157
155
|
```
|
|
158
156
|
|
|
159
157
|
**表因**
|
|
160
158
|
|
|
161
|
-
zhipu 后端在解析 `tool_result` 内容块时错误地访问 `.id` 属性。但 Anthropic API 规范中 `tool_result` 块只有 `tool_use_id` 字段(用于关联对应的 `tool_use`),没有 `id`
|
|
159
|
+
zhipu 后端在解析 `tool_result` 内容块时错误地访问 `.id` 属性。但 Anthropic API 规范中 `tool_result` 块只有 `tool_use_id` 字段(用于关联对应的 `tool_use`),没有 `id` 字段。
|
|
162
160
|
|
|
163
|
-
|
|
161
|
+
**根因**(2026-04-29 复盘更新)
|
|
164
162
|
|
|
165
|
-
|
|
163
|
+
**初始诊断**(已推翻):认为 zhipu 后端期望 `tool_result` 有 `id` 字段,通过 `_inject_tool_result_id_for_zhipu` 注入 `id = tool_use_id` 可绕过。
|
|
166
164
|
|
|
167
|
-
|
|
165
|
+
**实际根因**:转换通道本身引入的问题。具体因果链:
|
|
166
|
+
|
|
167
|
+
1. **转换前**:zhipu 偶发在 assistant 消息中内联输出 `tool_result`(违反 Anthropic 规范),但 zhipu 后端对 assistant 消息中内联的 `tool_result` **不做 `.id` 属性访问**,因此不触发 500。
|
|
168
|
+
2. **转换后**:所有 zhipu 目标通道执行 `enforce_anthropic_tool_pairing`,将 assistant 内联的 `tool_result` 搬迁到紧随的 user 消息。zhipu 后端对 user 消息中的 `tool_result` **执行 `.id` 属性访问**(代码路径不同),触发 `AttributeError` → 500。
|
|
169
|
+
3. **`_inject_tool_result_id_for_zhipu` 无效**:该函数往 JSON dict 注入 `"id": tool_use_id`,但 zhipu 后端的 `ClaudeContentBlockToolResult` Python 类不从 JSON 读取 `id` 字段(类定义中无此属性),注入的值在反序列化时被丢弃。
|
|
170
|
+
|
|
171
|
+
**实证依据**:用户确认「转换通道之前 zhipu 正常,转换通道之后才出现 500 错误」。
|
|
172
|
+
|
|
173
|
+
**处理方式**(2026-04-29 更新)
|
|
174
|
+
|
|
175
|
+
从所有 zhipu 目标转换通道中移除以下三个步骤:
|
|
176
|
+
|
|
177
|
+
| 移除项 | 原因 |
|
|
178
|
+
|--------|------|
|
|
179
|
+
| `enforce_anthropic_tool_pairing` | 搬迁 `tool_result` 到 user 消息触发 zhipu 500 |
|
|
180
|
+
| `_inject_tool_result_id_for_zhipu` | zhipu 类不读取注入的 `id`,无效且可能干扰 |
|
|
181
|
+
| `_strip_cache_control` | zhipu 原生支持 `cache_control`(cache_read 已实证),剥离反损性能 |
|
|
182
|
+
|
|
183
|
+
保留的必要步骤:
|
|
184
|
+
|
|
185
|
+
| 保留项 | 原因 |
|
|
186
|
+
|--------|------|
|
|
187
|
+
| `strip_thinking_blocks` | copilot/anthropic 的 thinking 签名 zhipu 无法验证 |
|
|
188
|
+
| 移除 `thinking`/`extended_thinking` 顶层参数 | zhipu 不支持 |
|
|
189
|
+
| `_remove_vendor_blocks(server_tool_use_delta)` | zhipu 自身流式残块 |
|
|
190
|
+
| `_remove_vendor_blocks(server_tool_use)` | Anthropic beta 块,zhipu 不支持 |
|
|
191
|
+
|
|
192
|
+
**涉及变更的转换通道**:
|
|
193
|
+
- `prepare_copilot_to_zhipu` — 移除 cache_control / tool pairing / id 注入
|
|
194
|
+
- `prepare_anthropic_to_zhipu` — 移除 cache_control / tool pairing / id 注入
|
|
195
|
+
- `prepare_zhipu_self_cleanup` — 移除 tool pairing / id 注入
|
|
168
196
|
|
|
169
|
-
|
|
170
|
-
- 在三个 targeting zhipu 的转换通道末尾统一调用此辅助函数
|
|
171
|
-
- 保留 executor 中已有的 500 错误检测作为纵深防御
|
|
197
|
+
**注意**: `prepare_zhipu_to_anthropic` 和 `prepare_zhipu_to_copilot` 不受影响(目标是 anthropic/copilot,不是 zhipu),仍保留 `enforce_anthropic_tool_pairing`。
|
|
172
198
|
|
|
173
199
|
**后续防范**
|
|
174
200
|
|
|
175
|
-
-
|
|
176
|
-
-
|
|
201
|
+
- **转换通道的「最小干预」原则**:跨供应商转换应仅清理目标供应商**确认不支持**的特性。未经验证的「预防性清理」(如剥离 cache_control)可能误伤供应商原生支持的功能,甚至引入新的故障。
|
|
202
|
+
- **workaround 须验证有效**:`_inject_tool_result_id_for_zhipu` 虽有注释说明目的,但未经验证其有效性即合入。后续 workaround 须附带验证证据(如 curl 复现、上游确认)。
|
|
203
|
+
- **zhipu 后端 bug 跟踪**:`ClaudeContentBlockToolResult` 类缺少 `id` 属性是 zhipu 上游 bug。若 zhipu 修复此 bug,可考虑恢复 tool pairing 以获得更严格的消息结构校验。
|
|
177
204
|
|
|
178
205
|
**同类问题影响与处理注意事项**
|
|
179
206
|
|
|
180
|
-
- `
|
|
181
|
-
-
|
|
207
|
+
- `NativeAnthropicVendor` 子类的自清理通道应**精确剪裁**:仅修复 vendor 自身拒绝的产物,不做跨供应商的全量清理。
|
|
208
|
+
- 当 zhipu 后端出现新的 400 拒绝(如 inline tool_result 再次被拒),应优先调查是 zhipu 后端变更还是请求格式问题,而非立即加回 tool pairing(可能重新触发 500)。
|
|
209
|
+
- `_inject_tool_result_id_for_zhipu` 函数暂时保留在代码中(未删除),标记为 deprecated,待确认不需要后清理。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "coding-proxy"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.1a6"
|
|
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"
|
|
@@ -572,13 +572,18 @@ def infer_source_vendor_from_body(body: dict[str, Any]) -> str | None:
|
|
|
572
572
|
def prepare_copilot_to_zhipu(
|
|
573
573
|
body: dict[str, Any],
|
|
574
574
|
) -> tuple[dict[str, Any], list[str]]:
|
|
575
|
-
"""copilot → zhipu 转换:
|
|
575
|
+
"""copilot → zhipu 转换: 仅清理 copilot 产物中 zhipu 确认不支持的部分.
|
|
576
576
|
|
|
577
|
-
GLM-5 的 Anthropic
|
|
578
|
-
- thinking / redacted_thinking 块 (signature 由非 Anthropic 签发)
|
|
579
|
-
- cache_control 字段
|
|
580
|
-
-
|
|
581
|
-
- 顶层 thinking / extended_thinking 参数
|
|
577
|
+
GLM-5 的 Anthropic 兼容端点:
|
|
578
|
+
- ✗ thinking / redacted_thinking 块 (signature 由非 Anthropic 签发)
|
|
579
|
+
- ✓ cache_control 字段 (cache_read 已在生产实证)
|
|
580
|
+
- ✓ tool_result 在 assistant 消息中内联 (zhipu 自身偶发产出,可自行消化)
|
|
581
|
+
- ✗ 顶层 thinking / extended_thinking 参数
|
|
582
|
+
|
|
583
|
+
注意: 不再执行 enforce_anthropic_tool_pairing 和 _inject_tool_result_id_for_zhipu。
|
|
584
|
+
实证表明 tool_result 重定位会触发 zhipu 后端 ``'ClaudeContentBlockToolResult'
|
|
585
|
+
object has no attribute 'id'`` 500 错误;id 注入对 zhipu 的 Python 类
|
|
586
|
+
(不读取 JSON 中的 id 字段) 亦无效。详见 docs/issue.md。
|
|
582
587
|
|
|
583
588
|
Returns:
|
|
584
589
|
(prepared_body, adaptations) — adaptations 为应用的变换描述列表。
|
|
@@ -591,27 +596,12 @@ def prepare_copilot_to_zhipu(
|
|
|
591
596
|
if stripped:
|
|
592
597
|
adaptations.append(f"stripped_{stripped}_thinking_blocks")
|
|
593
598
|
|
|
594
|
-
# Step 2:
|
|
595
|
-
removed_cc = _strip_cache_control(prepared)
|
|
596
|
-
if removed_cc:
|
|
597
|
-
adaptations.append(f"removed_{removed_cc}_cache_control_fields")
|
|
598
|
-
|
|
599
|
-
# Step 3: 移除顶层 thinking/extended_thinking 参数(GLM-5 不支持)
|
|
599
|
+
# Step 2: 移除顶层 thinking/extended_thinking 参数(GLM-5 不支持)
|
|
600
600
|
for param in ("thinking", "extended_thinking"):
|
|
601
601
|
if param in prepared:
|
|
602
602
|
del prepared[param]
|
|
603
603
|
adaptations.append(f"removed_{param}_param")
|
|
604
604
|
|
|
605
|
-
# Step 4: 强制 tool_use/tool_result 配对
|
|
606
|
-
pairing_fixes = enforce_anthropic_tool_pairing(prepared.get("messages", []))
|
|
607
|
-
if pairing_fixes:
|
|
608
|
-
adaptations.extend(pairing_fixes)
|
|
609
|
-
|
|
610
|
-
# Step 5: 为 tool_result 块注入 id 字段(zhipu 后端 bug workaround)
|
|
611
|
-
injected = _inject_tool_result_id_for_zhipu(prepared)
|
|
612
|
-
if injected:
|
|
613
|
-
adaptations.append(f"injected_{injected}_tool_result_id_fields")
|
|
614
|
-
|
|
615
605
|
return prepared, adaptations
|
|
616
606
|
|
|
617
607
|
|
|
@@ -632,9 +622,11 @@ def prepare_anthropic_to_zhipu(
|
|
|
632
622
|
Anthropic API 可能产生的非兼容产物:
|
|
633
623
|
- ``server_tool_use`` blocks(web search / computer use 等 beta 功能)
|
|
634
624
|
- ``thinking`` / ``redacted_thinking`` blocks(含 Anthropic 签发的 signature)
|
|
635
|
-
- ``cache_control`` 字段
|
|
636
625
|
- 顶层 ``thinking`` / ``extended_thinking`` 参数
|
|
637
626
|
|
|
627
|
+
注意: 不再移除 cache_control (GLM-5 支持) ,不再执行 tool pairing 和
|
|
628
|
+
id 注入。原因同 prepare_copilot_to_zhipu 的 docstring。
|
|
629
|
+
|
|
638
630
|
Returns:
|
|
639
631
|
(prepared_body, adaptations) — adaptations 为应用的变换描述列表。
|
|
640
632
|
"""
|
|
@@ -651,27 +643,12 @@ def prepare_anthropic_to_zhipu(
|
|
|
651
643
|
if stripped:
|
|
652
644
|
adaptations.append(f"stripped_{stripped}_thinking_blocks")
|
|
653
645
|
|
|
654
|
-
# Step 3:
|
|
655
|
-
removed_cc = _strip_cache_control(prepared)
|
|
656
|
-
if removed_cc:
|
|
657
|
-
adaptations.append(f"removed_{removed_cc}_cache_control_fields")
|
|
658
|
-
|
|
659
|
-
# Step 4: 移除顶层 thinking/extended_thinking 参数(GLM-5 不支持)
|
|
646
|
+
# Step 3: 移除顶层 thinking/extended_thinking 参数(GLM-5 不支持)
|
|
660
647
|
for param in ("thinking", "extended_thinking"):
|
|
661
648
|
if param in prepared:
|
|
662
649
|
del prepared[param]
|
|
663
650
|
adaptations.append(f"removed_{param}_param")
|
|
664
651
|
|
|
665
|
-
# Step 5: 强制 tool_use/tool_result 配对
|
|
666
|
-
pairing_fixes = enforce_anthropic_tool_pairing(prepared.get("messages", []))
|
|
667
|
-
if pairing_fixes:
|
|
668
|
-
adaptations.extend(pairing_fixes)
|
|
669
|
-
|
|
670
|
-
# Step 6: 为 tool_result 块注入 id 字段(zhipu 后端 bug workaround)
|
|
671
|
-
injected = _inject_tool_result_id_for_zhipu(prepared)
|
|
672
|
-
if injected:
|
|
673
|
-
adaptations.append(f"injected_{injected}_tool_result_id_fields")
|
|
674
|
-
|
|
675
652
|
return prepared, adaptations
|
|
676
653
|
|
|
677
654
|
|
|
@@ -782,25 +759,22 @@ def prepare_zhipu_to_anthropic(
|
|
|
782
759
|
def prepare_zhipu_self_cleanup(
|
|
783
760
|
body: dict[str, Any],
|
|
784
761
|
) -> tuple[dict[str, Any], list[str]]:
|
|
785
|
-
"""zhipu → zhipu 自清理:
|
|
762
|
+
"""zhipu → zhipu 自清理: 仅剥离 zhipu 自身的流式残块.
|
|
786
763
|
|
|
787
|
-
GLM-5
|
|
788
|
-
|
|
789
|
-
产物原样回送下一轮请求时,zhipu 的 Anthropic 兼容端点会以 400 拒绝
|
|
790
|
-
(表现为 "400 + tool_results" 偶发,进而触发到 copilot 的降级)。
|
|
764
|
+
GLM-5 在流式响应中偶发暴露 ``server_tool_use_delta`` 私有块。当 Claude Code
|
|
765
|
+
将这些产物原样回送下一轮请求时,zhipu 的 Anthropic 兼容端点会拒绝。
|
|
791
766
|
|
|
792
|
-
|
|
767
|
+
本通道**保留**所有 zhipu 原生支持的特性:
|
|
793
768
|
|
|
794
769
|
- ✓ ``srvtoolu_*`` ID 与 ``server_tool_use`` 类型(zhipu 原生)
|
|
795
770
|
- ✓ thinking blocks 的 zhipu 自签 signature
|
|
796
771
|
- ✓ ``cache_control`` 字段(GLM Anthropic 端点支持,cache_read 已实证)
|
|
797
772
|
- ✓ 顶层 ``thinking`` / ``extended_thinking`` 参数
|
|
773
|
+
- ✓ tool_result 在 assistant 消息中内联(zhipu 自身偶发产出,可自行消化)
|
|
798
774
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
搬迁到紧随的 user 消息)
|
|
803
|
-
3. 为 ``tool_result`` 块注入 ``id`` 字段(zhipu 后端错误访问 ``.id`` 属性)
|
|
775
|
+
注意: 不再执行 enforce_anthropic_tool_pairing 和 _inject_tool_result_id_for_zhipu。
|
|
776
|
+
实证表明 tool_result 重定位会触发 zhipu 后端 500 错误。
|
|
777
|
+
详见 docs/issue.md。
|
|
804
778
|
|
|
805
779
|
Returns:
|
|
806
780
|
(prepared_body, adaptations) — adaptations 为应用的变换描述列表。
|
|
@@ -813,16 +787,6 @@ def prepare_zhipu_self_cleanup(
|
|
|
813
787
|
if removed_vendor_blocks:
|
|
814
788
|
adaptations.append(f"removed_{removed_vendor_blocks}_zhipu_vendor_blocks")
|
|
815
789
|
|
|
816
|
-
# Step 2: 强制 tool_use/tool_result 配对
|
|
817
|
-
pairing_fixes = enforce_anthropic_tool_pairing(prepared.get("messages", []))
|
|
818
|
-
if pairing_fixes:
|
|
819
|
-
adaptations.extend(pairing_fixes)
|
|
820
|
-
|
|
821
|
-
# Step 3: 为 tool_result 块注入 id 字段(zhipu 后端 bug workaround)
|
|
822
|
-
injected = _inject_tool_result_id_for_zhipu(prepared)
|
|
823
|
-
if injected:
|
|
824
|
-
adaptations.append(f"injected_{injected}_tool_result_id_fields")
|
|
825
|
-
|
|
826
790
|
return prepared, adaptations
|
|
827
791
|
|
|
828
792
|
|
|
@@ -2034,7 +2034,12 @@ class TestPrepareBodyForTierSelfTransition:
|
|
|
2034
2034
|
"""验证 zhipu → zhipu 自转换通道在 _prepare_body_for_tier 中的应用行为."""
|
|
2035
2035
|
|
|
2036
2036
|
def test_applies_zhipu_self_cleanup(self):
|
|
2037
|
-
"""source=zhipu, target=zhipu →
|
|
2037
|
+
"""source=zhipu, target=zhipu → 仅剥离 server_tool_use_delta.
|
|
2038
|
+
|
|
2039
|
+
不再做 tool pairing(搬迁 tool_result 会触发 zhipu 500),
|
|
2040
|
+
也不做 id 注入(zhipu 类不读取 JSON 中的 id)。
|
|
2041
|
+
inline tool_result 保留在 assistant 消息中,zhipu 可自行消化。
|
|
2042
|
+
"""
|
|
2038
2043
|
tier = MagicMock()
|
|
2039
2044
|
tier.name = "zhipu"
|
|
2040
2045
|
|
|
@@ -2067,18 +2072,16 @@ class TestPrepareBodyForTierSelfTransition:
|
|
|
2067
2072
|
assert result is not body
|
|
2068
2073
|
assert len(body["messages"][0]["content"]) == 3
|
|
2069
2074
|
|
|
2070
|
-
# delta
|
|
2075
|
+
# delta 块被剥离
|
|
2071
2076
|
assistant_content = result["messages"][0]["content"]
|
|
2072
|
-
assert all(
|
|
2073
|
-
|
|
2074
|
-
for b in assistant_content
|
|
2075
|
-
)
|
|
2076
|
-
# tool_result 已搬到下一个 user 消息
|
|
2077
|
-
assert result["messages"][1]["role"] == "user"
|
|
2077
|
+
assert all(b.get("type") != "server_tool_use_delta" for b in assistant_content)
|
|
2078
|
+
# inline tool_result 保留在 assistant 中(不再搬迁)
|
|
2078
2079
|
assert any(
|
|
2079
2080
|
b.get("type") == "tool_result" and b.get("tool_use_id") == "srvtoolu_a"
|
|
2080
|
-
for b in
|
|
2081
|
+
for b in assistant_content
|
|
2081
2082
|
)
|
|
2083
|
+
# 不应插入额外的 user 消息
|
|
2084
|
+
assert len(result["messages"]) == 1
|
|
2082
2085
|
|
|
2083
2086
|
def test_self_cleanup_preserves_srvtoolu_ids(self):
|
|
2084
2087
|
"""回归保护: 自清理通道不得改写 zhipu 原生 srvtoolu_* ID."""
|
|
@@ -244,7 +244,8 @@ class TestCopilotToZhipuChannel:
|
|
|
244
244
|
# 原始 body 未被修改
|
|
245
245
|
assert body["messages"][0]["content"][0]["type"] == "thinking"
|
|
246
246
|
|
|
247
|
-
def
|
|
247
|
+
def test_preserves_cache_control(self):
|
|
248
|
+
"""copilot → zhipu 不再移除 cache_control(zhipu 原生支持)."""
|
|
248
249
|
body = {
|
|
249
250
|
"system": [
|
|
250
251
|
{"type": "text", "text": "sys", "cache_control": {"type": "ephemeral"}},
|
|
@@ -252,8 +253,8 @@ class TestCopilotToZhipuChannel:
|
|
|
252
253
|
"messages": [],
|
|
253
254
|
}
|
|
254
255
|
prepared, adaptations = prepare_copilot_to_zhipu(body)
|
|
255
|
-
assert any("cache_control" in a for a in adaptations)
|
|
256
|
-
assert "cache_control"
|
|
256
|
+
assert not any("cache_control" in a for a in adaptations)
|
|
257
|
+
assert "cache_control" in prepared["system"][0]
|
|
257
258
|
|
|
258
259
|
def test_removes_thinking_params(self):
|
|
259
260
|
body = {
|
|
@@ -267,7 +268,8 @@ class TestCopilotToZhipuChannel:
|
|
|
267
268
|
assert "removed_thinking_param" in adaptations
|
|
268
269
|
assert "removed_extended_thinking_param" in adaptations
|
|
269
270
|
|
|
270
|
-
def
|
|
271
|
+
def test_does_not_relocate_tool_results(self):
|
|
272
|
+
"""copilot → zhipu 不再执行 tool pairing(避免触发 zhipu 500)."""
|
|
271
273
|
body = {
|
|
272
274
|
"messages": [
|
|
273
275
|
{
|
|
@@ -288,14 +290,15 @@ class TestCopilotToZhipuChannel:
|
|
|
288
290
|
],
|
|
289
291
|
}
|
|
290
292
|
prepared, adaptations = prepare_copilot_to_zhipu(body)
|
|
293
|
+
# user 消息内容不变(无 synthesized tool_result)
|
|
291
294
|
user_content = prepared["messages"][1]["content"]
|
|
292
295
|
tool_results = [
|
|
293
296
|
b
|
|
294
297
|
for b in user_content
|
|
295
298
|
if isinstance(b, dict) and b.get("type") == "tool_result"
|
|
296
299
|
]
|
|
297
|
-
assert len(tool_results) ==
|
|
298
|
-
assert
|
|
300
|
+
assert len(tool_results) == 0
|
|
301
|
+
assert not any("misplaced" in a for a in adaptations)
|
|
299
302
|
|
|
300
303
|
def test_combined_transformations(self):
|
|
301
304
|
body = {
|
|
@@ -327,15 +330,17 @@ class TestCopilotToZhipuChannel:
|
|
|
327
330
|
b.get("type") not in ("thinking", "redacted_thinking")
|
|
328
331
|
for b in prepared["messages"][0]["content"]
|
|
329
332
|
)
|
|
330
|
-
|
|
333
|
+
# cache_control 保留
|
|
334
|
+
assert "cache_control" in prepared["system"][0]
|
|
331
335
|
assert "thinking" not in prepared
|
|
336
|
+
# tool pairing 不执行(user 消息内容不变)
|
|
332
337
|
user_content = prepared["messages"][1]["content"]
|
|
333
338
|
tool_results = [
|
|
334
339
|
b
|
|
335
340
|
for b in user_content
|
|
336
341
|
if isinstance(b, dict) and b.get("type") == "tool_result"
|
|
337
342
|
]
|
|
338
|
-
assert len(tool_results) ==
|
|
343
|
+
assert len(tool_results) == 0
|
|
339
344
|
|
|
340
345
|
def test_preserves_original_body(self):
|
|
341
346
|
body = {
|
|
@@ -392,8 +397,8 @@ class TestCopilotToZhipuChannel:
|
|
|
392
397
|
assert prepared2 == prepared1
|
|
393
398
|
assert adaptations2 == []
|
|
394
399
|
|
|
395
|
-
def
|
|
396
|
-
"""copilot → zhipu
|
|
400
|
+
def test_no_id_injection_on_tool_result(self):
|
|
401
|
+
"""copilot → zhipu 转换不再注入 id 字段(zhipu 类不读取,注入无效)."""
|
|
397
402
|
body = {
|
|
398
403
|
"messages": [
|
|
399
404
|
{
|
|
@@ -421,8 +426,8 @@ class TestCopilotToZhipuChannel:
|
|
|
421
426
|
}
|
|
422
427
|
prepared, adaptations = prepare_copilot_to_zhipu(body)
|
|
423
428
|
tr = prepared["messages"][1]["content"][0]
|
|
424
|
-
assert
|
|
425
|
-
assert any("injected" in a
|
|
429
|
+
assert "id" not in tr
|
|
430
|
+
assert not any("injected" in a for a in adaptations)
|
|
426
431
|
|
|
427
432
|
|
|
428
433
|
# ── zhipu → anthropic 转换通道测试 ────────────────────────────────
|
|
@@ -758,8 +763,8 @@ class TestZhipuSelfCleanupChannel:
|
|
|
758
763
|
assert all(b.get("type") != "server_tool_use_delta" for b in content)
|
|
759
764
|
assert any("zhipu_vendor_blocks" in a for a in adaptations)
|
|
760
765
|
|
|
761
|
-
def
|
|
762
|
-
"""assistant 内联 tool_result
|
|
766
|
+
def test_preserves_inline_tool_result_in_assistant(self):
|
|
767
|
+
"""assistant 内联 tool_result 保留原位(不再搬迁,避免触发 zhipu 500)."""
|
|
763
768
|
body = {
|
|
764
769
|
"messages": [
|
|
765
770
|
{
|
|
@@ -783,49 +788,19 @@ class TestZhipuSelfCleanupChannel:
|
|
|
783
788
|
}
|
|
784
789
|
prepared, adaptations = prepare_zhipu_self_cleanup(body)
|
|
785
790
|
|
|
786
|
-
# assistant
|
|
791
|
+
# assistant 消息中 tool_result 保留原位
|
|
787
792
|
assistant_content = prepared["messages"][0]["content"]
|
|
788
|
-
assert all(b.get("type") != "tool_result" for b in assistant_content)
|
|
789
|
-
# tool_result 已搬到下一个 user 消息
|
|
790
|
-
user_content = prepared["messages"][1]["content"]
|
|
791
793
|
assert any(
|
|
792
794
|
b.get("type") == "tool_result" and b.get("tool_use_id") == "srvtoolu_a"
|
|
793
|
-
for b in
|
|
795
|
+
for b in assistant_content
|
|
794
796
|
)
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
""
|
|
799
|
-
body = {
|
|
800
|
-
"messages": [
|
|
801
|
-
{
|
|
802
|
-
"role": "assistant",
|
|
803
|
-
"content": [
|
|
804
|
-
{
|
|
805
|
-
"type": "tool_use",
|
|
806
|
-
"id": "toolu_001",
|
|
807
|
-
"name": "bash",
|
|
808
|
-
"input": {},
|
|
809
|
-
},
|
|
810
|
-
{
|
|
811
|
-
"type": "tool_result",
|
|
812
|
-
"tool_use_id": "toolu_001",
|
|
813
|
-
"content": "ok",
|
|
814
|
-
},
|
|
815
|
-
],
|
|
816
|
-
},
|
|
817
|
-
{"role": "user", "content": []},
|
|
818
|
-
],
|
|
819
|
-
}
|
|
820
|
-
prepared, adaptations = prepare_zhipu_self_cleanup(body)
|
|
821
|
-
|
|
822
|
-
user_content = prepared["messages"][1]["content"]
|
|
823
|
-
tr = next(b for b in user_content if b.get("type") == "tool_result")
|
|
824
|
-
assert tr["id"] == "toolu_001"
|
|
825
|
-
assert any("injected" in a and "tool_result_id" in a for a in adaptations)
|
|
797
|
+
# 不应有 tool pairing 相关的 adaptations
|
|
798
|
+
assert not any("misplaced" in a for a in adaptations)
|
|
799
|
+
assert not any("orphaned" in a for a in adaptations)
|
|
800
|
+
assert not any("injected" in a for a in adaptations)
|
|
826
801
|
|
|
827
|
-
def
|
|
828
|
-
"""
|
|
802
|
+
def test_no_id_injection(self):
|
|
803
|
+
"""自清理通道不再注入 id 字段(zhipu 类不读取,注入无效)."""
|
|
829
804
|
body = {
|
|
830
805
|
"messages": [
|
|
831
806
|
{
|
|
@@ -854,11 +829,11 @@ class TestZhipuSelfCleanupChannel:
|
|
|
854
829
|
prepared, adaptations = prepare_zhipu_self_cleanup(body)
|
|
855
830
|
|
|
856
831
|
tr = prepared["messages"][1]["content"][0]
|
|
857
|
-
assert
|
|
858
|
-
assert any("injected" in a
|
|
832
|
+
assert "id" not in tr
|
|
833
|
+
assert not any("injected" in a for a in adaptations)
|
|
859
834
|
|
|
860
|
-
def
|
|
861
|
-
"""tool_result 已有 id
|
|
835
|
+
def test_preserves_existing_id(self):
|
|
836
|
+
"""tool_result 已有 id 字段时应原样保留,不被修改."""
|
|
862
837
|
body = {
|
|
863
838
|
"messages": [
|
|
864
839
|
{
|
|
@@ -878,7 +853,7 @@ class TestZhipuSelfCleanupChannel:
|
|
|
878
853
|
{
|
|
879
854
|
"type": "tool_result",
|
|
880
855
|
"tool_use_id": "toolu_001",
|
|
881
|
-
"id": "
|
|
856
|
+
"id": "original_id",
|
|
882
857
|
"content": "ok",
|
|
883
858
|
},
|
|
884
859
|
],
|
|
@@ -886,7 +861,8 @@ class TestZhipuSelfCleanupChannel:
|
|
|
886
861
|
],
|
|
887
862
|
}
|
|
888
863
|
prepared, adaptations = prepare_zhipu_self_cleanup(body)
|
|
889
|
-
|
|
864
|
+
tr = prepared["messages"][1]["content"][0]
|
|
865
|
+
assert tr["id"] == "original_id"
|
|
890
866
|
assert not any("injected" in a for a in adaptations)
|
|
891
867
|
|
|
892
868
|
def test_preserves_srvtoolu_ids(self):
|
|
@@ -1060,11 +1036,11 @@ class TestZhipuSelfCleanupChannel:
|
|
|
1060
1036
|
assert body == original
|
|
1061
1037
|
|
|
1062
1038
|
def test_combined_artifacts(self):
|
|
1063
|
-
"""端到端: server_tool_use_delta 被剥,
|
|
1039
|
+
"""端到端: server_tool_use_delta 被剥, 其余保留原位.
|
|
1064
1040
|
|
|
1065
|
-
典型场景:
|
|
1066
|
-
|
|
1067
|
-
|
|
1041
|
+
典型场景: zhipu 偶发在 assistant 消息中产出多种块。
|
|
1042
|
+
server_tool_use_delta 被剥离,其余块(含 inline tool_result)保留原位,
|
|
1043
|
+
不再做 tool pairing 和 id 注入。
|
|
1068
1044
|
"""
|
|
1069
1045
|
body = {
|
|
1070
1046
|
"messages": [
|
|
@@ -1098,8 +1074,11 @@ class TestZhipuSelfCleanupChannel:
|
|
|
1098
1074
|
assistant_content = prepared["messages"][0]["content"]
|
|
1099
1075
|
# delta 被剥离
|
|
1100
1076
|
assert all(b.get("type") != "server_tool_use_delta" for b in assistant_content)
|
|
1101
|
-
#
|
|
1102
|
-
assert
|
|
1077
|
+
# inline tool_result 保留在 assistant 中(不再搬迁)
|
|
1078
|
+
assert any(
|
|
1079
|
+
b.get("type") == "tool_result" and b.get("tool_use_id") == "toolu_bash_001"
|
|
1080
|
+
for b in assistant_content
|
|
1081
|
+
)
|
|
1103
1082
|
# server_tool_use 与其 srvtoolu_* ID 完整保留
|
|
1104
1083
|
srv_block = next(
|
|
1105
1084
|
b for b in assistant_content if b.get("type") == "server_tool_use"
|
|
@@ -1110,15 +1089,13 @@ class TestZhipuSelfCleanupChannel:
|
|
|
1110
1089
|
b for b in assistant_content if b.get("type") == "tool_use"
|
|
1111
1090
|
)
|
|
1112
1091
|
assert tool_use_block["id"] == "toolu_bash_001"
|
|
1113
|
-
#
|
|
1114
|
-
assert prepared["messages"]
|
|
1115
|
-
|
|
1116
|
-
b.get("type") == "tool_result" and b.get("tool_use_id") == "toolu_bash_001"
|
|
1117
|
-
for b in prepared["messages"][1]["content"]
|
|
1118
|
-
)
|
|
1119
|
-
# 关键 adaptation 标签均出现
|
|
1092
|
+
# 不插入额外 user 消息
|
|
1093
|
+
assert len(prepared["messages"]) == 1
|
|
1094
|
+
# 关键 adaptation 标签
|
|
1120
1095
|
assert any("zhipu_vendor_blocks" in a for a in adaptations)
|
|
1121
|
-
|
|
1096
|
+
# 不应有 tool pairing / id 注入 相关 adaptation
|
|
1097
|
+
assert not any("misplaced" in a for a in adaptations)
|
|
1098
|
+
assert not any("injected" in a for a in adaptations)
|
|
1122
1099
|
|
|
1123
1100
|
|
|
1124
1101
|
# ── 转换注册表测试 ────────────────────────────────────────────
|
|
@@ -2689,7 +2666,8 @@ class TestAnthropicToZhipuChannel:
|
|
|
2689
2666
|
{"type": "text", "text": "response"},
|
|
2690
2667
|
]
|
|
2691
2668
|
|
|
2692
|
-
def
|
|
2669
|
+
def test_preserves_cache_control(self):
|
|
2670
|
+
"""anthropic → zhipu 不再移除 cache_control(zhipu 原生支持)."""
|
|
2693
2671
|
body = {
|
|
2694
2672
|
"system": [
|
|
2695
2673
|
{"type": "text", "text": "sys", "cache_control": {"type": "ephemeral"}},
|
|
@@ -2697,8 +2675,8 @@ class TestAnthropicToZhipuChannel:
|
|
|
2697
2675
|
"messages": [],
|
|
2698
2676
|
}
|
|
2699
2677
|
prepared, adaptations = prepare_anthropic_to_zhipu(body)
|
|
2700
|
-
assert any("cache_control" in a for a in adaptations)
|
|
2701
|
-
assert "cache_control"
|
|
2678
|
+
assert not any("cache_control" in a for a in adaptations)
|
|
2679
|
+
assert "cache_control" in prepared["system"][0]
|
|
2702
2680
|
|
|
2703
2681
|
def test_removes_thinking_params(self):
|
|
2704
2682
|
body = {
|
|
@@ -2712,7 +2690,8 @@ class TestAnthropicToZhipuChannel:
|
|
|
2712
2690
|
assert "removed_thinking_param" in adaptations
|
|
2713
2691
|
assert "removed_extended_thinking_param" in adaptations
|
|
2714
2692
|
|
|
2715
|
-
def
|
|
2693
|
+
def test_does_not_relocate_tool_results(self):
|
|
2694
|
+
"""anthropic → zhipu 不再执行 tool pairing(避免触发 zhipu 500)."""
|
|
2716
2695
|
body = {
|
|
2717
2696
|
"messages": [
|
|
2718
2697
|
{
|
|
@@ -2730,14 +2709,13 @@ class TestAnthropicToZhipuChannel:
|
|
|
2730
2709
|
],
|
|
2731
2710
|
}
|
|
2732
2711
|
prepared, adaptations = prepare_anthropic_to_zhipu(body)
|
|
2733
|
-
assert "
|
|
2712
|
+
assert not any("orphaned" in a for a in adaptations)
|
|
2734
2713
|
user_results = [
|
|
2735
2714
|
b
|
|
2736
2715
|
for b in prepared["messages"][1]["content"]
|
|
2737
2716
|
if isinstance(b, dict) and b.get("type") == "tool_result"
|
|
2738
2717
|
]
|
|
2739
|
-
assert len(user_results) ==
|
|
2740
|
-
assert user_results[0]["tool_use_id"] == "toolu_1"
|
|
2718
|
+
assert len(user_results) == 0
|
|
2741
2719
|
|
|
2742
2720
|
def test_preserves_original_body(self):
|
|
2743
2721
|
body = {
|
|
@@ -2847,7 +2825,7 @@ class TestAnthropicToZhipuChannel:
|
|
|
2847
2825
|
]
|
|
2848
2826
|
|
|
2849
2827
|
def test_combined_server_tool_use_and_thinking(self):
|
|
2850
|
-
"""server_tool_use + thinking
|
|
2828
|
+
"""server_tool_use + thinking 的组合清洗, cache_control 保留."""
|
|
2851
2829
|
body = {
|
|
2852
2830
|
"system": [
|
|
2853
2831
|
{"type": "text", "text": "sys", "cache_control": {"type": "ephemeral"}},
|
|
@@ -2874,13 +2852,14 @@ class TestAnthropicToZhipuChannel:
|
|
|
2874
2852
|
b.get("type") not in ("thinking", "redacted_thinking", "server_tool_use")
|
|
2875
2853
|
for b in prepared["messages"][0]["content"]
|
|
2876
2854
|
)
|
|
2877
|
-
|
|
2855
|
+
# cache_control 保留(zhipu 原生支持)
|
|
2856
|
+
assert "cache_control" in prepared["system"][0]
|
|
2878
2857
|
assert "thinking" not in prepared
|
|
2879
2858
|
assert any("server_tool_use" in a for a in adaptations)
|
|
2880
2859
|
assert any("thinking_blocks" in a for a in adaptations)
|
|
2881
2860
|
|
|
2882
|
-
def
|
|
2883
|
-
"""anthropic → zhipu
|
|
2861
|
+
def test_no_id_injection_on_tool_result(self):
|
|
2862
|
+
"""anthropic → zhipu 转换不再注入 id 字段(zhipu 类不读取,注入无效)."""
|
|
2884
2863
|
body = {
|
|
2885
2864
|
"messages": [
|
|
2886
2865
|
{
|
|
@@ -2908,5 +2887,5 @@ class TestAnthropicToZhipuChannel:
|
|
|
2908
2887
|
}
|
|
2909
2888
|
prepared, adaptations = prepare_anthropic_to_zhipu(body)
|
|
2910
2889
|
tr = prepared["messages"][1]["content"][0]
|
|
2911
|
-
assert
|
|
2912
|
-
assert any("injected" in a
|
|
2890
|
+
assert "id" not in tr
|
|
2891
|
+
assert not any("injected" in a for a in adaptations)
|
|
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
|