xai-review 0.21.0__tar.gz → 0.23.0__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.

Potentially problematic release.


This version of xai-review might be problematic. Click here for more details.

Files changed (242) hide show
  1. {xai_review-0.21.0/xai_review.egg-info → xai_review-0.23.0}/PKG-INFO +2 -2
  2. {xai_review-0.21.0 → xai_review-0.23.0}/README.md +1 -1
  3. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/claude/client.py +1 -1
  4. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/gemini/client.py +1 -1
  5. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/github/client.py +1 -1
  6. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/github/pr/client.py +64 -16
  7. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/github/pr/schema/comments.py +4 -0
  8. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/github/pr/schema/files.py +4 -0
  9. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/github/pr/schema/reviews.py +4 -0
  10. xai_review-0.23.0/ai_review/clients/github/pr/types.py +49 -0
  11. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/gitlab/client.py +1 -1
  12. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/gitlab/mr/client.py +25 -8
  13. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/gitlab/mr/schema/discussions.py +4 -0
  14. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/gitlab/mr/schema/notes.py +4 -0
  15. xai_review-0.23.0/ai_review/clients/gitlab/mr/types.py +35 -0
  16. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/openai/client.py +1 -1
  17. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/config.py +2 -0
  18. xai_review-0.23.0/ai_review/libs/asynchronous/gather.py +17 -0
  19. xai_review-0.23.0/ai_review/libs/config/core.py +5 -0
  20. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/http/event_hooks/logger.py +5 -2
  21. xai_review-0.23.0/ai_review/libs/http/transports/retry.py +51 -0
  22. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/diff/renderers.py +3 -3
  23. xai_review-0.23.0/ai_review/services/prompt/schema.py +41 -0
  24. xai_review-0.23.0/ai_review/tests/fixtures/clients/claude.py +22 -0
  25. xai_review-0.21.0/ai_review/tests/suites/clients/gemini/test_client.py → xai_review-0.23.0/ai_review/tests/fixtures/clients/gemini.py +2 -11
  26. xai_review-0.23.0/ai_review/tests/fixtures/clients/github.py +181 -0
  27. xai_review-0.23.0/ai_review/tests/fixtures/clients/gitlab.py +150 -0
  28. xai_review-0.21.0/ai_review/tests/suites/clients/openai/test_client.py → xai_review-0.23.0/ai_review/tests/fixtures/clients/openai.py +2 -11
  29. xai_review-0.23.0/ai_review/tests/suites/clients/claude/test_client.py +12 -0
  30. xai_review-0.23.0/ai_review/tests/suites/clients/gemini/test_client.py +12 -0
  31. xai_review-0.23.0/ai_review/tests/suites/clients/github/test_client.py +14 -0
  32. xai_review-0.23.0/ai_review/tests/suites/clients/gitlab/test_client.py +14 -0
  33. xai_review-0.23.0/ai_review/tests/suites/clients/openai/test_client.py +12 -0
  34. xai_review-0.23.0/ai_review/tests/suites/libs/asynchronous/test_gather.py +46 -0
  35. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/services/diff/test_service.py +1 -1
  36. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/services/diff/test_tools.py +1 -1
  37. xai_review-0.23.0/ai_review/tests/suites/services/llm/test_factory.py +30 -0
  38. xai_review-0.23.0/ai_review/tests/suites/services/prompt/test_schema.py +136 -0
  39. xai_review-0.23.0/ai_review/tests/suites/services/review/__init__.py +0 -0
  40. xai_review-0.23.0/ai_review/tests/suites/services/review/inline/__init__.py +0 -0
  41. xai_review-0.23.0/ai_review/tests/suites/services/review/policy/__init__.py +0 -0
  42. xai_review-0.23.0/ai_review/tests/suites/services/review/summary/__init__.py +0 -0
  43. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/services/review/test_service.py +9 -9
  44. xai_review-0.23.0/ai_review/tests/suites/services/vcs/__init__.py +0 -0
  45. xai_review-0.23.0/ai_review/tests/suites/services/vcs/github/__init__.py +0 -0
  46. xai_review-0.23.0/ai_review/tests/suites/services/vcs/github/test_service.py +114 -0
  47. xai_review-0.23.0/ai_review/tests/suites/services/vcs/gitlab/__init__.py +0 -0
  48. xai_review-0.23.0/ai_review/tests/suites/services/vcs/gitlab/test_service.py +123 -0
  49. xai_review-0.23.0/ai_review/tests/suites/services/vcs/test_factory.py +23 -0
  50. {xai_review-0.21.0 → xai_review-0.23.0}/pyproject.toml +1 -1
  51. {xai_review-0.21.0 → xai_review-0.23.0/xai_review.egg-info}/PKG-INFO +2 -2
  52. {xai_review-0.21.0 → xai_review-0.23.0}/xai_review.egg-info/SOURCES.txt +30 -10
  53. xai_review-0.21.0/ai_review/libs/asynchronous/gather.py +0 -14
  54. xai_review-0.21.0/ai_review/libs/http/transports/retry.py +0 -34
  55. xai_review-0.21.0/ai_review/services/prompt/schema.py +0 -52
  56. xai_review-0.21.0/ai_review/tests/suites/clients/claude/test_client.py +0 -31
  57. xai_review-0.21.0/ai_review/tests/suites/clients/github/test_client.py +0 -36
  58. xai_review-0.21.0/ai_review/tests/suites/clients/gitlab/test_client.py +0 -35
  59. xai_review-0.21.0/ai_review/tests/suites/services/prompt/test_schema.py +0 -71
  60. {xai_review-0.21.0 → xai_review-0.23.0}/LICENSE +0 -0
  61. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/__init__.py +0 -0
  62. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/cli/__init__.py +0 -0
  63. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/cli/commands/__init__.py +0 -0
  64. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/cli/commands/run_context_review.py +0 -0
  65. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/cli/commands/run_inline_review.py +0 -0
  66. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/cli/commands/run_review.py +0 -0
  67. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/cli/commands/run_summary_review.py +0 -0
  68. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/cli/main.py +0 -0
  69. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/__init__.py +0 -0
  70. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/claude/__init__.py +0 -0
  71. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/claude/schema.py +0 -0
  72. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/gemini/__init__.py +0 -0
  73. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/gemini/schema.py +0 -0
  74. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/github/__init__.py +0 -0
  75. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/github/pr/__init__.py +0 -0
  76. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/github/pr/schema/__init__.py +0 -0
  77. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/github/pr/schema/pull_request.py +0 -0
  78. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/gitlab/__init__.py +0 -0
  79. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/gitlab/mr/__init__.py +0 -0
  80. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/gitlab/mr/schema/__init__.py +0 -0
  81. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/gitlab/mr/schema/changes.py +0 -0
  82. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/openai/__init__.py +0 -0
  83. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/clients/openai/schema.py +0 -0
  84. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/__init__.py +0 -0
  85. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/asynchronous/__init__.py +0 -0
  86. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/config/__init__.py +0 -0
  87. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/config/artifacts.py +0 -0
  88. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/config/base.py +0 -0
  89. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/config/claude.py +0 -0
  90. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/config/gemini.py +0 -0
  91. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/config/github.py +0 -0
  92. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/config/gitlab.py +0 -0
  93. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/config/http.py +0 -0
  94. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/config/llm.py +0 -0
  95. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/config/logger.py +0 -0
  96. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/config/openai.py +0 -0
  97. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/config/prompt.py +0 -0
  98. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/config/review.py +0 -0
  99. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/config/vcs.py +0 -0
  100. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/constants/__init__.py +0 -0
  101. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/constants/llm_provider.py +0 -0
  102. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/constants/vcs_provider.py +0 -0
  103. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/diff/__init__.py +0 -0
  104. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/diff/models.py +0 -0
  105. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/diff/parser.py +0 -0
  106. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/diff/tools.py +0 -0
  107. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/http/__init__.py +0 -0
  108. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/http/client.py +0 -0
  109. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/http/event_hooks/__init__.py +0 -0
  110. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/http/event_hooks/base.py +0 -0
  111. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/http/handlers.py +0 -0
  112. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/http/transports/__init__.py +0 -0
  113. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/json.py +0 -0
  114. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/logger.py +0 -0
  115. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/resources.py +0 -0
  116. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/template/__init__.py +0 -0
  117. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/libs/template/render.py +0 -0
  118. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/prompts/__init__.py +0 -0
  119. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/prompts/default_context.md +0 -0
  120. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/prompts/default_inline.md +0 -0
  121. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/prompts/default_summary.md +0 -0
  122. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/prompts/default_system_context.md +0 -0
  123. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/prompts/default_system_inline.md +0 -0
  124. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/prompts/default_system_summary.md +0 -0
  125. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/resources/__init__.py +0 -0
  126. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/resources/pricing.yaml +0 -0
  127. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/__init__.py +0 -0
  128. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/artifacts/__init__.py +0 -0
  129. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/artifacts/schema.py +0 -0
  130. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/artifacts/service.py +0 -0
  131. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/artifacts/tools.py +0 -0
  132. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/artifacts/types.py +0 -0
  133. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/cost/__init__.py +0 -0
  134. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/cost/schema.py +0 -0
  135. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/cost/service.py +0 -0
  136. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/cost/types.py +0 -0
  137. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/diff/__init__.py +0 -0
  138. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/diff/schema.py +0 -0
  139. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/diff/service.py +0 -0
  140. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/diff/tools.py +0 -0
  141. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/diff/types.py +0 -0
  142. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/git/__init__.py +0 -0
  143. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/git/service.py +0 -0
  144. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/git/types.py +0 -0
  145. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/hook/__init__.py +0 -0
  146. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/hook/constants.py +0 -0
  147. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/hook/service.py +0 -0
  148. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/hook/types.py +0 -0
  149. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/llm/__init__.py +0 -0
  150. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/llm/claude/__init__.py +0 -0
  151. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/llm/claude/client.py +0 -0
  152. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/llm/factory.py +0 -0
  153. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/llm/gemini/__init__.py +0 -0
  154. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/llm/gemini/client.py +0 -0
  155. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/llm/openai/__init__.py +0 -0
  156. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/llm/openai/client.py +0 -0
  157. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/llm/types.py +0 -0
  158. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/prompt/__init__.py +0 -0
  159. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/prompt/adapter.py +0 -0
  160. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/prompt/service.py +0 -0
  161. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/prompt/tools.py +0 -0
  162. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/prompt/types.py +0 -0
  163. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/review/__init__.py +0 -0
  164. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/review/gateway/__init__.py +0 -0
  165. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/review/gateway/comment.py +0 -0
  166. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/review/gateway/llm.py +0 -0
  167. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/review/inline/__init__.py +0 -0
  168. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/review/inline/schema.py +0 -0
  169. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/review/inline/service.py +0 -0
  170. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/review/inline/types.py +0 -0
  171. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/review/policy/__init__.py +0 -0
  172. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/review/policy/service.py +0 -0
  173. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/review/service.py +0 -0
  174. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/review/summary/__init__.py +0 -0
  175. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/review/summary/schema.py +0 -0
  176. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/review/summary/service.py +0 -0
  177. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/review/summary/types.py +0 -0
  178. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/vcs/__init__.py +0 -0
  179. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/vcs/factory.py +0 -0
  180. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/vcs/github/__init__.py +0 -0
  181. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/vcs/github/client.py +0 -0
  182. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/vcs/gitlab/__init__.py +0 -0
  183. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/vcs/gitlab/client.py +0 -0
  184. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/services/vcs/types.py +0 -0
  185. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/__init__.py +0 -0
  186. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/fixtures/__init__.py +0 -0
  187. {xai_review-0.21.0/ai_review/tests/fixtures/review → xai_review-0.23.0/ai_review/tests/fixtures/clients}/__init__.py +0 -0
  188. {xai_review-0.21.0/ai_review/tests/suites → xai_review-0.23.0/ai_review/tests/fixtures/services}/__init__.py +0 -0
  189. {xai_review-0.21.0/ai_review/tests/fixtures → xai_review-0.23.0/ai_review/tests/fixtures/services}/artifacts.py +0 -0
  190. {xai_review-0.21.0/ai_review/tests/fixtures → xai_review-0.23.0/ai_review/tests/fixtures/services}/cost.py +0 -0
  191. {xai_review-0.21.0/ai_review/tests/fixtures → xai_review-0.23.0/ai_review/tests/fixtures/services}/diff.py +0 -0
  192. {xai_review-0.21.0/ai_review/tests/fixtures → xai_review-0.23.0/ai_review/tests/fixtures/services}/git.py +0 -0
  193. {xai_review-0.21.0/ai_review/tests/fixtures → xai_review-0.23.0/ai_review/tests/fixtures/services}/llm.py +0 -0
  194. {xai_review-0.21.0/ai_review/tests/fixtures → xai_review-0.23.0/ai_review/tests/fixtures/services}/prompt.py +0 -0
  195. {xai_review-0.21.0/ai_review/tests/suites/clients → xai_review-0.23.0/ai_review/tests/fixtures/services/review}/__init__.py +0 -0
  196. {xai_review-0.21.0/ai_review/tests/fixtures → xai_review-0.23.0/ai_review/tests/fixtures/services}/review/inline.py +0 -0
  197. {xai_review-0.21.0/ai_review/tests/fixtures → xai_review-0.23.0/ai_review/tests/fixtures/services}/review/summary.py +0 -0
  198. {xai_review-0.21.0/ai_review/tests/fixtures → xai_review-0.23.0/ai_review/tests/fixtures/services}/vcs.py +0 -0
  199. {xai_review-0.21.0/ai_review/tests/suites/clients/claude → xai_review-0.23.0/ai_review/tests/suites}/__init__.py +0 -0
  200. {xai_review-0.21.0/ai_review/tests/suites/clients/gemini → xai_review-0.23.0/ai_review/tests/suites/clients}/__init__.py +0 -0
  201. {xai_review-0.21.0/ai_review/tests/suites/clients/github → xai_review-0.23.0/ai_review/tests/suites/clients/claude}/__init__.py +0 -0
  202. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/clients/claude/test_schema.py +0 -0
  203. {xai_review-0.21.0/ai_review/tests/suites/clients/gitlab → xai_review-0.23.0/ai_review/tests/suites/clients/gemini}/__init__.py +0 -0
  204. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/clients/gemini/test_schema.py +0 -0
  205. {xai_review-0.21.0/ai_review/tests/suites/clients/openai → xai_review-0.23.0/ai_review/tests/suites/clients/github}/__init__.py +0 -0
  206. {xai_review-0.21.0/ai_review/tests/suites/libs → xai_review-0.23.0/ai_review/tests/suites/clients/gitlab}/__init__.py +0 -0
  207. {xai_review-0.21.0/ai_review/tests/suites/libs/config → xai_review-0.23.0/ai_review/tests/suites/clients/openai}/__init__.py +0 -0
  208. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/clients/openai/test_schema.py +0 -0
  209. {xai_review-0.21.0/ai_review/tests/suites/libs/diff → xai_review-0.23.0/ai_review/tests/suites/libs}/__init__.py +0 -0
  210. {xai_review-0.21.0/ai_review/tests/suites/libs/template → xai_review-0.23.0/ai_review/tests/suites/libs/asynchronous}/__init__.py +0 -0
  211. {xai_review-0.21.0/ai_review/tests/suites/services → xai_review-0.23.0/ai_review/tests/suites/libs/config}/__init__.py +0 -0
  212. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/libs/config/test_prompt.py +0 -0
  213. {xai_review-0.21.0/ai_review/tests/suites/services/cost → xai_review-0.23.0/ai_review/tests/suites/libs/diff}/__init__.py +0 -0
  214. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/libs/diff/test_models.py +0 -0
  215. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/libs/diff/test_parser.py +0 -0
  216. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/libs/diff/test_tools.py +0 -0
  217. {xai_review-0.21.0/ai_review/tests/suites/services/diff → xai_review-0.23.0/ai_review/tests/suites/libs/template}/__init__.py +0 -0
  218. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/libs/template/test_render.py +0 -0
  219. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/libs/test_json.py +0 -0
  220. {xai_review-0.21.0/ai_review/tests/suites/services/hook → xai_review-0.23.0/ai_review/tests/suites/services}/__init__.py +0 -0
  221. {xai_review-0.21.0/ai_review/tests/suites/services/prompt → xai_review-0.23.0/ai_review/tests/suites/services/cost}/__init__.py +0 -0
  222. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/services/cost/test_schema.py +0 -0
  223. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/services/cost/test_service.py +0 -0
  224. {xai_review-0.21.0/ai_review/tests/suites/services/review → xai_review-0.23.0/ai_review/tests/suites/services/diff}/__init__.py +0 -0
  225. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/services/diff/test_renderers.py +0 -0
  226. {xai_review-0.21.0/ai_review/tests/suites/services/review/inline → xai_review-0.23.0/ai_review/tests/suites/services/hook}/__init__.py +0 -0
  227. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/services/hook/test_service.py +0 -0
  228. {xai_review-0.21.0/ai_review/tests/suites/services/review/policy → xai_review-0.23.0/ai_review/tests/suites/services/llm}/__init__.py +0 -0
  229. {xai_review-0.21.0/ai_review/tests/suites/services/review/summary → xai_review-0.23.0/ai_review/tests/suites/services/prompt}/__init__.py +0 -0
  230. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/services/prompt/test_adapter.py +0 -0
  231. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/services/prompt/test_service.py +0 -0
  232. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/services/prompt/test_tools.py +0 -0
  233. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/services/review/inline/test_schema.py +0 -0
  234. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/services/review/inline/test_service.py +0 -0
  235. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/services/review/policy/test_service.py +0 -0
  236. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/services/review/summary/test_schema.py +0 -0
  237. {xai_review-0.21.0 → xai_review-0.23.0}/ai_review/tests/suites/services/review/summary/test_service.py +0 -0
  238. {xai_review-0.21.0 → xai_review-0.23.0}/setup.cfg +0 -0
  239. {xai_review-0.21.0 → xai_review-0.23.0}/xai_review.egg-info/dependency_links.txt +0 -0
  240. {xai_review-0.21.0 → xai_review-0.23.0}/xai_review.egg-info/entry_points.txt +0 -0
  241. {xai_review-0.21.0 → xai_review-0.23.0}/xai_review.egg-info/requires.txt +0 -0
  242. {xai_review-0.21.0 → xai_review-0.23.0}/xai_review.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xai-review
3
- Version: 0.21.0
3
+ Version: 0.23.0
4
4
  Summary: AI-powered code review tool
5
5
  Author-email: Nikita Filonov <nikita.filonov@example.com>
6
6
  Maintainer-email: Nikita Filonov <nikita.filonov@example.com>
@@ -209,7 +209,7 @@ jobs:
209
209
  runs-on: ubuntu-latest
210
210
  steps:
211
211
  - uses: actions/checkout@v4
212
- - uses: Nikita-Filonov/ai-review@v0.21.0
212
+ - uses: Nikita-Filonov/ai-review@v0.23.0
213
213
  with:
214
214
  review-command: ${{ inputs.review-command }}
215
215
  env:
@@ -175,7 +175,7 @@ jobs:
175
175
  runs-on: ubuntu-latest
176
176
  steps:
177
177
  - uses: actions/checkout@v4
178
- - uses: Nikita-Filonov/ai-review@v0.21.0
178
+ - uses: Nikita-Filonov/ai-review@v0.23.0
179
179
  with:
180
180
  review-command: ${{ inputs.review-command }}
181
181
  env:
@@ -26,7 +26,7 @@ class ClaudeHTTPClient(HTTPClient):
26
26
  def get_claude_http_client() -> ClaudeHTTPClient:
27
27
  logger = get_logger("CLAUDE_HTTP_CLIENT")
28
28
  logger_event_hook = LoggerEventHook(logger=logger)
29
- retry_transport = RetryTransport(transport=AsyncHTTPTransport())
29
+ retry_transport = RetryTransport(logger=logger, transport=AsyncHTTPTransport())
30
30
 
31
31
  client = AsyncClient(
32
32
  timeout=settings.llm.http_client.timeout,
@@ -29,7 +29,7 @@ class GeminiHTTPClient(HTTPClient):
29
29
  def get_gemini_http_client() -> GeminiHTTPClient:
30
30
  logger = get_logger("GEMINI_HTTP_CLIENT")
31
31
  logger_event_hook = LoggerEventHook(logger=logger)
32
- retry_transport = RetryTransport(transport=AsyncHTTPTransport())
32
+ retry_transport = RetryTransport(logger=logger, transport=AsyncHTTPTransport())
33
33
 
34
34
  client = AsyncClient(
35
35
  timeout=settings.llm.http_client.timeout,
@@ -15,7 +15,7 @@ class GitHubHTTPClient:
15
15
  def get_github_http_client() -> GitHubHTTPClient:
16
16
  logger = get_logger("GITHUB_HTTP_CLIENT")
17
17
  logger_event_hook = LoggerEventHook(logger=logger)
18
- retry_transport = RetryTransport(transport=AsyncHTTPTransport())
18
+ retry_transport = RetryTransport(logger=logger, transport=AsyncHTTPTransport())
19
19
 
20
20
  client = AsyncClient(
21
21
  timeout=settings.llm.http_client.timeout,
@@ -1,15 +1,23 @@
1
- from httpx import Response
1
+ from httpx import Response, QueryParams
2
2
 
3
3
  from ai_review.clients.github.pr.schema.comments import (
4
+ GitHubGetPRCommentsQuerySchema,
4
5
  GitHubGetPRCommentsResponseSchema,
5
6
  GitHubCreateIssueCommentRequestSchema,
6
7
  GitHubCreateIssueCommentResponseSchema,
7
8
  GitHubCreateReviewCommentRequestSchema,
8
9
  GitHubCreateReviewCommentResponseSchema
9
10
  )
10
- from ai_review.clients.github.pr.schema.files import GitHubGetPRFilesResponseSchema
11
+ from ai_review.clients.github.pr.schema.files import (
12
+ GitHubGetPRFilesQuerySchema,
13
+ GitHubGetPRFilesResponseSchema
14
+ )
11
15
  from ai_review.clients.github.pr.schema.pull_request import GitHubGetPRResponseSchema
12
- from ai_review.clients.github.pr.schema.reviews import GitHubGetPRReviewsResponseSchema
16
+ from ai_review.clients.github.pr.schema.reviews import (
17
+ GitHubGetPRReviewsQuerySchema,
18
+ GitHubGetPRReviewsResponseSchema
19
+ )
20
+ from ai_review.clients.github.pr.types import GitHubPullRequestsHTTPClientProtocol
13
21
  from ai_review.libs.http.client import HTTPClient
14
22
  from ai_review.libs.http.handlers import HTTPClientError, handle_http_error
15
23
 
@@ -18,22 +26,49 @@ class GitHubPullRequestsHTTPClientError(HTTPClientError):
18
26
  pass
19
27
 
20
28
 
21
- class GitHubPullRequestsHTTPClient(HTTPClient):
29
+ class GitHubPullRequestsHTTPClient(HTTPClient, GitHubPullRequestsHTTPClientProtocol):
22
30
  @handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
23
31
  async def get_pull_request_api(self, owner: str, repo: str, pull_number: str) -> Response:
24
32
  return await self.get(f"/repos/{owner}/{repo}/pulls/{pull_number}")
25
33
 
26
34
  @handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
27
- async def get_files_api(self, owner: str, repo: str, pull_number: str) -> Response:
28
- return await self.get(f"/repos/{owner}/{repo}/pulls/{pull_number}/files")
35
+ async def get_files_api(
36
+ self,
37
+ owner: str,
38
+ repo: str,
39
+ pull_number: str,
40
+ query: GitHubGetPRFilesQuerySchema
41
+ ) -> Response:
42
+ return await self.get(
43
+ f"/repos/{owner}/{repo}/pulls/{pull_number}/files",
44
+ query=QueryParams(**query.model_dump())
45
+ )
29
46
 
30
47
  @handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
31
- async def get_issue_comments_api(self, owner: str, repo: str, issue_number: str) -> Response:
32
- return await self.get(f"/repos/{owner}/{repo}/issues/{issue_number}/comments")
48
+ async def get_issue_comments_api(
49
+ self,
50
+ owner: str,
51
+ repo: str,
52
+ issue_number: str,
53
+ query: GitHubGetPRCommentsQuerySchema,
54
+ ) -> Response:
55
+ return await self.get(
56
+ f"/repos/{owner}/{repo}/issues/{issue_number}/comments",
57
+ query=QueryParams(**query.model_dump())
58
+ )
33
59
 
34
60
  @handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
35
- async def get_review_comments_api(self, owner: str, repo: str, pull_number: str) -> Response:
36
- return await self.get(f"/repos/{owner}/{repo}/pulls/{pull_number}/comments")
61
+ async def get_review_comments_api(
62
+ self,
63
+ owner: str,
64
+ repo: str,
65
+ pull_number: str,
66
+ query: GitHubGetPRCommentsQuerySchema,
67
+ ) -> Response:
68
+ return await self.get(
69
+ f"/repos/{owner}/{repo}/pulls/{pull_number}/comments",
70
+ query=QueryParams(**query.model_dump())
71
+ )
37
72
 
38
73
  @handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
39
74
  async def create_review_comment_api(
@@ -62,27 +97,40 @@ class GitHubPullRequestsHTTPClient(HTTPClient):
62
97
  )
63
98
 
64
99
  @handle_http_error(client="GitHubPullRequestsHTTPClient", exception=GitHubPullRequestsHTTPClientError)
65
- async def get_reviews_api(self, owner: str, repo: str, pull_number: str) -> Response:
66
- return await self.get(f"/repos/{owner}/{repo}/pulls/{pull_number}/reviews")
100
+ async def get_reviews_api(
101
+ self,
102
+ owner: str,
103
+ repo: str,
104
+ pull_number: str,
105
+ query: GitHubGetPRReviewsQuerySchema
106
+ ) -> Response:
107
+ return await self.get(
108
+ f"/repos/{owner}/{repo}/pulls/{pull_number}/reviews",
109
+ query=QueryParams(**query.model_dump())
110
+ )
67
111
 
68
112
  async def get_pull_request(self, owner: str, repo: str, pull_number: str) -> GitHubGetPRResponseSchema:
69
113
  response = await self.get_pull_request_api(owner, repo, pull_number)
70
114
  return GitHubGetPRResponseSchema.model_validate_json(response.text)
71
115
 
72
116
  async def get_files(self, owner: str, repo: str, pull_number: str) -> GitHubGetPRFilesResponseSchema:
73
- response = await self.get_files_api(owner, repo, pull_number)
117
+ query = GitHubGetPRFilesQuerySchema(per_page=100)
118
+ response = await self.get_files_api(owner, repo, pull_number, query)
74
119
  return GitHubGetPRFilesResponseSchema.model_validate_json(response.text)
75
120
 
76
121
  async def get_issue_comments(self, owner: str, repo: str, issue_number: str) -> GitHubGetPRCommentsResponseSchema:
77
- response = await self.get_issue_comments_api(owner, repo, issue_number)
122
+ query = GitHubGetPRCommentsQuerySchema(per_page=100)
123
+ response = await self.get_issue_comments_api(owner, repo, issue_number, query)
78
124
  return GitHubGetPRCommentsResponseSchema.model_validate_json(response.text)
79
125
 
80
126
  async def get_review_comments(self, owner: str, repo: str, pull_number: str) -> GitHubGetPRCommentsResponseSchema:
81
- response = await self.get_review_comments_api(owner, repo, pull_number)
127
+ query = GitHubGetPRCommentsQuerySchema(per_page=100)
128
+ response = await self.get_review_comments_api(owner, repo, pull_number, query)
82
129
  return GitHubGetPRCommentsResponseSchema.model_validate_json(response.text)
83
130
 
84
131
  async def get_reviews(self, owner: str, repo: str, pull_number: str) -> GitHubGetPRReviewsResponseSchema:
85
- response = await self.get_reviews_api(owner, repo, pull_number)
132
+ query = GitHubGetPRReviewsQuerySchema(per_page=100)
133
+ response = await self.get_reviews_api(owner, repo, pull_number, query)
86
134
  return GitHubGetPRReviewsResponseSchema.model_validate_json(response.text)
87
135
 
88
136
  async def create_review_comment(
@@ -8,6 +8,10 @@ class GitHubPRCommentSchema(BaseModel):
8
8
  line: int | None = None
9
9
 
10
10
 
11
+ class GitHubGetPRCommentsQuerySchema(BaseModel):
12
+ per_page: int
13
+
14
+
11
15
  class GitHubGetPRCommentsResponseSchema(RootModel[list[GitHubPRCommentSchema]]):
12
16
  root: list[GitHubPRCommentSchema]
13
17
 
@@ -8,5 +8,9 @@ class GitHubPRFileSchema(BaseModel):
8
8
  filename: str
9
9
 
10
10
 
11
+ class GitHubGetPRFilesQuerySchema(BaseModel):
12
+ per_page: int
13
+
14
+
11
15
  class GitHubGetPRFilesResponseSchema(RootModel[list[GitHubPRFileSchema]]):
12
16
  root: list[GitHubPRFileSchema]
@@ -9,5 +9,9 @@ class GitHubPRReviewSchema(BaseModel):
9
9
  state: str
10
10
 
11
11
 
12
+ class GitHubGetPRReviewsQuerySchema(BaseModel):
13
+ per_page: int
14
+
15
+
12
16
  class GitHubGetPRReviewsResponseSchema(RootModel[list[GitHubPRReviewSchema]]):
13
17
  root: list[GitHubPRReviewSchema]
@@ -0,0 +1,49 @@
1
+ from typing import Protocol
2
+
3
+ from ai_review.clients.github.pr.schema.comments import (
4
+ GitHubGetPRCommentsResponseSchema,
5
+ GitHubCreateIssueCommentResponseSchema,
6
+ GitHubCreateReviewCommentResponseSchema,
7
+ GitHubCreateReviewCommentRequestSchema,
8
+ )
9
+ from ai_review.clients.github.pr.schema.files import GitHubGetPRFilesResponseSchema
10
+ from ai_review.clients.github.pr.schema.pull_request import GitHubGetPRResponseSchema
11
+ from ai_review.clients.github.pr.schema.reviews import GitHubGetPRReviewsResponseSchema
12
+
13
+
14
+ class GitHubPullRequestsHTTPClientProtocol(Protocol):
15
+ async def get_pull_request(self, owner: str, repo: str, pull_number: str) -> GitHubGetPRResponseSchema: ...
16
+
17
+ async def get_files(self, owner: str, repo: str, pull_number: str) -> GitHubGetPRFilesResponseSchema: ...
18
+
19
+ async def get_issue_comments(
20
+ self,
21
+ owner: str,
22
+ repo: str,
23
+ issue_number: str
24
+ ) -> GitHubGetPRCommentsResponseSchema: ...
25
+
26
+ async def get_review_comments(
27
+ self,
28
+ owner: str,
29
+ repo: str,
30
+ pull_number: str
31
+ ) -> GitHubGetPRCommentsResponseSchema: ...
32
+
33
+ async def get_reviews(self, owner: str, repo: str, pull_number: str) -> GitHubGetPRReviewsResponseSchema: ...
34
+
35
+ async def create_review_comment(
36
+ self,
37
+ owner: str,
38
+ repo: str,
39
+ pull_number: str,
40
+ request: GitHubCreateReviewCommentRequestSchema,
41
+ ) -> GitHubCreateReviewCommentResponseSchema: ...
42
+
43
+ async def create_issue_comment(
44
+ self,
45
+ owner: str,
46
+ repo: str,
47
+ issue_number: str,
48
+ body: str,
49
+ ) -> GitHubCreateIssueCommentResponseSchema: ...
@@ -15,7 +15,7 @@ class GitLabHTTPClient:
15
15
  def get_gitlab_http_client() -> GitLabHTTPClient:
16
16
  logger = get_logger("GITLAB_HTTP_CLIENT")
17
17
  logger_event_hook = LoggerEventHook(logger=logger)
18
- retry_transport = RetryTransport(transport=AsyncHTTPTransport())
18
+ retry_transport = RetryTransport(logger=logger, transport=AsyncHTTPTransport())
19
19
 
20
20
  client = AsyncClient(
21
21
  timeout=settings.llm.http_client.timeout,
@@ -1,16 +1,19 @@
1
- from httpx import Response
1
+ from httpx import Response, QueryParams
2
2
 
3
3
  from ai_review.clients.gitlab.mr.schema.changes import GitLabGetMRChangesResponseSchema
4
4
  from ai_review.clients.gitlab.mr.schema.discussions import (
5
+ GitLabGetMRDiscussionsQuerySchema,
5
6
  GitLabGetMRDiscussionsResponseSchema,
6
7
  GitLabCreateMRDiscussionRequestSchema,
7
8
  GitLabCreateMRDiscussionResponseSchema
8
9
  )
9
10
  from ai_review.clients.gitlab.mr.schema.notes import (
11
+ GitLabGetMRNotesQuerySchema,
10
12
  GitLabGetMRNotesResponseSchema,
11
13
  GitLabCreateMRNoteRequestSchema,
12
14
  GitLabCreateMRNoteResponseSchema,
13
15
  )
16
+ from ai_review.clients.gitlab.mr.types import GitLabMergeRequestsHTTPClientProtocol
14
17
  from ai_review.libs.http.client import HTTPClient
15
18
  from ai_review.libs.http.handlers import handle_http_error, HTTPClientError
16
19
 
@@ -19,7 +22,7 @@ class GitLabMergeRequestsHTTPClientError(HTTPClientError):
19
22
  pass
20
23
 
21
24
 
22
- class GitLabMergeRequestsHTTPClient(HTTPClient):
25
+ class GitLabMergeRequestsHTTPClient(HTTPClient, GitLabMergeRequestsHTTPClientProtocol):
23
26
  @handle_http_error(client="GitLabMergeRequestsHTTPClient", exception=GitLabMergeRequestsHTTPClientError)
24
27
  async def get_changes_api(self, project_id: str, merge_request_id: str) -> Response:
25
28
  return await self.get(
@@ -27,15 +30,27 @@ class GitLabMergeRequestsHTTPClient(HTTPClient):
27
30
  )
28
31
 
29
32
  @handle_http_error(client="GitLabMergeRequestsHTTPClient", exception=GitLabMergeRequestsHTTPClientError)
30
- async def get_notes_api(self, project_id: str, merge_request_id: str) -> Response:
33
+ async def get_notes_api(
34
+ self,
35
+ project_id: str,
36
+ merge_request_id: str,
37
+ query: GitLabGetMRNotesQuerySchema
38
+ ) -> Response:
31
39
  return await self.get(
32
- f"/api/v4/projects/{project_id}/merge_requests/{merge_request_id}/notes"
40
+ f"/api/v4/projects/{project_id}/merge_requests/{merge_request_id}/notes",
41
+ query=QueryParams(**query.model_dump())
33
42
  )
34
43
 
35
44
  @handle_http_error(client="GitLabMergeRequestsHTTPClient", exception=GitLabMergeRequestsHTTPClientError)
36
- async def get_discussions_api(self, project_id: str, merge_request_id: str) -> Response:
45
+ async def get_discussions_api(
46
+ self,
47
+ project_id: str,
48
+ merge_request_id: str,
49
+ query: GitLabGetMRDiscussionsQuerySchema
50
+ ) -> Response:
37
51
  return await self.get(
38
- f"/api/v4/projects/{project_id}/merge_requests/{merge_request_id}/discussions"
52
+ f"/api/v4/projects/{project_id}/merge_requests/{merge_request_id}/discussions",
53
+ query=QueryParams(**query.model_dump())
39
54
  )
40
55
 
41
56
  @handle_http_error(client="GitLabMergeRequestsHTTPClient", exception=GitLabMergeRequestsHTTPClientError)
@@ -71,7 +86,8 @@ class GitLabMergeRequestsHTTPClient(HTTPClient):
71
86
  project_id: str,
72
87
  merge_request_id: str
73
88
  ) -> GitLabGetMRNotesResponseSchema:
74
- response = await self.get_notes_api(project_id, merge_request_id)
89
+ query = GitLabGetMRNotesQuerySchema(per_page=100)
90
+ response = await self.get_notes_api(project_id, merge_request_id, query)
75
91
  return GitLabGetMRNotesResponseSchema.model_validate_json(response.text)
76
92
 
77
93
  async def get_discussions(
@@ -79,7 +95,8 @@ class GitLabMergeRequestsHTTPClient(HTTPClient):
79
95
  project_id: str,
80
96
  merge_request_id: str
81
97
  ) -> GitLabGetMRDiscussionsResponseSchema:
82
- response = await self.get_discussions_api(project_id, merge_request_id)
98
+ query = GitLabGetMRDiscussionsQuerySchema(per_page=100)
99
+ response = await self.get_discussions_api(project_id, merge_request_id, query)
83
100
  return GitLabGetMRDiscussionsResponseSchema.model_validate_json(response.text)
84
101
 
85
102
  async def create_note(
@@ -17,6 +17,10 @@ class GitLabDiscussionPositionSchema(BaseModel):
17
17
  new_line: int
18
18
 
19
19
 
20
+ class GitLabGetMRDiscussionsQuerySchema(BaseModel):
21
+ per_page: int
22
+
23
+
20
24
  class GitLabGetMRDiscussionsResponseSchema(RootModel[list[GitLabDiscussionSchema]]):
21
25
  root: list[GitLabDiscussionSchema]
22
26
 
@@ -6,6 +6,10 @@ class GitLabNoteSchema(BaseModel):
6
6
  body: str
7
7
 
8
8
 
9
+ class GitLabGetMRNotesQuerySchema(BaseModel):
10
+ per_page: int
11
+
12
+
9
13
  class GitLabGetMRNotesResponseSchema(RootModel[list[GitLabNoteSchema]]):
10
14
  root: list[GitLabNoteSchema]
11
15
 
@@ -0,0 +1,35 @@
1
+ from typing import Protocol
2
+
3
+ from ai_review.clients.gitlab.mr.schema.changes import GitLabGetMRChangesResponseSchema
4
+ from ai_review.clients.gitlab.mr.schema.discussions import (
5
+ GitLabGetMRDiscussionsResponseSchema,
6
+ GitLabCreateMRDiscussionResponseSchema,
7
+ GitLabCreateMRDiscussionRequestSchema,
8
+ )
9
+ from ai_review.clients.gitlab.mr.schema.notes import GitLabGetMRNotesResponseSchema, GitLabCreateMRNoteResponseSchema
10
+
11
+
12
+ class GitLabMergeRequestsHTTPClientProtocol(Protocol):
13
+ async def get_changes(self, project_id: str, merge_request_id: str) -> GitLabGetMRChangesResponseSchema: ...
14
+
15
+ async def get_notes(self, project_id: str, merge_request_id: str) -> GitLabGetMRNotesResponseSchema: ...
16
+
17
+ async def get_discussions(
18
+ self,
19
+ project_id: str,
20
+ merge_request_id: str
21
+ ) -> GitLabGetMRDiscussionsResponseSchema: ...
22
+
23
+ async def create_note(
24
+ self,
25
+ body: str,
26
+ project_id: str,
27
+ merge_request_id: str,
28
+ ) -> GitLabCreateMRNoteResponseSchema: ...
29
+
30
+ async def create_discussion(
31
+ self,
32
+ project_id: str,
33
+ merge_request_id: str,
34
+ request: GitLabCreateMRDiscussionRequestSchema,
35
+ ) -> GitLabCreateMRDiscussionResponseSchema: ...
@@ -26,7 +26,7 @@ class OpenAIHTTPClient(HTTPClient):
26
26
  def get_openai_http_client() -> OpenAIHTTPClient:
27
27
  logger = get_logger("OPENAI_HTTP_CLIENT")
28
28
  logger_event_hook = LoggerEventHook(logger=logger)
29
- retry_transport = RetryTransport(transport=AsyncHTTPTransport())
29
+ retry_transport = RetryTransport(logger=logger, transport=AsyncHTTPTransport())
30
30
 
31
31
  client = AsyncClient(
32
32
  timeout=settings.llm.http_client.timeout,
@@ -12,6 +12,7 @@ from ai_review.libs.config.base import (
12
12
  get_yaml_config_file_or_default,
13
13
  get_json_config_file_or_default
14
14
  )
15
+ from ai_review.libs.config.core import CoreConfig
15
16
  from ai_review.libs.config.llm import LLMConfig
16
17
  from ai_review.libs.config.logger import LoggerConfig
17
18
  from ai_review.libs.config.prompt import PromptConfig
@@ -36,6 +37,7 @@ class Settings(BaseSettings):
36
37
 
37
38
  llm: LLMConfig
38
39
  vcs: VCSConfig
40
+ core: CoreConfig = CoreConfig()
39
41
  prompt: PromptConfig = PromptConfig()
40
42
  review: ReviewConfig = ReviewConfig()
41
43
  logger: LoggerConfig = LoggerConfig()
@@ -0,0 +1,17 @@
1
+ import asyncio
2
+ from typing import Awaitable, Iterable, TypeVar
3
+
4
+ from ai_review.config import settings
5
+
6
+ T = TypeVar("T")
7
+
8
+
9
+ async def bounded_gather(coroutines: Iterable[Awaitable[T]]) -> tuple[T, ...]:
10
+ sem = asyncio.Semaphore(settings.core.concurrency)
11
+
12
+ async def wrap(coro: Awaitable[T]) -> T:
13
+ async with sem:
14
+ return await coro
15
+
16
+ results = await asyncio.gather(*(wrap(coroutine) for coroutine in coroutines), return_exceptions=True)
17
+ return tuple(results)
@@ -0,0 +1,5 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class CoreConfig(BaseModel):
5
+ concurrency: int = 7
@@ -1,12 +1,15 @@
1
- from logging import Logger
1
+ from typing import TYPE_CHECKING
2
2
 
3
3
  from httpx import Request, Response
4
4
 
5
5
  from ai_review.libs.http.event_hooks.base import BaseEventHook
6
6
 
7
+ if TYPE_CHECKING:
8
+ from loguru import Logger
9
+
7
10
 
8
11
  class LoggerEventHook(BaseEventHook):
9
- def __init__(self, logger: Logger):
12
+ def __init__(self, logger: "Logger"):
10
13
  self.logger = logger
11
14
 
12
15
  async def request(self, request: Request):
@@ -0,0 +1,51 @@
1
+ import asyncio
2
+ from http import HTTPStatus
3
+ from typing import TYPE_CHECKING
4
+
5
+ from httpx import Request, Response, AsyncBaseTransport
6
+
7
+ if TYPE_CHECKING:
8
+ from loguru import Logger
9
+
10
+
11
+ class RetryTransport(AsyncBaseTransport):
12
+ def __init__(
13
+ self,
14
+ logger: "Logger",
15
+ transport: AsyncBaseTransport,
16
+ max_retries: int = 5,
17
+ retry_delay: float = 0.5,
18
+ retry_status_codes: tuple[HTTPStatus, ...] = (
19
+ HTTPStatus.BAD_GATEWAY,
20
+ HTTPStatus.GATEWAY_TIMEOUT,
21
+ HTTPStatus.SERVICE_UNAVAILABLE,
22
+ HTTPStatus.INTERNAL_SERVER_ERROR,
23
+ )
24
+ ):
25
+ self.logger = logger
26
+ self.transport = transport
27
+ self.max_retries = max_retries
28
+ self.retry_delay = retry_delay
29
+ self.retry_status_codes = retry_status_codes
30
+
31
+ async def handle_async_request(self, request: Request) -> Response:
32
+ last_response: Response | None = None
33
+ for attempt in range(self.max_retries):
34
+ last_response = await self.transport.handle_async_request(request)
35
+ if last_response.status_code not in self.retry_status_codes:
36
+ return last_response
37
+
38
+ self.logger.warning(
39
+ f"Attempt {attempt}/{self.max_retries} failed "
40
+ f"with status={last_response.status_code} for {request.method} {request.url}. "
41
+ f"Retrying in {self.retry_delay:.1f}s..."
42
+ )
43
+
44
+ await asyncio.sleep(self.retry_delay)
45
+
46
+ self.logger.error(
47
+ f"All {self.max_retries} attempts failed for "
48
+ f"{request.method} {request.url} (last status={last_response.status_code})"
49
+ )
50
+
51
+ return last_response
@@ -116,14 +116,14 @@ def render_unified(
116
116
  added_new_positions = file.added_line_numbers()
117
117
  removed_old_positions = file.removed_line_numbers()
118
118
 
119
- def in_context(old_no: int | None, new_no: int | None) -> bool:
119
+ def in_context(inner_old_no: int | None, inner_new_no: int | None) -> bool:
120
120
  """Check if an unchanged line falls within context radius."""
121
121
  if context <= 0:
122
122
  return False
123
- if include_added and new_no is not None:
123
+ if include_added and inner_new_no is not None:
124
124
  if any(abs(new_no - a) <= context for a in added_new_positions):
125
125
  return True
126
- if include_removed and old_no is not None:
126
+ if include_removed and inner_old_no is not None:
127
127
  if any(abs(old_no - r) <= context for r in removed_old_positions):
128
128
  return True
129
129
  return False
@@ -0,0 +1,41 @@
1
+ from pydantic import BaseModel, Field, field_serializer
2
+
3
+ from ai_review.config import settings
4
+ from ai_review.libs.template.render import render_template
5
+
6
+
7
+ class PromptContextSchema(BaseModel):
8
+ review_title: str = ""
9
+ review_description: str = ""
10
+
11
+ review_author_name: str = ""
12
+ review_author_username: str = ""
13
+
14
+ review_reviewer: str = ""
15
+ review_reviewers: list[str] = Field(default_factory=list)
16
+ review_reviewers_usernames: list[str] = Field(default_factory=list)
17
+
18
+ review_assignees: list[str] = Field(default_factory=list)
19
+ review_assignees_usernames: list[str] = Field(default_factory=list)
20
+
21
+ source_branch: str = ""
22
+ target_branch: str = ""
23
+
24
+ labels: list[str] = Field(default_factory=list)
25
+ changed_files: list[str] = Field(default_factory=list)
26
+
27
+ @field_serializer(
28
+ "review_reviewers",
29
+ "review_reviewers_usernames",
30
+ "review_assignees",
31
+ "review_assignees_usernames",
32
+ "labels",
33
+ "changed_files",
34
+ when_used="always"
35
+ )
36
+ def list_of_strings_serializer(self, value: list[str]) -> str:
37
+ return ", ".join(value)
38
+
39
+ def apply_format(self, prompt: str) -> str:
40
+ values = {**self.model_dump(), **settings.prompt.context}
41
+ return render_template(prompt, values, settings.prompt.context_placeholder)
@@ -0,0 +1,22 @@
1
+ import pytest
2
+ from pydantic import HttpUrl, SecretStr
3
+
4
+ from ai_review.config import settings
5
+ from ai_review.libs.config.claude import ClaudeMetaConfig, ClaudeHTTPClientConfig
6
+ from ai_review.libs.config.llm import ClaudeLLMConfig
7
+ from ai_review.libs.constants.llm_provider import LLMProvider
8
+
9
+
10
+ @pytest.fixture
11
+ def claude_http_client_config(monkeypatch: pytest.MonkeyPatch):
12
+ fake_config = ClaudeLLMConfig(
13
+ meta=ClaudeMetaConfig(),
14
+ provider=LLMProvider.CLAUDE,
15
+ http_client=ClaudeHTTPClientConfig(
16
+ timeout=10,
17
+ api_url=HttpUrl("https://api.anthropic.com"),
18
+ api_token=SecretStr("fake-token"),
19
+ api_version="2023-06-01",
20
+ )
21
+ )
22
+ monkeypatch.setattr(settings, "llm", fake_config)
@@ -1,16 +1,14 @@
1
1
  import pytest
2
- from httpx import AsyncClient
3
2
  from pydantic import HttpUrl, SecretStr
4
3
 
5
- from ai_review.clients.gemini.client import get_gemini_http_client, GeminiHTTPClient
6
4
  from ai_review.config import settings
7
5
  from ai_review.libs.config.gemini import GeminiMetaConfig, GeminiHTTPClientConfig
8
6
  from ai_review.libs.config.llm import GeminiLLMConfig
9
7
  from ai_review.libs.constants.llm_provider import LLMProvider
10
8
 
11
9
 
12
- @pytest.fixture(autouse=True)
13
- def gemini_http_client_config(monkeypatch):
10
+ @pytest.fixture
11
+ def gemini_http_client_config(monkeypatch: pytest.MonkeyPatch):
14
12
  fake_config = GeminiLLMConfig(
15
13
  meta=GeminiMetaConfig(),
16
14
  provider=LLMProvider.GEMINI,
@@ -21,10 +19,3 @@ def gemini_http_client_config(monkeypatch):
21
19
  )
22
20
  )
23
21
  monkeypatch.setattr(settings, "llm", fake_config)
24
-
25
-
26
- def test_get_gemini_http_client_builds_ok():
27
- gemini_http_client = get_gemini_http_client()
28
-
29
- assert isinstance(gemini_http_client, GeminiHTTPClient)
30
- assert isinstance(gemini_http_client.client, AsyncClient)