coding-proxy 0.3.1a8__tar.gz → 0.3.1a10__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 (183) hide show
  1. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/PKG-INFO +1 -1
  2. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/pyproject.toml +1 -1
  3. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/cli/__init__.py +95 -0
  4. coding_proxy-0.3.1a10/src/coding/proxy/routing/session_policy.py +116 -0
  5. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/server/dashboard.py +314 -47
  6. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/server/routes.py +91 -0
  7. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_session_aware.py +141 -0
  8. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/uv.lock +1 -1
  9. coding_proxy-0.3.1a8/src/coding/proxy/routing/session_policy.py +0 -56
  10. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/.github/workflows/ci.yml +0 -0
  11. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/.github/workflows/coverage.yml +0 -0
  12. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/.github/workflows/release.yml +0 -0
  13. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/.gitignore +0 -0
  14. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/.pre-commit-config.yaml +0 -0
  15. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/AGENTS.md +0 -0
  16. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/CHANGELOG.md +0 -0
  17. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/CLAUDE.md +0 -0
  18. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/LICENSE +0 -0
  19. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/README.md +0 -0
  20. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/assets/dashboard-v0.2.4.png +0 -0
  21. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/arch/config-reference.md +0 -0
  22. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/arch/convert.md +0 -0
  23. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/arch/design-patterns.md +0 -0
  24. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/arch/routing.md +0 -0
  25. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/arch/testing.md +0 -0
  26. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/arch/vendors.md +0 -0
  27. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/ci-cd.md +0 -0
  28. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/framework.md +0 -0
  29. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/guide/api-reference.md +0 -0
  30. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/guide/cli-reference.md +0 -0
  31. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/guide/dashboard.md +0 -0
  32. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/guide/monitoring.md +0 -0
  33. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/guide/quickstart.md +0 -0
  34. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/guide/vendors.md +0 -0
  35. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/issue.md +0 -0
  36. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/user-guide.md +0 -0
  37. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/docs/zh-CN/README.md +0 -0
  38. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/__init__.py +0 -0
  39. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/__init__.py +0 -0
  40. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/__main__.py +0 -0
  41. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/auth/__init__.py +0 -0
  42. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/auth/providers/__init__.py +0 -0
  43. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/auth/providers/base.py +0 -0
  44. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/auth/providers/github.py +0 -0
  45. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/auth/providers/google.py +0 -0
  46. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/auth/runtime.py +0 -0
  47. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/auth/store.py +0 -0
  48. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/cli/auth_commands.py +0 -0
  49. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/cli/banner.py +0 -0
  50. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/compat/__init__.py +0 -0
  51. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/compat/canonical.py +0 -0
  52. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/compat/session_store.py +0 -0
  53. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/__init__.py +0 -0
  54. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/auth_schema.py +0 -0
  55. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/config.default.yaml +0 -0
  56. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/loader.py +0 -0
  57. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/resiliency.py +0 -0
  58. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/routing.py +0 -0
  59. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/schema.py +0 -0
  60. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/server.py +0 -0
  61. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/session_policy.py +0 -0
  62. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/config/vendors.py +0 -0
  63. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/convert/__init__.py +0 -0
  64. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/convert/anthropic_to_gemini.py +0 -0
  65. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/convert/anthropic_to_openai.py +0 -0
  66. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/convert/gemini_sse_adapter.py +0 -0
  67. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/convert/gemini_to_anthropic.py +0 -0
  68. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/convert/openai_to_anthropic.py +0 -0
  69. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/convert/vendor_channels.py +0 -0
  70. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/logging/__init__.py +0 -0
  71. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/logging/db.py +0 -0
  72. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/logging/formatters.py +0 -0
  73. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/logging/stats.py +0 -0
  74. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/model/__init__.py +0 -0
  75. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/model/auth.py +0 -0
  76. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/model/compat.py +0 -0
  77. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/model/constants.py +0 -0
  78. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/model/pricing.py +0 -0
  79. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/model/token.py +0 -0
  80. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/model/vendor.py +0 -0
  81. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/__init__.py +0 -0
  82. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/config.py +0 -0
  83. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/extractors/__init__.py +0 -0
  84. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/extractors/anthropic.py +0 -0
  85. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/extractors/gemini.py +0 -0
  86. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/extractors/openai.py +0 -0
  87. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/handler.py +0 -0
  88. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/operation.py +0 -0
  89. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/routes.py +0 -0
  90. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/native_api/usage_registry.py +0 -0
  91. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/pricing.py +0 -0
  92. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/__init__.py +0 -0
  93. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/circuit_breaker.py +0 -0
  94. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/error_classifier.py +0 -0
  95. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/executor.py +0 -0
  96. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/model_mapper.py +0 -0
  97. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/quota_guard.py +0 -0
  98. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/rate_limit.py +0 -0
  99. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/retry.py +0 -0
  100. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/router.py +0 -0
  101. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/session_manager.py +0 -0
  102. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/tier.py +0 -0
  103. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/usage_parser.py +0 -0
  104. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/routing/usage_recorder.py +0 -0
  105. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/server/__init__.py +0 -0
  106. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/server/app.py +0 -0
  107. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/server/factory.py +0 -0
  108. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/server/responses.py +0 -0
  109. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/streaming/__init__.py +0 -0
  110. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/streaming/anthropic_compat.py +0 -0
  111. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/__init__.py +0 -0
  112. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/alibaba.py +0 -0
  113. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/anthropic.py +0 -0
  114. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/antigravity.py +0 -0
  115. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/base.py +0 -0
  116. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/copilot.py +0 -0
  117. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/copilot_models.py +0 -0
  118. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/copilot_token_manager.py +0 -0
  119. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/copilot_urls.py +0 -0
  120. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/doubao.py +0 -0
  121. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/kimi.py +0 -0
  122. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/minimax.py +0 -0
  123. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/mixins.py +0 -0
  124. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/native_anthropic.py +0 -0
  125. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/token_manager.py +0 -0
  126. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/xiaomi.py +0 -0
  127. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/src/coding/proxy/vendors/zhipu.py +0 -0
  128. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/__init__.py +0 -0
  129. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_antigravity.py +0 -0
  130. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_app_routes.py +0 -0
  131. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_auto_login.py +0 -0
  132. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_banner.py +0 -0
  133. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_circuit_breaker.py +0 -0
  134. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_cli_usage.py +0 -0
  135. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_compat.py +0 -0
  136. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_config_init.py +0 -0
  137. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_config_loader.py +0 -0
  138. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_convert_request.py +0 -0
  139. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_convert_response.py +0 -0
  140. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_convert_sse.py +0 -0
  141. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_copilot.py +0 -0
  142. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_copilot_convert_request.py +0 -0
  143. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_copilot_convert_response.py +0 -0
  144. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_copilot_models.py +0 -0
  145. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_copilot_urls.py +0 -0
  146. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_currency.py +0 -0
  147. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_error_classifier.py +0 -0
  148. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_logging_dual_write.py +0 -0
  149. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_mixins.py +0 -0
  150. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_model_auth.py +0 -0
  151. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_model_compat.py +0 -0
  152. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_model_constants.py +0 -0
  153. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_model_mapper.py +0 -0
  154. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_model_pricing.py +0 -0
  155. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_model_token.py +0 -0
  156. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_model_vendor.py +0 -0
  157. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_native_api_base_url_override.py +0 -0
  158. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_native_api_extractors.py +0 -0
  159. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_native_api_handler.py +0 -0
  160. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_native_api_operation.py +0 -0
  161. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_native_api_routes.py +0 -0
  162. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_native_vendors.py +0 -0
  163. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_parse_usage.py +0 -0
  164. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_parse_usage_gemini.py +0 -0
  165. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_pricing.py +0 -0
  166. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_quota_guard.py +0 -0
  167. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_rate_limit.py +0 -0
  168. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_router_chain.py +0 -0
  169. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_router_executor.py +0 -0
  170. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_runtime_reauth.py +0 -0
  171. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_schema.py +0 -0
  172. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_streaming_anthropic_compat.py +0 -0
  173. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_tier.py +0 -0
  174. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_tiers_config.py +0 -0
  175. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_time_range.py +0 -0
  176. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_token_logger.py +0 -0
  177. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_token_logger_native_columns.py +0 -0
  178. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_token_manager.py +0 -0
  179. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_types.py +0 -0
  180. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_vendor_channels.py +0 -0
  181. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_vendor_streaming.py +0 -0
  182. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_vendors.py +0 -0
  183. {coding_proxy-0.3.1a8 → coding_proxy-0.3.1a10}/tests/test_zhipu.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coding-proxy
3
- Version: 0.3.1a8
3
+ Version: 0.3.1a10
4
4
  Summary: A High-Availability, Transparent, and Smart Multi-Vendor Proxy for Claude Code. Support Claude Plans, GitHub Copilot, Google Antigravity, ZAI/GLM, MiniMax, Qwen, Xiaomi, Kimi, Doubao...
5
5
  Project-URL: Source Code, https://github.com/ThreeFish-AI/coding-proxy
6
6
  Project-URL: User Guide, https://github.com/ThreeFish-AI/coding-proxy/blob/master/docs/user-guide.md
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "coding-proxy"
3
- version = "0.3.1a8"
3
+ version = "0.3.1a10"
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"
@@ -30,6 +30,10 @@ logger = logging.getLogger(__name__)
30
30
  # 注册 Auth 子应用
31
31
  app.add_typer(auth_app, name="auth")
32
32
 
33
+ # 注册 Session 子应用
34
+ session_app = typer.Typer(name="session", help="管理 Session-Vendor 运行时绑定")
35
+ app.add_typer(session_app, name="session")
36
+
33
37
 
34
38
  def _build_token_store(cfg_path: Path | None = None):
35
39
  """按配置解析 Token Store 路径并完成加载."""
@@ -264,6 +268,97 @@ def reset(
264
268
  console.print("[red]代理服务未运行[/red]")
265
269
 
266
270
 
271
+ # ── Session 子命令 ───────────────────────────────────────────────
272
+
273
+
274
+ @session_app.command("bind")
275
+ def session_bind(
276
+ key: str = typer.Option(..., "--key", "-k", help="Session key"),
277
+ vendor: str = typer.Option(
278
+ ..., "--vendor", "-v", help="绑定 vendor(逗号分隔多个)"
279
+ ),
280
+ port: int = typer.Option(3392, "--port", "-p", help="代理服务端口"),
281
+ ) -> None:
282
+ """为指定 Session 绑定 vendor 优先级."""
283
+ import httpx as _httpx
284
+
285
+ vendors = [v.strip() for v in vendor.split(",") if v.strip()]
286
+ try:
287
+ resp = _httpx.put(
288
+ f"http://127.0.0.1:{port}/api/session-vendor",
289
+ json={"session_key": key, "vendors": vendors},
290
+ timeout=5,
291
+ )
292
+ if resp.status_code == 200:
293
+ data = resp.json()
294
+ console.print(
295
+ f"[green]绑定成功:[/] session [cyan]{key[:16]}…[/cyan] → "
296
+ + " → ".join(data.get("vendors", vendors))
297
+ )
298
+ else:
299
+ try:
300
+ err = resp.json()
301
+ msg = err.get("error", {}).get("message", resp.text)
302
+ except Exception:
303
+ msg = resp.text
304
+ console.print(f"[red]绑定失败: {msg}[/red]")
305
+ except _httpx.ConnectError:
306
+ console.print("[red]代理服务未运行[/red]")
307
+
308
+
309
+ @session_app.command("unbind")
310
+ def session_unbind(
311
+ key: str = typer.Option(..., "--key", "-k", help="Session key"),
312
+ port: int = typer.Option(3392, "--port", "-p", help="代理服务端口"),
313
+ ) -> None:
314
+ """解除指定 Session 的 vendor 绑定."""
315
+ from urllib.parse import quote
316
+
317
+ import httpx as _httpx
318
+
319
+ try:
320
+ resp = _httpx.delete(
321
+ f"http://127.0.0.1:{port}/api/session-vendor/{quote(key, safe='')}",
322
+ timeout=5,
323
+ )
324
+ if resp.status_code == 200:
325
+ console.print(f"[green]已解除绑定:[/] session [cyan]{key[:16]}…[/cyan]")
326
+ elif resp.status_code == 404:
327
+ console.print(f"[yellow]未找到绑定:[/] session [cyan]{key[:16]}…[/cyan]")
328
+ else:
329
+ console.print(f"[red]解除失败: {resp.status_code} {resp.text}[/red]")
330
+ except _httpx.ConnectError:
331
+ console.print("[red]代理服务未运行[/red]")
332
+
333
+
334
+ @session_app.command("list")
335
+ def session_list(
336
+ port: int = typer.Option(3392, "--port", "-p", help="代理服务端口"),
337
+ ) -> None:
338
+ """列出所有运行时 Session-Vendor 绑定."""
339
+ import httpx as _httpx
340
+
341
+ try:
342
+ resp = _httpx.get(
343
+ f"http://127.0.0.1:{port}/api/session-vendor",
344
+ timeout=5,
345
+ )
346
+ if resp.status_code == 200:
347
+ data = resp.json()
348
+ bindings = data.get("bindings", [])
349
+ if not bindings:
350
+ console.print("[dim]当前无运行时绑定[/dim]")
351
+ return
352
+ for b in bindings:
353
+ key = b.get("session_key", "?")
354
+ vendors = b.get("vendors", [])
355
+ console.print(f" [cyan]{key[:24]}…[/cyan] → " + " → ".join(vendors))
356
+ else:
357
+ console.print(f"[red]查询失败: {resp.status_code} {resp.text}[/red]")
358
+ except _httpx.ConnectError:
359
+ console.print("[red]代理服务未运行[/red]")
360
+
361
+
267
362
  def _resolve_config_path(config: str | Path | None = None) -> Path | None:
268
363
  """标准化配置路径输入."""
269
364
  if config is None:
@@ -0,0 +1,116 @@
1
+ """Session Policy 解析引擎 — 根据 session_key + client_category 解析适用的路由策略."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import threading
7
+
8
+ from ..config.session_policy import SessionPolicy, SessionPolicyMatch
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class SessionPolicyResolver:
14
+ """根据 session_key + client_category 解析适用的 SessionPolicy.
15
+
16
+ 设计要点:
17
+ - 启动时构建索引,运行时 O(1) 查找
18
+ - 精确匹配优先:session_key > client_category > 无策略
19
+ - 无侵入性:不匹配时返回 None,路由行为与现有一致
20
+ - 运行时可变:支持 API 动态 upsert/remove session → vendor 绑定
21
+ """
22
+
23
+ def __init__(self, policies: list[SessionPolicy] | None = None) -> None:
24
+ self._policies = policies or []
25
+ self._key_index: dict[str, SessionPolicy] = {}
26
+ self._category_index: dict[str, SessionPolicy] = {}
27
+ self._config_key_backup: dict[str, SessionPolicy] = {}
28
+ self._lock = threading.Lock()
29
+ self._build_index()
30
+
31
+ def _build_index(self) -> None:
32
+ """构建 session_key / client_category → SessionPolicy 的查找索引.
33
+
34
+ 按定义顺序遍历,首次出现的 key/category 获得最高优先级。
35
+ """
36
+ for policy in self._policies:
37
+ for key in policy.match.session_keys:
38
+ if key not in self._key_index:
39
+ self._key_index[key] = policy
40
+ if (
41
+ policy.match.client_category
42
+ and policy.match.client_category not in self._category_index
43
+ ):
44
+ self._category_index[policy.match.client_category] = policy
45
+
46
+ if self._key_index or self._category_index:
47
+ logger.info(
48
+ "SessionPolicyResolver initialized: %d key rules, %d category rules",
49
+ len(self._key_index),
50
+ len(self._category_index),
51
+ )
52
+
53
+ def resolve(
54
+ self, session_key: str, client_category: str = "cc"
55
+ ) -> SessionPolicy | None:
56
+ """返回匹配的策略,优先精确 session_key 匹配,其次 category 匹配.
57
+
58
+ 返回的 SessionPolicy 对象应为不可变引用;调用方不应修改其内部属性,
59
+ 否则在并发 upsert/remove 场景下可能产生竞态。
60
+ """
61
+ with self._lock:
62
+ policy = self._key_index.get(session_key)
63
+ if policy:
64
+ return policy
65
+ return self._category_index.get(client_category)
66
+
67
+ # ── 运行时 session → vendor 绑定 ──────────────────────────────
68
+
69
+ def upsert(self, session_key: str, tier_names: list[str]) -> SessionPolicy:
70
+ """为指定 session key 创建或替换运行时 vendor 绑定.
71
+
72
+ 运行时策略使用 ``runtime:`` 名称前缀,与配置文件驱动的策略区分。
73
+ """
74
+ policy = SessionPolicy(
75
+ name=f"runtime:{session_key}",
76
+ match=SessionPolicyMatch(session_keys=[session_key]),
77
+ tiers=tier_names,
78
+ )
79
+ with self._lock:
80
+ existing = self._key_index.get(session_key)
81
+ if existing and not existing.name.startswith("runtime:"):
82
+ self._config_key_backup[session_key] = existing
83
+ self._key_index[session_key] = policy
84
+ logger.info(
85
+ "Session vendor binding upserted: session_key=%s → %s",
86
+ session_key,
87
+ tier_names,
88
+ )
89
+ return policy
90
+
91
+ def remove(self, session_key: str) -> bool:
92
+ """删除指定 session key 的运行时 vendor 绑定.
93
+
94
+ Returns:
95
+ True 如果找到并删除了绑定,False 如果不存在。
96
+ """
97
+ with self._lock:
98
+ policy = self._key_index.get(session_key)
99
+ if policy is None or not policy.name.startswith("runtime:"):
100
+ return False
101
+ del self._key_index[session_key]
102
+ # 恢复被运行时绑定覆盖的配置策略
103
+ backup = self._config_key_backup.pop(session_key, None)
104
+ if backup is not None:
105
+ self._key_index[session_key] = backup
106
+ logger.info("Session vendor binding removed: session_key=%s", session_key)
107
+ return True
108
+
109
+ def list_runtime_bindings(self) -> list[dict[str, str | list[str]]]:
110
+ """返回所有运行时注入的绑定快照(仅 API 创建的,不含配置文件驱动的)."""
111
+ with self._lock:
112
+ return [
113
+ {"session_key": key, "vendors": policy.tiers}
114
+ for key, policy in self._key_index.items()
115
+ if policy.name.startswith("runtime:")
116
+ ]