coding-proxy 0.3.1a4__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.1a4 → coding_proxy-0.3.1a6}/AGENTS.md +1 -0
  2. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/CHANGELOG.md +1 -0
  3. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/PKG-INFO +1 -1
  4. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/issue.md +66 -0
  5. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/pyproject.toml +1 -1
  6. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/vendor_channels.py +54 -44
  7. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_router_executor.py +12 -9
  8. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_vendor_channels.py +177 -39
  9. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/uv.lock +1 -1
  10. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/.github/workflows/ci.yml +0 -0
  11. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/.github/workflows/coverage.yml +0 -0
  12. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/.github/workflows/release.yml +0 -0
  13. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/.gitignore +0 -0
  14. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/.pre-commit-config.yaml +0 -0
  15. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/CLAUDE.md +0 -0
  16. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/LICENSE +0 -0
  17. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/README.md +0 -0
  18. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/assets/dashboard-v0.2.4.png +0 -0
  19. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/arch/config-reference.md +0 -0
  20. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/arch/convert.md +0 -0
  21. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/arch/design-patterns.md +0 -0
  22. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/arch/routing.md +0 -0
  23. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/arch/testing.md +0 -0
  24. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/arch/vendors.md +0 -0
  25. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/ci-cd.md +0 -0
  26. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/framework.md +0 -0
  27. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/guide/api-reference.md +0 -0
  28. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/guide/cli-reference.md +0 -0
  29. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/guide/dashboard.md +0 -0
  30. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/guide/monitoring.md +0 -0
  31. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/guide/quickstart.md +0 -0
  32. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/guide/vendors.md +0 -0
  33. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/user-guide.md +0 -0
  34. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/docs/zh-CN/README.md +0 -0
  35. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/__init__.py +0 -0
  36. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/__init__.py +0 -0
  37. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/__main__.py +0 -0
  38. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/__init__.py +0 -0
  39. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/providers/__init__.py +0 -0
  40. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/providers/base.py +0 -0
  41. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/providers/github.py +0 -0
  42. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/providers/google.py +0 -0
  43. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/runtime.py +0 -0
  44. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/auth/store.py +0 -0
  45. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/cli/__init__.py +0 -0
  46. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/cli/auth_commands.py +0 -0
  47. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/cli/banner.py +0 -0
  48. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/compat/__init__.py +0 -0
  49. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/compat/canonical.py +0 -0
  50. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/compat/session_store.py +0 -0
  51. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/config/__init__.py +0 -0
  52. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/config/auth_schema.py +0 -0
  53. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/config/config.default.yaml +0 -0
  54. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/config/loader.py +0 -0
  55. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/config/resiliency.py +0 -0
  56. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/config/routing.py +0 -0
  57. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/config/schema.py +0 -0
  58. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/config/server.py +0 -0
  59. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/config/vendors.py +0 -0
  60. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/__init__.py +0 -0
  61. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/anthropic_to_gemini.py +0 -0
  62. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/anthropic_to_openai.py +0 -0
  63. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/gemini_sse_adapter.py +0 -0
  64. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/gemini_to_anthropic.py +0 -0
  65. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/convert/openai_to_anthropic.py +0 -0
  66. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/logging/__init__.py +0 -0
  67. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/logging/db.py +0 -0
  68. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/logging/formatters.py +0 -0
  69. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/logging/stats.py +0 -0
  70. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/model/__init__.py +0 -0
  71. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/model/auth.py +0 -0
  72. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/model/compat.py +0 -0
  73. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/model/constants.py +0 -0
  74. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/model/pricing.py +0 -0
  75. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/model/token.py +0 -0
  76. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/model/vendor.py +0 -0
  77. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/__init__.py +0 -0
  78. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/config.py +0 -0
  79. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/extractors/__init__.py +0 -0
  80. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/extractors/anthropic.py +0 -0
  81. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/extractors/gemini.py +0 -0
  82. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/extractors/openai.py +0 -0
  83. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/handler.py +0 -0
  84. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/operation.py +0 -0
  85. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/routes.py +0 -0
  86. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/native_api/usage_registry.py +0 -0
  87. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/pricing.py +0 -0
  88. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/__init__.py +0 -0
  89. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/circuit_breaker.py +0 -0
  90. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/error_classifier.py +0 -0
  91. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/executor.py +0 -0
  92. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/model_mapper.py +0 -0
  93. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/quota_guard.py +0 -0
  94. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/rate_limit.py +0 -0
  95. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/retry.py +0 -0
  96. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/router.py +0 -0
  97. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/session_manager.py +0 -0
  98. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/tier.py +0 -0
  99. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/usage_parser.py +0 -0
  100. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/routing/usage_recorder.py +0 -0
  101. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/server/__init__.py +0 -0
  102. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/server/app.py +0 -0
  103. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/server/dashboard.py +0 -0
  104. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/server/factory.py +0 -0
  105. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/server/responses.py +0 -0
  106. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/server/routes.py +0 -0
  107. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/streaming/__init__.py +0 -0
  108. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/streaming/anthropic_compat.py +0 -0
  109. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/__init__.py +0 -0
  110. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/alibaba.py +0 -0
  111. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/anthropic.py +0 -0
  112. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/antigravity.py +0 -0
  113. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/base.py +0 -0
  114. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/copilot.py +0 -0
  115. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/copilot_models.py +0 -0
  116. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/copilot_token_manager.py +0 -0
  117. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/copilot_urls.py +0 -0
  118. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/doubao.py +0 -0
  119. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/kimi.py +0 -0
  120. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/minimax.py +0 -0
  121. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/mixins.py +0 -0
  122. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/native_anthropic.py +0 -0
  123. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/token_manager.py +0 -0
  124. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/xiaomi.py +0 -0
  125. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/src/coding/proxy/vendors/zhipu.py +0 -0
  126. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/__init__.py +0 -0
  127. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_antigravity.py +0 -0
  128. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_app_routes.py +0 -0
  129. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_auto_login.py +0 -0
  130. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_banner.py +0 -0
  131. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_circuit_breaker.py +0 -0
  132. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_cli_usage.py +0 -0
  133. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_compat.py +0 -0
  134. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_config_init.py +0 -0
  135. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_config_loader.py +0 -0
  136. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_convert_request.py +0 -0
  137. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_convert_response.py +0 -0
  138. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_convert_sse.py +0 -0
  139. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_copilot.py +0 -0
  140. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_copilot_convert_request.py +0 -0
  141. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_copilot_convert_response.py +0 -0
  142. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_copilot_models.py +0 -0
  143. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_copilot_urls.py +0 -0
  144. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_currency.py +0 -0
  145. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_error_classifier.py +0 -0
  146. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_logging_dual_write.py +0 -0
  147. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_mixins.py +0 -0
  148. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_model_auth.py +0 -0
  149. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_model_compat.py +0 -0
  150. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_model_constants.py +0 -0
  151. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_model_mapper.py +0 -0
  152. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_model_pricing.py +0 -0
  153. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_model_token.py +0 -0
  154. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_model_vendor.py +0 -0
  155. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_native_api_base_url_override.py +0 -0
  156. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_native_api_extractors.py +0 -0
  157. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_native_api_handler.py +0 -0
  158. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_native_api_operation.py +0 -0
  159. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_native_api_routes.py +0 -0
  160. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_native_vendors.py +0 -0
  161. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_parse_usage.py +0 -0
  162. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_parse_usage_gemini.py +0 -0
  163. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_pricing.py +0 -0
  164. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_quota_guard.py +0 -0
  165. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_rate_limit.py +0 -0
  166. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_router_chain.py +0 -0
  167. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_runtime_reauth.py +0 -0
  168. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_schema.py +0 -0
  169. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_streaming_anthropic_compat.py +0 -0
  170. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_tier.py +0 -0
  171. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_tiers_config.py +0 -0
  172. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_time_range.py +0 -0
  173. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_token_logger.py +0 -0
  174. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_token_logger_native_columns.py +0 -0
  175. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_token_manager.py +0 -0
  176. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_types.py +0 -0
  177. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_vendor_streaming.py +0 -0
  178. {coding_proxy-0.3.1a4 → coding_proxy-0.3.1a6}/tests/test_vendors.py +0 -0
  179. {coding_proxy-0.3.1a4 → 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
 
@@ -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.1a4
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
@@ -141,3 +141,69 @@ 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
+ ```
156
+
157
+ **表因**
158
+
159
+ zhipu 后端在解析 `tool_result` 内容块时错误地访问 `.id` 属性。但 Anthropic API 规范中 `tool_result` 块只有 `tool_use_id` 字段(用于关联对应的 `tool_use`),没有 `id` 字段。
160
+
161
+ **根因**(2026-04-29 复盘更新)
162
+
163
+ **初始诊断**(已推翻):认为 zhipu 后端期望 `tool_result` 有 `id` 字段,通过 `_inject_tool_result_id_for_zhipu` 注入 `id = tool_use_id` 可绕过。
164
+
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 注入
196
+
197
+ **注意**: `prepare_zhipu_to_anthropic` 和 `prepare_zhipu_to_copilot` 不受影响(目标是 anthropic/copilot,不是 zhipu),仍保留 `enforce_anthropic_tool_pairing`。
198
+
199
+ **后续防范**
200
+
201
+ - **转换通道的「最小干预」原则**:跨供应商转换应仅清理目标供应商**确认不支持**的特性。未经验证的「预防性清理」(如剥离 cache_control)可能误伤供应商原生支持的功能,甚至引入新的故障。
202
+ - **workaround 须验证有效**:`_inject_tool_result_id_for_zhipu` 虽有注释说明目的,但未经验证其有效性即合入。后续 workaround 须附带验证证据(如 curl 复现、上游确认)。
203
+ - **zhipu 后端 bug 跟踪**:`ClaudeContentBlockToolResult` 类缺少 `id` 属性是 zhipu 上游 bug。若 zhipu 修复此 bug,可考虑恢复 tool pairing 以获得更严格的消息结构校验。
204
+
205
+ **同类问题影响与处理注意事项**
206
+
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.1a4"
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"
@@ -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
 
@@ -542,13 +572,18 @@ def infer_source_vendor_from_body(body: dict[str, Any]) -> str | None:
542
572
  def prepare_copilot_to_zhipu(
543
573
  body: dict[str, Any],
544
574
  ) -> tuple[dict[str, Any], list[str]]:
545
- """copilot → zhipu 转换: 清理 copilot 产物以适配 GLM-5.
575
+ """copilot → zhipu 转换: 仅清理 copilot 产物中 zhipu 确认不支持的部分.
546
576
 
547
- GLM-5 的 Anthropic 兼容端点对以下特性支持不完整:
548
- - thinking / redacted_thinking 块 (signature 由非 Anthropic 签发)
549
- - cache_control 字段
550
- - 跨供应商产物 (misplaced tool_result, 非标准 tool_use ID)
551
- - 顶层 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。
552
587
 
553
588
  Returns:
554
589
  (prepared_body, adaptations) — adaptations 为应用的变换描述列表。
@@ -561,22 +596,12 @@ def prepare_copilot_to_zhipu(
561
596
  if stripped:
562
597
  adaptations.append(f"stripped_{stripped}_thinking_blocks")
563
598
 
564
- # Step 2: 移除 cache_control 字段
565
- removed_cc = _strip_cache_control(prepared)
566
- if removed_cc:
567
- adaptations.append(f"removed_{removed_cc}_cache_control_fields")
568
-
569
- # Step 3: 移除顶层 thinking/extended_thinking 参数(GLM-5 不支持)
599
+ # Step 2: 移除顶层 thinking/extended_thinking 参数(GLM-5 不支持)
570
600
  for param in ("thinking", "extended_thinking"):
571
601
  if param in prepared:
572
602
  del prepared[param]
573
603
  adaptations.append(f"removed_{param}_param")
574
604
 
575
- # Step 4: 强制 tool_use/tool_result 配对
576
- pairing_fixes = enforce_anthropic_tool_pairing(prepared.get("messages", []))
577
- if pairing_fixes:
578
- adaptations.extend(pairing_fixes)
579
-
580
605
  return prepared, adaptations
581
606
 
582
607
 
@@ -597,9 +622,11 @@ def prepare_anthropic_to_zhipu(
597
622
  Anthropic API 可能产生的非兼容产物:
598
623
  - ``server_tool_use`` blocks(web search / computer use 等 beta 功能)
599
624
  - ``thinking`` / ``redacted_thinking`` blocks(含 Anthropic 签发的 signature)
600
- - ``cache_control`` 字段
601
625
  - 顶层 ``thinking`` / ``extended_thinking`` 参数
602
626
 
627
+ 注意: 不再移除 cache_control (GLM-5 支持) ,不再执行 tool pairing 和
628
+ id 注入。原因同 prepare_copilot_to_zhipu 的 docstring。
629
+
603
630
  Returns:
604
631
  (prepared_body, adaptations) — adaptations 为应用的变换描述列表。
605
632
  """
@@ -616,22 +643,12 @@ def prepare_anthropic_to_zhipu(
616
643
  if stripped:
617
644
  adaptations.append(f"stripped_{stripped}_thinking_blocks")
618
645
 
619
- # Step 3: 移除 cache_control 字段
620
- removed_cc = _strip_cache_control(prepared)
621
- if removed_cc:
622
- adaptations.append(f"removed_{removed_cc}_cache_control_fields")
623
-
624
- # Step 4: 移除顶层 thinking/extended_thinking 参数(GLM-5 不支持)
646
+ # Step 3: 移除顶层 thinking/extended_thinking 参数(GLM-5 不支持)
625
647
  for param in ("thinking", "extended_thinking"):
626
648
  if param in prepared:
627
649
  del prepared[param]
628
650
  adaptations.append(f"removed_{param}_param")
629
651
 
630
- # Step 5: 强制 tool_use/tool_result 配对
631
- pairing_fixes = enforce_anthropic_tool_pairing(prepared.get("messages", []))
632
- if pairing_fixes:
633
- adaptations.extend(pairing_fixes)
634
-
635
652
  return prepared, adaptations
636
653
 
637
654
 
@@ -742,24 +759,22 @@ def prepare_zhipu_to_anthropic(
742
759
  def prepare_zhipu_self_cleanup(
743
760
  body: dict[str, Any],
744
761
  ) -> tuple[dict[str, Any], list[str]]:
745
- """zhipu → zhipu 自清理: 仅修复 zhipu 自身无法消化的产物.
762
+ """zhipu → zhipu 自清理: 仅剥离 zhipu 自身的流式残块.
746
763
 
747
- GLM-5 偶发地在 assistant 消息中输出 ``tool_result`` 块(违反 Anthropic 规范),
748
- 或在流式响应中暴露 ``server_tool_use_delta`` 私有块。当 Claude Code 将这些
749
- 产物原样回送下一轮请求时,zhipu 的 Anthropic 兼容端点会以 400 拒绝
750
- (表现为 "400 + tool_results" 偶发,进而触发到 copilot 的降级)。
764
+ GLM-5 在流式响应中偶发暴露 ``server_tool_use_delta`` 私有块。当 Claude Code
765
+ 将这些产物原样回送下一轮请求时,zhipu Anthropic 兼容端点会拒绝。
751
766
 
752
- 本通道仅修复 zhipu 自身拒绝的两类产物,**保留** 所有 zhipu 原生支持的特性:
767
+ 本通道**保留**所有 zhipu 原生支持的特性:
753
768
 
754
769
  - ✓ ``srvtoolu_*`` ID 与 ``server_tool_use`` 类型(zhipu 原生)
755
770
  - ✓ thinking blocks 的 zhipu 自签 signature
756
771
  - ✓ ``cache_control`` 字段(GLM Anthropic 端点支持,cache_read 已实证)
757
772
  - ✓ 顶层 ``thinking`` / ``extended_thinking`` 参数
773
+ - ✓ tool_result 在 assistant 消息中内联(zhipu 自身偶发产出,可自行消化)
758
774
 
759
- 清理操作(顺序、就地、幂等):
760
- 1. 剥离 ``server_tool_use_delta`` 流式残块
761
- 2. 强制 tool_use/tool_result 配对(关键: 把 assistant 内联的 tool_result
762
- 搬迁到紧随的 user 消息)
775
+ 注意: 不再执行 enforce_anthropic_tool_pairing 和 _inject_tool_result_id_for_zhipu。
776
+ 实证表明 tool_result 重定位会触发 zhipu 后端 500 错误。
777
+ 详见 docs/issue.md。
763
778
 
764
779
  Returns:
765
780
  (prepared_body, adaptations) — adaptations 为应用的变换描述列表。
@@ -772,11 +787,6 @@ def prepare_zhipu_self_cleanup(
772
787
  if removed_vendor_blocks:
773
788
  adaptations.append(f"removed_{removed_vendor_blocks}_zhipu_vendor_blocks")
774
789
 
775
- # Step 2: 强制 tool_use/tool_result 配对
776
- pairing_fixes = enforce_anthropic_tool_pairing(prepared.get("messages", []))
777
- if pairing_fixes:
778
- adaptations.extend(pairing_fixes)
779
-
780
790
  return prepared, adaptations
781
791
 
782
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."""