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.
Files changed (179) hide show
  1. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/AGENTS.md +1 -0
  2. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/PKG-INFO +1 -1
  3. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/issue.md +41 -13
  4. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/pyproject.toml +1 -1
  5. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/vendor_channels.py +24 -60
  6. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_router_executor.py +12 -9
  7. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_vendor_channels.py +64 -85
  8. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/uv.lock +1 -1
  9. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/.github/workflows/ci.yml +0 -0
  10. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/.github/workflows/coverage.yml +0 -0
  11. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/.github/workflows/release.yml +0 -0
  12. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/.gitignore +0 -0
  13. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/.pre-commit-config.yaml +0 -0
  14. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/CHANGELOG.md +0 -0
  15. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/CLAUDE.md +0 -0
  16. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/LICENSE +0 -0
  17. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/README.md +0 -0
  18. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/assets/dashboard-v0.2.4.png +0 -0
  19. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/arch/config-reference.md +0 -0
  20. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/arch/convert.md +0 -0
  21. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/arch/design-patterns.md +0 -0
  22. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/arch/routing.md +0 -0
  23. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/arch/testing.md +0 -0
  24. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/arch/vendors.md +0 -0
  25. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/ci-cd.md +0 -0
  26. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/framework.md +0 -0
  27. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/guide/api-reference.md +0 -0
  28. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/guide/cli-reference.md +0 -0
  29. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/guide/dashboard.md +0 -0
  30. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/guide/monitoring.md +0 -0
  31. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/guide/quickstart.md +0 -0
  32. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/guide/vendors.md +0 -0
  33. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/user-guide.md +0 -0
  34. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/docs/zh-CN/README.md +0 -0
  35. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/__init__.py +0 -0
  36. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/__init__.py +0 -0
  37. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/__main__.py +0 -0
  38. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/__init__.py +0 -0
  39. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/providers/__init__.py +0 -0
  40. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/providers/base.py +0 -0
  41. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/providers/github.py +0 -0
  42. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/providers/google.py +0 -0
  43. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/runtime.py +0 -0
  44. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/store.py +0 -0
  45. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/cli/__init__.py +0 -0
  46. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/cli/auth_commands.py +0 -0
  47. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/cli/banner.py +0 -0
  48. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/compat/__init__.py +0 -0
  49. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/compat/canonical.py +0 -0
  50. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/compat/session_store.py +0 -0
  51. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/__init__.py +0 -0
  52. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/auth_schema.py +0 -0
  53. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/config.default.yaml +0 -0
  54. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/loader.py +0 -0
  55. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/resiliency.py +0 -0
  56. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/routing.py +0 -0
  57. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/schema.py +0 -0
  58. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/server.py +0 -0
  59. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/config/vendors.py +0 -0
  60. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/__init__.py +0 -0
  61. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/anthropic_to_gemini.py +0 -0
  62. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/anthropic_to_openai.py +0 -0
  63. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/gemini_sse_adapter.py +0 -0
  64. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/gemini_to_anthropic.py +0 -0
  65. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/openai_to_anthropic.py +0 -0
  66. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/logging/__init__.py +0 -0
  67. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/logging/db.py +0 -0
  68. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/logging/formatters.py +0 -0
  69. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/logging/stats.py +0 -0
  70. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/model/__init__.py +0 -0
  71. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/model/auth.py +0 -0
  72. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/model/compat.py +0 -0
  73. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/model/constants.py +0 -0
  74. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/model/pricing.py +0 -0
  75. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/model/token.py +0 -0
  76. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/model/vendor.py +0 -0
  77. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/__init__.py +0 -0
  78. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/config.py +0 -0
  79. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/extractors/__init__.py +0 -0
  80. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/extractors/anthropic.py +0 -0
  81. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/extractors/gemini.py +0 -0
  82. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/extractors/openai.py +0 -0
  83. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/handler.py +0 -0
  84. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/operation.py +0 -0
  85. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/routes.py +0 -0
  86. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/usage_registry.py +0 -0
  87. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/pricing.py +0 -0
  88. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/__init__.py +0 -0
  89. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/circuit_breaker.py +0 -0
  90. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/error_classifier.py +0 -0
  91. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/executor.py +0 -0
  92. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/model_mapper.py +0 -0
  93. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/quota_guard.py +0 -0
  94. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/rate_limit.py +0 -0
  95. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/retry.py +0 -0
  96. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/router.py +0 -0
  97. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/session_manager.py +0 -0
  98. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/tier.py +0 -0
  99. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/usage_parser.py +0 -0
  100. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/usage_recorder.py +0 -0
  101. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/server/__init__.py +0 -0
  102. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/server/app.py +0 -0
  103. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/server/dashboard.py +0 -0
  104. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/server/factory.py +0 -0
  105. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/server/responses.py +0 -0
  106. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/server/routes.py +0 -0
  107. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/streaming/__init__.py +0 -0
  108. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/streaming/anthropic_compat.py +0 -0
  109. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/__init__.py +0 -0
  110. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/alibaba.py +0 -0
  111. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/anthropic.py +0 -0
  112. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/antigravity.py +0 -0
  113. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/base.py +0 -0
  114. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/copilot.py +0 -0
  115. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/copilot_models.py +0 -0
  116. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/copilot_token_manager.py +0 -0
  117. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/copilot_urls.py +0 -0
  118. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/doubao.py +0 -0
  119. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/kimi.py +0 -0
  120. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/minimax.py +0 -0
  121. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/mixins.py +0 -0
  122. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/native_anthropic.py +0 -0
  123. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/token_manager.py +0 -0
  124. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/xiaomi.py +0 -0
  125. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/zhipu.py +0 -0
  126. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/__init__.py +0 -0
  127. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_antigravity.py +0 -0
  128. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_app_routes.py +0 -0
  129. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_auto_login.py +0 -0
  130. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_banner.py +0 -0
  131. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_circuit_breaker.py +0 -0
  132. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_cli_usage.py +0 -0
  133. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_compat.py +0 -0
  134. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_config_init.py +0 -0
  135. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_config_loader.py +0 -0
  136. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_convert_request.py +0 -0
  137. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_convert_response.py +0 -0
  138. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_convert_sse.py +0 -0
  139. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_copilot.py +0 -0
  140. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_copilot_convert_request.py +0 -0
  141. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_copilot_convert_response.py +0 -0
  142. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_copilot_models.py +0 -0
  143. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_copilot_urls.py +0 -0
  144. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_currency.py +0 -0
  145. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_error_classifier.py +0 -0
  146. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_logging_dual_write.py +0 -0
  147. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_mixins.py +0 -0
  148. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_model_auth.py +0 -0
  149. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_model_compat.py +0 -0
  150. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_model_constants.py +0 -0
  151. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_model_mapper.py +0 -0
  152. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_model_pricing.py +0 -0
  153. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_model_token.py +0 -0
  154. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_model_vendor.py +0 -0
  155. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_native_api_base_url_override.py +0 -0
  156. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_native_api_extractors.py +0 -0
  157. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_native_api_handler.py +0 -0
  158. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_native_api_operation.py +0 -0
  159. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_native_api_routes.py +0 -0
  160. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_native_vendors.py +0 -0
  161. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_parse_usage.py +0 -0
  162. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_parse_usage_gemini.py +0 -0
  163. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_pricing.py +0 -0
  164. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_quota_guard.py +0 -0
  165. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_rate_limit.py +0 -0
  166. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_router_chain.py +0 -0
  167. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_runtime_reauth.py +0 -0
  168. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_schema.py +0 -0
  169. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_streaming_anthropic_compat.py +0 -0
  170. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_tier.py +0 -0
  171. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_tiers_config.py +0 -0
  172. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_time_range.py +0 -0
  173. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_token_logger.py +0 -0
  174. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_token_logger_native_columns.py +0 -0
  175. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_token_manager.py +0 -0
  176. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_types.py +0 -0
  177. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_vendor_streaming.py +0 -0
  178. {coding_proxy-0.3.1a5 → coding_proxy-0.3.1a6}/tests/test_vendors.py +0 -0
  179. {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.1a5
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` 字段(`id` 是 `tool_use` 块的属性)。
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
- 所有 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`。
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
- - `vendor_channels.py` 新增 `_inject_tool_result_id_for_zhipu` 辅助函数:扫描所有消息中的 `tool_result` 块,将 `tool_use_id` 值复制为 `id` 字段(仅注入尚无 `id` 的块,保持幂等)
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
- - 其他 `NativeAnthropicVendor` 子类若出现类似的「后端期望非标准字段」问题,可参考此模式在对应的转换通道中注入兼容字段。
176
- - zhipu 后端修复此 bug(不再访问 `.id`)后,此 workaround 仍安全保留(多一个 `id` 字段不影响 Anthropic API 语义)。
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
- - `enforce_anthropic_tool_pairing` 合成的 `is_error=True` 占位块只有 `tool_use_id`,同样需要 `id` 注入——辅助函数在配对后统一处理,无需在合成逻辑中单独添加。
181
- - `tool_result.id` 的值设为与 `tool_use_id` 相同,语义上可视为「内容块标识符」,对 zhipu 后端足够区分不同 tool_result 块。
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.1a5"
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 转换: 清理 copilot 产物以适配 GLM-5.
575
+ """copilot → zhipu 转换: 仅清理 copilot 产物中 zhipu 确认不支持的部分.
576
576
 
577
- GLM-5 的 Anthropic 兼容端点对以下特性支持不完整:
578
- - thinking / redacted_thinking 块 (signature 由非 Anthropic 签发)
579
- - cache_control 字段
580
- - 跨供应商产物 (misplaced tool_result, 非标准 tool_use ID)
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: 移除 cache_control 字段
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: 移除 cache_control 字段
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 自清理: 仅修复 zhipu 自身无法消化的产物.
762
+ """zhipu → zhipu 自清理: 仅剥离 zhipu 自身的流式残块.
786
763
 
787
- GLM-5 偶发地在 assistant 消息中输出 ``tool_result`` 块(违反 Anthropic 规范),
788
- 或在流式响应中暴露 ``server_tool_use_delta`` 私有块。当 Claude Code 将这些
789
- 产物原样回送下一轮请求时,zhipu 的 Anthropic 兼容端点会以 400 拒绝
790
- (表现为 "400 + tool_results" 偶发,进而触发到 copilot 的降级)。
764
+ GLM-5 在流式响应中偶发暴露 ``server_tool_use_delta`` 私有块。当 Claude Code
765
+ 将这些产物原样回送下一轮请求时,zhipu Anthropic 兼容端点会拒绝。
791
766
 
792
- 本通道仅修复 zhipu 自身拒绝的两类产物,**保留** 所有 zhipu 原生支持的特性:
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
- 1. 剥离 ``server_tool_use_delta`` 流式残块
801
- 2. 强制 tool_use/tool_result 配对(关键: 把 assistant 内联的 tool_result
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 → 剥离 server_tool_use_delta + tool pairing."""
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 块被剥离, tool_result 被搬迁出 assistant
2075
+ # delta 块被剥离
2071
2076
  assistant_content = result["messages"][0]["content"]
2072
- assert all(
2073
- b.get("type") not in ("server_tool_use_delta", "tool_result")
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 result["messages"][1]["content"]
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 test_removes_cache_control(self):
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" not in prepared["system"][0]
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 test_enforces_tool_pairing(self):
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) == 1
298
- assert tool_results[0]["tool_use_id"] == "toolu_1"
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
- assert "cache_control" not in prepared["system"][0]
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) == 1
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 test_injects_id_on_tool_result_for_zhipu(self):
396
- """copilot → zhipu 转换后 tool_result 应包含 id 字段."""
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 tr["id"] == "toolu_001"
425
- assert any("injected" in a and "tool_result_id" in a for a in adaptations)
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 test_relocates_misplaced_tool_result(self):
762
- """assistant 内联 tool_result 应被搬迁到下一个 user 消息."""
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 消息中应不再包含 tool_result
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 user_content
795
+ for b in assistant_content
794
796
  )
795
- assert "misplaced_tool_result_relocated" in adaptations
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)
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 test_injects_id_on_existing_user_tool_result(self):
828
- """user 消息中已有的 tool_result 也应被注入 id 字段."""
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 tr["id"] == "toolu_001"
858
- assert any("injected" in a and "tool_result_id" in a for a in adaptations)
832
+ assert "id" not in tr
833
+ assert not any("injected" in a for a in adaptations)
859
834
 
860
- def test_skips_id_injection_when_already_present(self):
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": "toolu_001",
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
- # 不应产生注入 adaptation(id 已存在)
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 被剥, server_tool_use 保留, 错位 tool_result 搬迁.
1039
+ """端到端: server_tool_use_delta 被剥, 其余保留原位.
1064
1040
 
1065
- 典型场景: Claude Code 的客户端工具 (Bash/Read 等) 以 ``tool_use`` 形式
1066
- emit, 其错位的 ``tool_result`` 应被重定位; zhipu 原生 ``server_tool_use``
1067
- 块不需要客户端 tool_result, 仅需保留原状.
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
- # 错位 tool_result 被搬出 assistant
1102
- assert all(b.get("type") != "tool_result" for b in assistant_content)
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
- # 后续 user 消息已被插入并包含 tool_result
1114
- assert prepared["messages"][1]["role"] == "user"
1115
- assert any(
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
- assert "misplaced_tool_result_relocated" in adaptations
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 test_removes_cache_control(self):
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" not in prepared["system"][0]
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 test_enforces_tool_pairing(self):
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 "orphaned_tool_use_repaired" in adaptations
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) == 1
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 + cache_control 的组合清洗."""
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
- assert "cache_control" not in prepared["system"][0]
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 test_injects_id_on_tool_result_for_zhipu(self):
2883
- """anthropic → zhipu 转换后 tool_result 应包含 id 字段."""
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 tr["id"] == "toolu_001"
2912
- assert any("injected" in a and "tool_result_id" in a for a in adaptations)
2890
+ assert "id" not in tr
2891
+ assert not any("injected" in a for a in adaptations)
@@ -74,7 +74,7 @@ wheels = [
74
74
 
75
75
  [[package]]
76
76
  name = "coding-proxy"
77
- version = "0.3.1a5"
77
+ version = "0.3.1a6"
78
78
  source = { editable = "." }
79
79
  dependencies = [
80
80
  { name = "aiosqlite" },
File without changes
File without changes
File without changes