coding-proxy 0.3.1a4__tar.gz → 0.3.1a5__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.1a4 → coding_proxy-0.3.1a5}/CHANGELOG.md +1 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/PKG-INFO +1 -1
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/issue.md +38 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/pyproject.toml +1 -1
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/convert/vendor_channels.py +46 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_vendor_channels.py +159 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/uv.lock +1 -1
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/.github/workflows/ci.yml +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/.github/workflows/coverage.yml +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/.github/workflows/release.yml +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/.gitignore +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/.pre-commit-config.yaml +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/AGENTS.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/CLAUDE.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/LICENSE +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/README.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/assets/dashboard-v0.2.4.png +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/arch/config-reference.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/arch/convert.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/arch/design-patterns.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/arch/routing.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/arch/testing.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/arch/vendors.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/ci-cd.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/framework.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/guide/api-reference.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/guide/cli-reference.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/guide/dashboard.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/guide/monitoring.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/guide/quickstart.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/guide/vendors.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/user-guide.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/docs/zh-CN/README.md +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/__main__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/auth/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/auth/providers/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/auth/providers/base.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/auth/providers/github.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/auth/providers/google.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/auth/runtime.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/auth/store.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/cli/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/cli/auth_commands.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/cli/banner.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/compat/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/compat/canonical.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/compat/session_store.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/config/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/config/auth_schema.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/config/config.default.yaml +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/config/loader.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/config/resiliency.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/config/routing.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/config/schema.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/config/server.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/config/vendors.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/convert/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/convert/anthropic_to_gemini.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/convert/anthropic_to_openai.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/convert/gemini_sse_adapter.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/convert/gemini_to_anthropic.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/convert/openai_to_anthropic.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/logging/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/logging/db.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/logging/formatters.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/logging/stats.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/model/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/model/auth.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/model/compat.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/model/constants.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/model/pricing.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/model/token.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/model/vendor.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/native_api/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/native_api/config.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/native_api/extractors/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/native_api/extractors/anthropic.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/native_api/extractors/gemini.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/native_api/extractors/openai.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/native_api/handler.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/native_api/operation.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/native_api/routes.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/native_api/usage_registry.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/pricing.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/routing/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/routing/circuit_breaker.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/routing/error_classifier.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/routing/executor.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/routing/model_mapper.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/routing/quota_guard.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/routing/rate_limit.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/routing/retry.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/routing/router.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/routing/session_manager.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/routing/tier.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/routing/usage_parser.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/routing/usage_recorder.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/server/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/server/app.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/server/dashboard.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/server/factory.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/server/responses.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/server/routes.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/streaming/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/streaming/anthropic_compat.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/alibaba.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/anthropic.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/antigravity.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/base.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/copilot.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/copilot_models.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/copilot_token_manager.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/copilot_urls.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/doubao.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/kimi.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/minimax.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/mixins.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/native_anthropic.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/token_manager.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/xiaomi.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/zhipu.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/__init__.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_antigravity.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_app_routes.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_auto_login.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_banner.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_circuit_breaker.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_cli_usage.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_compat.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_config_init.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_config_loader.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_convert_request.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_convert_response.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_convert_sse.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_copilot.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_copilot_convert_request.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_copilot_convert_response.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_copilot_models.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_copilot_urls.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_currency.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_error_classifier.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_logging_dual_write.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_mixins.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_model_auth.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_model_compat.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_model_constants.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_model_mapper.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_model_pricing.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_model_token.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_model_vendor.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_native_api_base_url_override.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_native_api_extractors.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_native_api_handler.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_native_api_operation.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_native_api_routes.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_native_vendors.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_parse_usage.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_parse_usage_gemini.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_pricing.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_quota_guard.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_rate_limit.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_router_chain.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_router_executor.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_runtime_reauth.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_schema.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_streaming_anthropic_compat.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_tier.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_tiers_config.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_time_range.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_token_logger.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_token_logger_native_columns.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_token_manager.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_types.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_vendor_streaming.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_vendors.py +0 -0
- {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/tests/test_zhipu.py +0 -0
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
- fix(vendor-channels): 为所有 targeting zhipu 的转换通道(zhipu→zhipu、copilot→zhipu、anthropic→zhipu)新增 `tool_result.id` 字段注入,修复 zhipu GLM-5 后端错误访问 `.id` 属性(`'ClaudeContentBlockToolResult' object has no attribute 'id'`)导致的 500 错误,使 zhipu 可完全承接含 tool_result 的会话;
|
|
7
8
|
- fix(vendor-channels): 新增 zhipu 同 vendor 自清理通道,修复 GLM-5 自循环 400 + tool_results 偶发降级;
|
|
8
9
|
- fix(vendor-channels): 修复 `_rewrite_srvtoolu_ids` 块顺序敏感性导致 inline tool_result 漏改名,进而 enforce 阶段 dict key 与 tool_use_ids 错位、anthropic 报 `tool_use ids without tool_result blocks immediately after` 的 cascade failover 问题(改为两遍扫描:先收集 id_map,再统一改写所有 tool_result.tool_use_id 引用);
|
|
9
10
|
- fix(vendor-channels): `enforce_anthropic_tool_pairing` 增加全局 sanity check pass,主循环边角错位让 dangling tool_use 漏过校验时兜底合成 is_error 占位并打 `pairing_sanity_repaired` 标签,避免 anthropic 二次报错;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coding-proxy
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1a5
|
|
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
|
|
@@ -141,3 +141,41 @@ adaptations 列表显示 `misplaced_tool_result_relocated` 但**没有** `orphan
|
|
|
141
141
|
|
|
142
142
|
- 任何对 messages 进行 ID 重写的转换链 (如 `_rewrite_srvtoolu_ids`、`anthropic_to_openai`、`anthropic_to_gemini`) 都应使用两遍扫描或一次性收集后再批量改写, 以保证 block 顺序无关性。
|
|
143
143
|
- enforce 类校验函数若依赖 dict key 与 list 元素的**等同性**, 必须先确保两者在同一参考系下 (改名前 vs 改名后); 否则错位会以 "看起来 OK 实际有漏" 的方式静默泄漏到下游。
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## zhipu 500 `'ClaudeContentBlockToolResult' object has no attribute 'id'`
|
|
148
|
+
|
|
149
|
+
**问题描述**
|
|
150
|
+
|
|
151
|
+
zhipu GLM-5 在处理含 `tool_result` 块的会话时持续返回 500 错误,每次请求都触发故障转移至 copilot,zhipu 完全无法承接含工具调用的多轮对话:
|
|
152
|
+
|
|
153
|
+
```
|
|
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
|
+
```
|
|
158
|
+
|
|
159
|
+
**表因**
|
|
160
|
+
|
|
161
|
+
zhipu 后端在解析 `tool_result` 内容块时错误地访问 `.id` 属性。但 Anthropic API 规范中 `tool_result` 块只有 `tool_use_id` 字段(用于关联对应的 `tool_use`),没有 `id` 字段(`id` 是 `tool_use` 块的属性)。
|
|
162
|
+
|
|
163
|
+
**根因**
|
|
164
|
+
|
|
165
|
+
所有 targeting zhipu 的转换通道(`prepare_zhipu_self_cleanup`、`prepare_copilot_to_zhipu`、`prepare_anthropic_to_zhipu`)在完成 `enforce_anthropic_tool_pairing` 后,没有为 `tool_result` 块补上 zhipu 后端期望的 `id` 字段。搬迁或合成的 `tool_result` 块仅有 `tool_use_id`,缺少 `id`。
|
|
166
|
+
|
|
167
|
+
**处理方式**
|
|
168
|
+
|
|
169
|
+
- 在 `vendor_channels.py` 新增 `_inject_tool_result_id_for_zhipu` 辅助函数:扫描所有消息中的 `tool_result` 块,将 `tool_use_id` 值复制为 `id` 字段(仅注入尚无 `id` 的块,保持幂等)
|
|
170
|
+
- 在三个 targeting zhipu 的转换通道末尾统一调用此辅助函数
|
|
171
|
+
- 保留 executor 中已有的 500 错误检测作为纵深防御
|
|
172
|
+
|
|
173
|
+
**后续防范**
|
|
174
|
+
|
|
175
|
+
- 其他 `NativeAnthropicVendor` 子类若出现类似的「后端期望非标准字段」问题,可参考此模式在对应的转换通道中注入兼容字段。
|
|
176
|
+
- 当 zhipu 后端修复此 bug(不再访问 `.id`)后,此 workaround 仍安全保留(多一个 `id` 字段不影响 Anthropic API 语义)。
|
|
177
|
+
|
|
178
|
+
**同类问题影响与处理注意事项**
|
|
179
|
+
|
|
180
|
+
- `enforce_anthropic_tool_pairing` 合成的 `is_error=True` 占位块只有 `tool_use_id`,同样需要 `id` 注入——辅助函数在配对后统一处理,无需在合成逻辑中单独添加。
|
|
181
|
+
- `tool_result.id` 的值设为与 `tool_use_id` 相同,语义上可视为「内容块标识符」,对 zhipu 后端足够区分不同 tool_result 块。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "coding-proxy"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.1a5"
|
|
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"
|
|
@@ -322,6 +322,36 @@ def _enforce_pairing_sanity_pass(messages_list: list[Any]) -> list[str]:
|
|
|
322
322
|
return sanity_synthesized
|
|
323
323
|
|
|
324
324
|
|
|
325
|
+
def _inject_tool_result_id_for_zhipu(body: dict[str, Any]) -> int:
|
|
326
|
+
"""为 tool_result 块注入 ``id`` 字段以兼容 zhipu GLM-5 后端.
|
|
327
|
+
|
|
328
|
+
zhipu 的 Anthropic 兼容端点在解析 ``tool_result`` 块时会访问 ``.id`` 属性,
|
|
329
|
+
但 Anthropic API 规范中 ``tool_result`` 只有 ``tool_use_id`` 字段而没有 ``id``。
|
|
330
|
+
此函数在所有 ``tool_result`` 块上补设 ``id``(值等于 ``tool_use_id``),
|
|
331
|
+
避免触发 ``'ClaudeContentBlockToolResult' object has no attribute 'id'`` 500 错误。
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
被注入 ``id`` 字段的 tool_result 块数量。
|
|
335
|
+
"""
|
|
336
|
+
injected = 0
|
|
337
|
+
for message in body.get("messages", []):
|
|
338
|
+
if not isinstance(message, dict):
|
|
339
|
+
continue
|
|
340
|
+
content = message.get("content")
|
|
341
|
+
if not isinstance(content, list):
|
|
342
|
+
continue
|
|
343
|
+
for block in content:
|
|
344
|
+
if (
|
|
345
|
+
isinstance(block, dict)
|
|
346
|
+
and block.get("type") == "tool_result"
|
|
347
|
+
and "id" not in block
|
|
348
|
+
and block.get("tool_use_id")
|
|
349
|
+
):
|
|
350
|
+
block["id"] = block["tool_use_id"]
|
|
351
|
+
injected += 1
|
|
352
|
+
return injected
|
|
353
|
+
|
|
354
|
+
|
|
325
355
|
def _strip_cache_control(body: dict[str, Any]) -> int:
|
|
326
356
|
"""从 system/messages/tools 中移除 cache_control 字段(就地).
|
|
327
357
|
|
|
@@ -577,6 +607,11 @@ def prepare_copilot_to_zhipu(
|
|
|
577
607
|
if pairing_fixes:
|
|
578
608
|
adaptations.extend(pairing_fixes)
|
|
579
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
|
+
|
|
580
615
|
return prepared, adaptations
|
|
581
616
|
|
|
582
617
|
|
|
@@ -632,6 +667,11 @@ def prepare_anthropic_to_zhipu(
|
|
|
632
667
|
if pairing_fixes:
|
|
633
668
|
adaptations.extend(pairing_fixes)
|
|
634
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
|
+
|
|
635
675
|
return prepared, adaptations
|
|
636
676
|
|
|
637
677
|
|
|
@@ -760,6 +800,7 @@ def prepare_zhipu_self_cleanup(
|
|
|
760
800
|
1. 剥离 ``server_tool_use_delta`` 流式残块
|
|
761
801
|
2. 强制 tool_use/tool_result 配对(关键: 把 assistant 内联的 tool_result
|
|
762
802
|
搬迁到紧随的 user 消息)
|
|
803
|
+
3. 为 ``tool_result`` 块注入 ``id`` 字段(zhipu 后端错误访问 ``.id`` 属性)
|
|
763
804
|
|
|
764
805
|
Returns:
|
|
765
806
|
(prepared_body, adaptations) — adaptations 为应用的变换描述列表。
|
|
@@ -777,6 +818,11 @@ def prepare_zhipu_self_cleanup(
|
|
|
777
818
|
if pairing_fixes:
|
|
778
819
|
adaptations.extend(pairing_fixes)
|
|
779
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
|
+
|
|
780
826
|
return prepared, adaptations
|
|
781
827
|
|
|
782
828
|
|
|
@@ -392,6 +392,38 @@ class TestCopilotToZhipuChannel:
|
|
|
392
392
|
assert prepared2 == prepared1
|
|
393
393
|
assert adaptations2 == []
|
|
394
394
|
|
|
395
|
+
def test_injects_id_on_tool_result_for_zhipu(self):
|
|
396
|
+
"""copilot → zhipu 转换后 tool_result 应包含 id 字段."""
|
|
397
|
+
body = {
|
|
398
|
+
"messages": [
|
|
399
|
+
{
|
|
400
|
+
"role": "assistant",
|
|
401
|
+
"content": [
|
|
402
|
+
{
|
|
403
|
+
"type": "tool_use",
|
|
404
|
+
"id": "toolu_001",
|
|
405
|
+
"name": "bash",
|
|
406
|
+
"input": {},
|
|
407
|
+
},
|
|
408
|
+
],
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
"role": "user",
|
|
412
|
+
"content": [
|
|
413
|
+
{
|
|
414
|
+
"type": "tool_result",
|
|
415
|
+
"tool_use_id": "toolu_001",
|
|
416
|
+
"content": "ok",
|
|
417
|
+
},
|
|
418
|
+
],
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
}
|
|
422
|
+
prepared, adaptations = prepare_copilot_to_zhipu(body)
|
|
423
|
+
tr = prepared["messages"][1]["content"][0]
|
|
424
|
+
assert tr["id"] == "toolu_001"
|
|
425
|
+
assert any("injected" in a and "tool_result_id" in a for a in adaptations)
|
|
426
|
+
|
|
395
427
|
|
|
396
428
|
# ── zhipu → anthropic 转换通道测试 ────────────────────────────────
|
|
397
429
|
|
|
@@ -762,6 +794,101 @@ class TestZhipuSelfCleanupChannel:
|
|
|
762
794
|
)
|
|
763
795
|
assert "misplaced_tool_result_relocated" in adaptations
|
|
764
796
|
|
|
797
|
+
def test_injects_id_on_relocated_tool_result(self):
|
|
798
|
+
"""搬迁后的 tool_result 块应具有 id 字段(zhipu 后端 bug workaround)."""
|
|
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)
|
|
826
|
+
|
|
827
|
+
def test_injects_id_on_existing_user_tool_result(self):
|
|
828
|
+
"""user 消息中已有的 tool_result 也应被注入 id 字段."""
|
|
829
|
+
body = {
|
|
830
|
+
"messages": [
|
|
831
|
+
{
|
|
832
|
+
"role": "assistant",
|
|
833
|
+
"content": [
|
|
834
|
+
{
|
|
835
|
+
"type": "tool_use",
|
|
836
|
+
"id": "toolu_001",
|
|
837
|
+
"name": "bash",
|
|
838
|
+
"input": {},
|
|
839
|
+
},
|
|
840
|
+
],
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
"role": "user",
|
|
844
|
+
"content": [
|
|
845
|
+
{
|
|
846
|
+
"type": "tool_result",
|
|
847
|
+
"tool_use_id": "toolu_001",
|
|
848
|
+
"content": "ok",
|
|
849
|
+
},
|
|
850
|
+
],
|
|
851
|
+
},
|
|
852
|
+
],
|
|
853
|
+
}
|
|
854
|
+
prepared, adaptations = prepare_zhipu_self_cleanup(body)
|
|
855
|
+
|
|
856
|
+
tr = prepared["messages"][1]["content"][0]
|
|
857
|
+
assert tr["id"] == "toolu_001"
|
|
858
|
+
assert any("injected" in a and "tool_result_id" in a for a in adaptations)
|
|
859
|
+
|
|
860
|
+
def test_skips_id_injection_when_already_present(self):
|
|
861
|
+
"""tool_result 已有 id 字段时不应重复注入."""
|
|
862
|
+
body = {
|
|
863
|
+
"messages": [
|
|
864
|
+
{
|
|
865
|
+
"role": "assistant",
|
|
866
|
+
"content": [
|
|
867
|
+
{
|
|
868
|
+
"type": "tool_use",
|
|
869
|
+
"id": "toolu_001",
|
|
870
|
+
"name": "bash",
|
|
871
|
+
"input": {},
|
|
872
|
+
},
|
|
873
|
+
],
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
"role": "user",
|
|
877
|
+
"content": [
|
|
878
|
+
{
|
|
879
|
+
"type": "tool_result",
|
|
880
|
+
"tool_use_id": "toolu_001",
|
|
881
|
+
"id": "toolu_001",
|
|
882
|
+
"content": "ok",
|
|
883
|
+
},
|
|
884
|
+
],
|
|
885
|
+
},
|
|
886
|
+
],
|
|
887
|
+
}
|
|
888
|
+
prepared, adaptations = prepare_zhipu_self_cleanup(body)
|
|
889
|
+
# 不应产生注入 adaptation(id 已存在)
|
|
890
|
+
assert not any("injected" in a for a in adaptations)
|
|
891
|
+
|
|
765
892
|
def test_preserves_srvtoolu_ids(self):
|
|
766
893
|
"""zhipu 原生 srvtoolu_* ID 与 server_tool_use 类型必须保留."""
|
|
767
894
|
body = {
|
|
@@ -2751,3 +2878,35 @@ class TestAnthropicToZhipuChannel:
|
|
|
2751
2878
|
assert "thinking" not in prepared
|
|
2752
2879
|
assert any("server_tool_use" in a for a in adaptations)
|
|
2753
2880
|
assert any("thinking_blocks" in a for a in adaptations)
|
|
2881
|
+
|
|
2882
|
+
def test_injects_id_on_tool_result_for_zhipu(self):
|
|
2883
|
+
"""anthropic → zhipu 转换后 tool_result 应包含 id 字段."""
|
|
2884
|
+
body = {
|
|
2885
|
+
"messages": [
|
|
2886
|
+
{
|
|
2887
|
+
"role": "assistant",
|
|
2888
|
+
"content": [
|
|
2889
|
+
{
|
|
2890
|
+
"type": "tool_use",
|
|
2891
|
+
"id": "toolu_001",
|
|
2892
|
+
"name": "bash",
|
|
2893
|
+
"input": {},
|
|
2894
|
+
},
|
|
2895
|
+
],
|
|
2896
|
+
},
|
|
2897
|
+
{
|
|
2898
|
+
"role": "user",
|
|
2899
|
+
"content": [
|
|
2900
|
+
{
|
|
2901
|
+
"type": "tool_result",
|
|
2902
|
+
"tool_use_id": "toolu_001",
|
|
2903
|
+
"content": "ok",
|
|
2904
|
+
},
|
|
2905
|
+
],
|
|
2906
|
+
},
|
|
2907
|
+
],
|
|
2908
|
+
}
|
|
2909
|
+
prepared, adaptations = prepare_anthropic_to_zhipu(body)
|
|
2910
|
+
tr = prepared["messages"][1]["content"][0]
|
|
2911
|
+
assert tr["id"] == "toolu_001"
|
|
2912
|
+
assert any("injected" in a and "tool_result_id" 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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/convert/anthropic_to_gemini.py
RENAMED
|
File without changes
|
{coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/convert/anthropic_to_openai.py
RENAMED
|
File without changes
|
{coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/convert/gemini_sse_adapter.py
RENAMED
|
File without changes
|
{coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/convert/gemini_to_anthropic.py
RENAMED
|
File without changes
|
{coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/convert/openai_to_anthropic.py
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
|
|
File without changes
|
|
File without changes
|
{coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/native_api/extractors/__init__.py
RENAMED
|
File without changes
|
{coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/native_api/extractors/anthropic.py
RENAMED
|
File without changes
|
{coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/native_api/extractors/gemini.py
RENAMED
|
File without changes
|
{coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/native_api/extractors/openai.py
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
|
|
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
|
|
File without changes
|
|
File without changes
|
{coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/streaming/anthropic_compat.py
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
|
{coding_proxy-0.3.1a4 → coding_proxy-0.3.1a5}/src/coding/proxy/vendors/copilot_token_manager.py
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|