coding-proxy 0.4.1a6__tar.gz → 0.4.1a7__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 (191) hide show
  1. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/PKG-INFO +1 -1
  2. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/pyproject.toml +1 -1
  3. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/logging/db.py +38 -1
  4. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/routing/executor.py +34 -3
  5. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/routing/session_manager.py +9 -4
  6. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/routing/usage_recorder.py +5 -0
  7. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/server/dashboard.py +17 -11
  8. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_router_executor.py +6 -5
  9. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/uv.lock +1 -1
  10. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/.github/workflows/ci.yml +0 -0
  11. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/.github/workflows/coverage.yml +0 -0
  12. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/.github/workflows/release.yml +0 -0
  13. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/.gitignore +0 -0
  14. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/.pre-commit-config.yaml +0 -0
  15. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/AGENTS.md +0 -0
  16. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/CHANGELOG.md +0 -0
  17. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/CLAUDE.md +0 -0
  18. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/LICENSE +0 -0
  19. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/README.md +0 -0
  20. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/assets/dashboard-v0.4.0.png +0 -0
  21. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/assets/session-v0.4.0.png +0 -0
  22. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/agents/browser-validation.md +0 -0
  23. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/agents/issue.md +0 -0
  24. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/agents/knowledge-map.md +0 -0
  25. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/agents/reference-specifications.md +0 -0
  26. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/arch/config-reference.md +0 -0
  27. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/arch/convert.md +0 -0
  28. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/arch/design-patterns.md +0 -0
  29. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/arch/routing.md +0 -0
  30. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/arch/testing.md +0 -0
  31. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/arch/vendors.md +0 -0
  32. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/framework.md +0 -0
  33. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/guide/api-reference.md +0 -0
  34. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/guide/cli-reference.md +0 -0
  35. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/guide/dashboard.md +0 -0
  36. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/guide/monitoring.md +0 -0
  37. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/guide/quickstart.md +0 -0
  38. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/guide/vendors.md +0 -0
  39. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/ops/ci-cd.md +0 -0
  40. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/user-guide.md +0 -0
  41. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/docs/zh-CN/README.md +0 -0
  42. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/__init__.py +0 -0
  43. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/__init__.py +0 -0
  44. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/__main__.py +0 -0
  45. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/auth/__init__.py +0 -0
  46. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/auth/providers/__init__.py +0 -0
  47. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/auth/providers/base.py +0 -0
  48. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/auth/providers/github.py +0 -0
  49. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/auth/providers/google.py +0 -0
  50. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/auth/runtime.py +0 -0
  51. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/auth/store.py +0 -0
  52. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/cli/__init__.py +0 -0
  53. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/cli/auth_commands.py +0 -0
  54. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/cli/banner.py +0 -0
  55. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/compat/__init__.py +0 -0
  56. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/compat/canonical.py +0 -0
  57. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/compat/session_store.py +0 -0
  58. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/config/__init__.py +0 -0
  59. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/config/auth_schema.py +0 -0
  60. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/config/config.default.yaml +0 -0
  61. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/config/loader.py +0 -0
  62. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/config/resiliency.py +0 -0
  63. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/config/routing.py +0 -0
  64. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/config/schema.py +0 -0
  65. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/config/server.py +0 -0
  66. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/config/session_policy.py +0 -0
  67. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/config/vendors.py +0 -0
  68. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/convert/__init__.py +0 -0
  69. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/convert/anthropic_to_gemini.py +0 -0
  70. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/convert/anthropic_to_openai.py +0 -0
  71. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/convert/gemini_sse_adapter.py +0 -0
  72. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/convert/gemini_to_anthropic.py +0 -0
  73. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/convert/openai_to_anthropic.py +0 -0
  74. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/convert/vendor_channels.py +0 -0
  75. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/logging/__init__.py +0 -0
  76. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/logging/formatters.py +0 -0
  77. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/logging/stats.py +0 -0
  78. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/model/__init__.py +0 -0
  79. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/model/auth.py +0 -0
  80. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/model/compat.py +0 -0
  81. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/model/constants.py +0 -0
  82. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/model/pricing.py +0 -0
  83. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/model/token.py +0 -0
  84. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/model/vendor.py +0 -0
  85. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/native_api/__init__.py +0 -0
  86. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/native_api/config.py +0 -0
  87. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/native_api/extractors/__init__.py +0 -0
  88. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/native_api/extractors/anthropic.py +0 -0
  89. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/native_api/extractors/gemini.py +0 -0
  90. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/native_api/extractors/openai.py +0 -0
  91. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/native_api/handler.py +0 -0
  92. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/native_api/operation.py +0 -0
  93. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/native_api/routes.py +0 -0
  94. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/native_api/usage_registry.py +0 -0
  95. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/pricing.py +0 -0
  96. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/routing/__init__.py +0 -0
  97. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/routing/circuit_breaker.py +0 -0
  98. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/routing/error_classifier.py +0 -0
  99. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/routing/model_mapper.py +0 -0
  100. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/routing/quota_guard.py +0 -0
  101. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/routing/rate_limit.py +0 -0
  102. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/routing/retry.py +0 -0
  103. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/routing/router.py +0 -0
  104. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/routing/session_policy.py +0 -0
  105. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/routing/tier.py +0 -0
  106. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/routing/usage_parser.py +0 -0
  107. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/server/__init__.py +0 -0
  108. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/server/app.py +0 -0
  109. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/server/factory.py +0 -0
  110. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/server/responses.py +0 -0
  111. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/server/routes.py +0 -0
  112. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/streaming/__init__.py +0 -0
  113. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/streaming/anthropic_compat.py +0 -0
  114. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/__init__.py +0 -0
  115. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/alibaba.py +0 -0
  116. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/anthropic.py +0 -0
  117. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/antigravity.py +0 -0
  118. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/base.py +0 -0
  119. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/copilot.py +0 -0
  120. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/copilot_models.py +0 -0
  121. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/copilot_token_manager.py +0 -0
  122. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/copilot_urls.py +0 -0
  123. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/doubao.py +0 -0
  124. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/kimi.py +0 -0
  125. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/minimax.py +0 -0
  126. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/mixins.py +0 -0
  127. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/native_anthropic.py +0 -0
  128. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/token_manager.py +0 -0
  129. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/xiaomi.py +0 -0
  130. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/src/coding/proxy/vendors/zhipu.py +0 -0
  131. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/__init__.py +0 -0
  132. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/e2e/__init__.py +0 -0
  133. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/e2e/conftest.py +0 -0
  134. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/e2e/test_e2e_http.py +0 -0
  135. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/e2e/test_e2e_token.py +0 -0
  136. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/e2e/test_e2e_vendor.py +0 -0
  137. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_antigravity.py +0 -0
  138. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_app_routes.py +0 -0
  139. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_auto_login.py +0 -0
  140. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_banner.py +0 -0
  141. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_circuit_breaker.py +0 -0
  142. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_cli_usage.py +0 -0
  143. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_compat.py +0 -0
  144. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_config_init.py +0 -0
  145. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_config_loader.py +0 -0
  146. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_convert_request.py +0 -0
  147. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_convert_response.py +0 -0
  148. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_convert_sse.py +0 -0
  149. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_copilot.py +0 -0
  150. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_copilot_convert_request.py +0 -0
  151. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_copilot_convert_response.py +0 -0
  152. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_copilot_models.py +0 -0
  153. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_copilot_urls.py +0 -0
  154. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_currency.py +0 -0
  155. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_error_classifier.py +0 -0
  156. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_logging_dual_write.py +0 -0
  157. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_mixins.py +0 -0
  158. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_model_auth.py +0 -0
  159. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_model_compat.py +0 -0
  160. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_model_constants.py +0 -0
  161. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_model_mapper.py +0 -0
  162. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_model_pricing.py +0 -0
  163. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_model_token.py +0 -0
  164. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_model_vendor.py +0 -0
  165. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_native_api_base_url_override.py +0 -0
  166. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_native_api_extractors.py +0 -0
  167. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_native_api_handler.py +0 -0
  168. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_native_api_operation.py +0 -0
  169. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_native_api_routes.py +0 -0
  170. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_native_vendors.py +0 -0
  171. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_parse_usage.py +0 -0
  172. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_parse_usage_gemini.py +0 -0
  173. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_pricing.py +0 -0
  174. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_quota_guard.py +0 -0
  175. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_rate_limit.py +0 -0
  176. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_router_chain.py +0 -0
  177. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_runtime_reauth.py +0 -0
  178. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_schema.py +0 -0
  179. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_session_aware.py +0 -0
  180. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_streaming_anthropic_compat.py +0 -0
  181. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_tier.py +0 -0
  182. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_tiers_config.py +0 -0
  183. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_time_range.py +0 -0
  184. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_token_logger.py +0 -0
  185. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_token_logger_native_columns.py +0 -0
  186. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_token_manager.py +0 -0
  187. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_types.py +0 -0
  188. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_vendor_channels.py +0 -0
  189. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_vendor_streaming.py +0 -0
  190. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_vendors.py +0 -0
  191. {coding_proxy-0.4.1a6 → coding_proxy-0.4.1a7}/tests/test_zhipu.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coding-proxy
3
- Version: 0.4.1a6
3
+ Version: 0.4.1a7
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.4.1a6"
3
+ version = "0.4.1a7"
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"
@@ -190,6 +190,14 @@ CREATE TABLE IF NOT EXISTS usage_evidence (
190
190
  );
191
191
  """
192
192
 
193
+ _CREATE_SESSION_META = """
194
+ CREATE TABLE IF NOT EXISTS session_meta (
195
+ session_key TEXT PRIMARY KEY,
196
+ title TEXT NOT NULL DEFAULT '',
197
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
198
+ );
199
+ """
200
+
193
201
  _CREATE_INDEXES = """
194
202
  CREATE INDEX IF NOT EXISTS idx_usage_ts ON usage_log(ts);
195
203
  CREATE INDEX IF NOT EXISTS idx_usage_vendor ON usage_log(vendor);
@@ -245,6 +253,7 @@ class TokenLogger:
245
253
  self._db.row_factory = aiosqlite.Row
246
254
  await self._db.execute("PRAGMA journal_mode=WAL")
247
255
  await self._db.executescript(_CREATE_TABLES)
256
+ await self._db.executescript(_CREATE_SESSION_META)
248
257
  # 迁移必须在建索引之前执行,确保 vendor 列已存在
249
258
  await self._migrate_rename_backend_to_vendor()
250
259
  await self._migrate_add_failover_from()
@@ -316,6 +325,28 @@ class TokenLogger:
316
325
  "Migration: renamed 'backend' column to 'vendor' in %s", table
317
326
  )
318
327
 
328
+ async def set_session_title(self, session_key: str, title: str) -> None:
329
+ """为新 session 设置标题(幂等,仅首次写入)."""
330
+ if not self._db or not title or not session_key:
331
+ return
332
+ await self._db.execute(
333
+ "INSERT OR IGNORE INTO session_meta (session_key, title) VALUES (?, ?)",
334
+ (session_key, title),
335
+ )
336
+ await self._db.commit()
337
+
338
+ async def get_session_titles(self, session_keys: list[str]) -> dict[str, str]:
339
+ """批量查询 session 标题."""
340
+ if not self._db or not session_keys:
341
+ return {}
342
+ placeholders = ",".join("?" for _ in session_keys)
343
+ cursor = await self._db.execute(
344
+ f"SELECT session_key, title FROM session_meta WHERE session_key IN ({placeholders})",
345
+ session_keys,
346
+ )
347
+ rows = await cursor.fetchall()
348
+ return {row["session_key"]: row["title"] for row in rows}
349
+
319
350
  async def log(
320
351
  self,
321
352
  vendor: str,
@@ -621,7 +652,13 @@ class TokenLogger:
621
652
  (cutoff_iso, limit),
622
653
  )
623
654
  rows = await cursor.fetchall()
624
- return [dict(row) for row in rows]
655
+ sessions = [dict(row) for row in rows]
656
+ if sessions:
657
+ keys = [s["session_key"] for s in sessions]
658
+ titles = await self.get_session_titles(keys)
659
+ for s in sessions:
660
+ s["title"] = titles.get(s["session_key"], "")
661
+ return sessions
625
662
 
626
663
  async def query_session_profile(self, session_key: str) -> dict | None:
627
664
  """查询单个会话的完整聚合数据."""
@@ -43,10 +43,29 @@ from .usage_recorder import UsageRecorder
43
43
  # 向后兼容别名
44
44
  BackendResponse = VendorResponse
45
45
  NoCompatibleBackendError = NoCompatibleVendorError
46
- from ..compat.canonical import CompatibilityStatus, build_canonical_request
46
+ from ..compat.canonical import (
47
+ CanonicalPartType,
48
+ CompatibilityStatus,
49
+ build_canonical_request,
50
+ )
51
+ from ..model.compat import CanonicalRequest
47
52
 
48
53
  logger = logging.getLogger(__name__)
49
54
 
55
+ _SESSION_TITLE_MAX_LEN = 30
56
+
57
+
58
+ def _extract_session_title(request: CanonicalRequest) -> str:
59
+ """从规范化请求中提取首个用户消息文本作为 session 标题."""
60
+ for part in request.messages:
61
+ if (
62
+ part.role == "user"
63
+ and part.type == CanonicalPartType.TEXT
64
+ and part.text.strip()
65
+ ):
66
+ return part.text.strip()[:_SESSION_TITLE_MAX_LEN]
67
+ return ""
68
+
50
69
 
51
70
  def _build_semantic_rejection_diagnostic(body: dict[str, Any]) -> str:
52
71
  """构建语义拒绝的请求体诊断上下文.
@@ -393,10 +412,16 @@ class _RouteExecutor:
393
412
  failed_tier_name: str | None = None
394
413
  request_caps = build_request_capabilities(body)
395
414
  canonical_request = build_canonical_request(body, headers)
396
- session_record = await self._session_mgr.get_or_create_record(
415
+ session_record, is_new_session = await self._session_mgr.get_or_create_record(
397
416
  canonical_request.session_key,
398
417
  canonical_request.trace_id,
399
418
  )
419
+ if is_new_session:
420
+ title = _extract_session_title(canonical_request)
421
+ if title:
422
+ await self._recorder.set_session_title(
423
+ canonical_request.session_key, title
424
+ )
400
425
  incompatible_reasons: list[str] = []
401
426
  effective_tiers = self._resolve_effective_tiers(canonical_request.session_key)
402
427
  last_idx = len(effective_tiers) - 1
@@ -564,10 +589,16 @@ class _RouteExecutor:
564
589
  failed_tier_name: str | None = None
565
590
  request_caps = build_request_capabilities(body)
566
591
  canonical_request = build_canonical_request(body, headers)
567
- session_record = await self._session_mgr.get_or_create_record(
592
+ session_record, is_new_session = await self._session_mgr.get_or_create_record(
568
593
  canonical_request.session_key,
569
594
  canonical_request.trace_id,
570
595
  )
596
+ if is_new_session:
597
+ title = _extract_session_title(canonical_request)
598
+ if title:
599
+ await self._recorder.set_session_title(
600
+ canonical_request.session_key, title
601
+ )
571
602
  incompatible_reasons: list[str] = []
572
603
  effective_tiers = self._resolve_effective_tiers(canonical_request.session_key)
573
604
  last_idx = len(effective_tiers) - 1
@@ -19,13 +19,18 @@ class RouteSessionManager:
19
19
 
20
20
  async def get_or_create_record(
21
21
  self, session_key: str, trace_id: str
22
- ) -> CompatSessionRecord | None:
22
+ ) -> tuple[CompatSessionRecord | None, bool]:
23
+ """获取或创建兼容性会话记录.
24
+
25
+ Returns:
26
+ (record, is_new) — is_new 为 True 表示本次创建的新会话。
27
+ """
23
28
  if self._store is None:
24
- return None
29
+ return None, False
25
30
  record = await self._store.get(session_key)
26
31
  if record is not None:
27
- return record
28
- return CompatSessionRecord(session_key=session_key, trace_id=trace_id)
32
+ return record, False
33
+ return CompatSessionRecord(session_key=session_key, trace_id=trace_id), True
29
34
 
30
35
  def apply_compat_context(
31
36
  self,
@@ -28,6 +28,11 @@ class UsageRecorder:
28
28
  def set_pricing_table(self, table: PricingTable) -> None:
29
29
  self._pricing_table = table
30
30
 
31
+ async def set_session_title(self, session_key: str, title: str) -> None:
32
+ """为新 session 设置标题(委托给 TokenLogger)."""
33
+ if self._token_logger:
34
+ await self._token_logger.set_session_title(session_key, title)
35
+
31
36
  # ── 用量信息构建 ──────────────────────────────────────
32
37
 
33
38
  @staticmethod
@@ -411,6 +411,7 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
411
411
  .session-table td.cell-tags { white-space: normal; overflow: visible; text-overflow: clip; line-height: 1.8; vertical-align: middle; }
412
412
  .session-table tr:hover td { background: var(--bg-card-hover); }
413
413
  .session-table .session-key { font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--accent-blue); cursor: default; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
414
+ .session-table .session-title { font-size: 12px; color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 0; }
414
415
  .session-id { display: flex; align-items: center; gap: 4px; }
415
416
  .session-id-text { overflow: hidden; text-overflow: ellipsis; }
416
417
  .copy-btn { background: none; border: none; color: var(--text-tertiary); cursor: pointer; padding: 2px; border-radius: 4px; font-size: 12px; line-height: 1; opacity: .5; flex-shrink: 0; }
@@ -676,20 +677,22 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
676
677
  <div class="session-table-wrap" id="sessions-table-wrap">
677
678
  <table class="session-table">
678
679
  <colgroup>
679
- <col style="width:12%">
680
- <col style="width:7%">
680
+ <col style="width:10%">
681
+ <col style="width:15%">
681
682
  <col style="width:6%">
683
+ <col style="width:5%">
684
+ <col style="width:5%">
685
+ <col style="width:15%">
686
+ <col style="width:10%">
682
687
  <col style="width:6%">
683
- <col style="width:17%">
684
- <col style="width:12%">
685
- <col style="width:7%">
686
- <col style="width:9%">
687
- <col style="width:12%">
688
- <col style="width:12%">
688
+ <col style="width:8%">
689
+ <col style="width:10%">
690
+ <col style="width:10%">
689
691
  </colgroup>
690
692
  <thead>
691
693
  <tr>
692
694
  <th>Session ID</th>
695
+ <th>Title</th>
693
696
  <th>Last Active</th>
694
697
  <th>Requests</th>
695
698
  <th>Tokens</th>
@@ -702,7 +705,7 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
702
705
  </tr>
703
706
  </thead>
704
707
  <tbody id="sessions-tbody">
705
- <tr><td colspan="10" class="empty">Loading...</td></tr>
708
+ <tr><td colspan="11" class="empty">Loading...</td></tr>
706
709
  </tbody>
707
710
  </table>
708
711
  <div class="session-pagination" id="session-pagination">
@@ -1573,7 +1576,7 @@ function renderSessionPage() {
1573
1576
  var tbody = document.getElementById('sessions-tbody');
1574
1577
 
1575
1578
  if (!total) {
1576
- tbody.innerHTML = '<tr><td colspan="10" class="empty"><div class="empty-icon">📭</div>No session data</td></tr>';
1579
+ tbody.innerHTML = '<tr><td colspan="11" class="empty"><div class="empty-icon">📭</div>No session data</td></tr>';
1577
1580
  } else {
1578
1581
  tbody.innerHTML = page.map(function(s) {
1579
1582
  var parsed = parseSessionKey(s.session_key);
@@ -1582,6 +1585,7 @@ function renderSessionPage() {
1582
1585
  var modelsFull = (s.models || '').split(',').map(function(c){return c.trim();});
1583
1586
  var vendorsFull = (s.vendors || '').split(',').map(function(v){return formatVendorLabel(v.trim());});
1584
1587
  var sr = s.success_rate != null ? Math.round(s.success_rate) : null;
1588
+ var sessionTitle = s.title || '';
1585
1589
  return '<tr data-row onclick="toggleRow(this)">' +
1586
1590
  '<td class="session-key" onclick="event.stopPropagation()">' +
1587
1591
  '<div class="session-id" data-key="' + escapeHtml(s.session_key) + '" title="' + escapeHtml(s.session_key) + '">' +
@@ -1592,6 +1596,7 @@ function renderSessionPage() {
1592
1596
  'dev:' + escapeHtml(shortId(parsed.device_id, 8)) + ' · acct:' + escapeHtml(shortId(parsed.account_uuid, 8)) +
1593
1597
  '</div>' +
1594
1598
  '</td>' +
1599
+ '<td class="session-title" title="' + escapeHtml(sessionTitle) + '">' + (sessionTitle ? escapeHtml(sessionTitle) : '–') + '</td>' +
1595
1600
  '<td>' + relativeTime(s.last_active_ts) + '</td>' +
1596
1601
  '<td style="font-family:JetBrains Mono,monospace">' + fmtNum(s.total_requests) + '</td>' +
1597
1602
  '<td style="font-family:JetBrains Mono,monospace">' + fmtTokens(s.total_tokens) + '</td>' +
@@ -1602,9 +1607,10 @@ function renderSessionPage() {
1602
1607
  '<td onclick="event.stopPropagation()">' + selectHtml + '</td>' +
1603
1608
  '<td>' + formatCategories(s.client_categories) + '</td>' +
1604
1609
  '</tr>' +
1605
- '<tr class="row-detail"><td colspan="10"><div class="detail-card">' +
1610
+ '<tr class="row-detail"><td colspan="11"><div class="detail-card">' +
1606
1611
  '<div class="detail-identity-row">' +
1607
1612
  '<div class="detail-item"><div class="detail-label">Session ID</div><div class="detail-value" title="' + escapeHtml(s.session_key) + '">' + escapeHtml(parsed.session_id || s.session_key) + '</div></div>' +
1613
+ '<div class="detail-item"><div class="detail-label">Title</div><div class="detail-value">' + (sessionTitle ? escapeHtml(sessionTitle) : '–') + '</div></div>' +
1608
1614
  '<div class="detail-item"><div class="detail-label">Device</div><div class="detail-value" title="' + escapeHtml(parsed.device_id || '') + '">' + (parsed.device_id ? escapeHtml(parsed.device_id) : '–') + '</div></div>' +
1609
1615
  '<div class="detail-item"><div class="detail-label">Account</div><div class="detail-value" title="' + escapeHtml(parsed.account_uuid || '') + '">' + (parsed.account_uuid ? escapeHtml(parsed.account_uuid) : '–') + '</div></div>' +
1610
1616
  '</div>' +
@@ -222,7 +222,7 @@ class TestTryGateTier:
222
222
  headers = {}
223
223
  caps = RequestCapabilities()
224
224
  req = build_canonical_request(body, headers)
225
- session_record = await exec_inst._session_mgr.get_or_create_record(
225
+ session_record, _is_new = await exec_inst._session_mgr.get_or_create_record(
226
226
  req.session_key, req.trace_id
227
227
  )
228
228
  reasons: list[str] = []
@@ -246,7 +246,7 @@ class TestTryGateTier:
246
246
  body = {"model": "test"}
247
247
  headers = {}
248
248
  req = build_canonical_request(body, headers)
249
- session_record = await exec_inst._session_mgr.get_or_create_record(
249
+ session_record, _is_new = await exec_inst._session_mgr.get_or_create_record(
250
250
  req.session_key, req.trace_id
251
251
  )
252
252
  reasons: list[str] = []
@@ -275,7 +275,7 @@ class TestTryGateTier:
275
275
  body = {"model": "test", "thinking": {"type": "enabled"}}
276
276
  headers = {}
277
277
  req = build_canonical_request(body, headers)
278
- session_record = await exec_inst._session_mgr.get_or_create_record(
278
+ session_record, _is_new = await exec_inst._session_mgr.get_or_create_record(
279
279
  req.session_key, req.trace_id
280
280
  )
281
281
  reasons: list[str] = []
@@ -651,9 +651,10 @@ class TestRouteSessionManagerIntegration:
651
651
  @pytest.mark.asyncio
652
652
  async def test_get_or_create_without_store(self):
653
653
  mgr = RouteSessionManager(compat_session_store=None)
654
- record = await mgr.get_or_create_record("sk_test", "trace_1")
655
- # 无 store 时返回 None(由 executor 层面处理空 record 场景)
654
+ record, is_new = await mgr.get_or_create_record("sk_test", "trace_1")
655
+ # 无 store 时返回 (None, False)
656
656
  assert record is None
657
+ assert is_new is False
657
658
 
658
659
  @pytest.mark.asyncio
659
660
  async def test_persist_session_without_store_is_noop(self):
@@ -74,7 +74,7 @@ wheels = [
74
74
 
75
75
  [[package]]
76
76
  name = "coding-proxy"
77
- version = "0.4.1a6"
77
+ version = "0.4.1a7"
78
78
  source = { editable = "." }
79
79
  dependencies = [
80
80
  { name = "aiosqlite" },
File without changes
File without changes
File without changes
File without changes