xai-review 0.20.0__tar.gz → 0.22.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.
- {xai_review-0.20.0 → xai_review-0.22.0}/PKG-INFO +5 -2
- {xai_review-0.20.0 → xai_review-0.22.0}/README.md +3 -1
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/claude/client.py +1 -1
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/gemini/client.py +1 -1
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/github/client.py +1 -1
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/github/pr/client.py +64 -16
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/github/pr/schema/comments.py +4 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/github/pr/schema/files.py +4 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/github/pr/schema/reviews.py +4 -0
- xai_review-0.22.0/ai_review/clients/github/pr/types.py +49 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/gitlab/client.py +1 -1
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/gitlab/mr/client.py +25 -8
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/gitlab/mr/schema/discussions.py +4 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/gitlab/mr/schema/notes.py +4 -0
- xai_review-0.22.0/ai_review/clients/gitlab/mr/types.py +35 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/openai/client.py +1 -1
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/config.py +2 -0
- xai_review-0.22.0/ai_review/libs/asynchronous/gather.py +17 -0
- xai_review-0.22.0/ai_review/libs/config/core.py +5 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/http/event_hooks/logger.py +5 -2
- xai_review-0.22.0/ai_review/libs/http/transports/retry.py +51 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/artifacts/service.py +2 -1
- xai_review-0.22.0/ai_review/services/artifacts/types.py +20 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/cost/service.py +2 -1
- xai_review-0.22.0/ai_review/services/cost/types.py +12 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/diff/service.py +2 -1
- xai_review-0.22.0/ai_review/services/diff/types.py +28 -0
- xai_review-0.22.0/ai_review/services/hook/__init__.py +5 -0
- xai_review-0.22.0/ai_review/services/hook/constants.py +24 -0
- xai_review-0.22.0/ai_review/services/hook/service.py +162 -0
- xai_review-0.22.0/ai_review/services/hook/types.py +28 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/llm/claude/client.py +2 -2
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/llm/factory.py +2 -2
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/llm/gemini/client.py +2 -2
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/llm/openai/client.py +2 -2
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/llm/types.py +1 -1
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/prompt/service.py +2 -1
- xai_review-0.22.0/ai_review/services/prompt/types.py +27 -0
- xai_review-0.22.0/ai_review/services/review/gateway/comment.py +65 -0
- xai_review-0.22.0/ai_review/services/review/gateway/llm.py +40 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/review/inline/schema.py +2 -2
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/review/inline/service.py +2 -1
- xai_review-0.22.0/ai_review/services/review/inline/types.py +11 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/review/service.py +23 -74
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/review/summary/service.py +2 -1
- xai_review-0.22.0/ai_review/services/review/summary/types.py +8 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/vcs/factory.py +2 -2
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/vcs/github/client.py +4 -2
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/vcs/gitlab/client.py +4 -2
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/vcs/types.py +1 -1
- xai_review-0.22.0/ai_review/tests/fixtures/clients/claude.py +22 -0
- xai_review-0.20.0/ai_review/tests/suites/clients/gemini/test_client.py → xai_review-0.22.0/ai_review/tests/fixtures/clients/gemini.py +2 -11
- xai_review-0.22.0/ai_review/tests/fixtures/clients/github.py +181 -0
- xai_review-0.22.0/ai_review/tests/fixtures/clients/gitlab.py +150 -0
- xai_review-0.20.0/ai_review/tests/suites/clients/openai/test_client.py → xai_review-0.22.0/ai_review/tests/fixtures/clients/openai.py +2 -11
- xai_review-0.22.0/ai_review/tests/fixtures/services/artifacts.py +51 -0
- xai_review-0.22.0/ai_review/tests/fixtures/services/cost.py +48 -0
- xai_review-0.22.0/ai_review/tests/fixtures/services/diff.py +46 -0
- {xai_review-0.20.0/ai_review/tests/fixtures → xai_review-0.22.0/ai_review/tests/fixtures/services}/git.py +11 -5
- xai_review-0.22.0/ai_review/tests/fixtures/services/llm.py +26 -0
- xai_review-0.22.0/ai_review/tests/fixtures/services/prompt.py +43 -0
- xai_review-0.22.0/ai_review/tests/fixtures/services/review/inline.py +25 -0
- xai_review-0.22.0/ai_review/tests/fixtures/services/review/summary.py +19 -0
- xai_review-0.22.0/ai_review/tests/fixtures/services/vcs.py +49 -0
- xai_review-0.22.0/ai_review/tests/suites/clients/claude/test_client.py +12 -0
- xai_review-0.22.0/ai_review/tests/suites/clients/gemini/test_client.py +12 -0
- xai_review-0.22.0/ai_review/tests/suites/clients/github/test_client.py +14 -0
- xai_review-0.22.0/ai_review/tests/suites/clients/gitlab/test_client.py +14 -0
- xai_review-0.22.0/ai_review/tests/suites/clients/openai/test_client.py +12 -0
- xai_review-0.22.0/ai_review/tests/suites/libs/asynchronous/test_gather.py +46 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/services/diff/test_service.py +4 -4
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/services/diff/test_tools.py +10 -10
- xai_review-0.22.0/ai_review/tests/suites/services/hook/__init__.py +0 -0
- xai_review-0.22.0/ai_review/tests/suites/services/hook/test_service.py +93 -0
- xai_review-0.22.0/ai_review/tests/suites/services/llm/__init__.py +0 -0
- xai_review-0.22.0/ai_review/tests/suites/services/llm/test_factory.py +30 -0
- xai_review-0.22.0/ai_review/tests/suites/services/prompt/__init__.py +0 -0
- xai_review-0.22.0/ai_review/tests/suites/services/review/__init__.py +0 -0
- xai_review-0.22.0/ai_review/tests/suites/services/review/inline/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/services/review/inline/test_schema.py +10 -9
- xai_review-0.22.0/ai_review/tests/suites/services/review/policy/__init__.py +0 -0
- xai_review-0.22.0/ai_review/tests/suites/services/review/summary/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/services/review/summary/test_schema.py +0 -1
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/services/review/summary/test_service.py +10 -7
- xai_review-0.22.0/ai_review/tests/suites/services/review/test_service.py +126 -0
- xai_review-0.22.0/ai_review/tests/suites/services/vcs/__init__.py +0 -0
- xai_review-0.22.0/ai_review/tests/suites/services/vcs/github/__init__.py +0 -0
- xai_review-0.22.0/ai_review/tests/suites/services/vcs/github/test_service.py +114 -0
- xai_review-0.22.0/ai_review/tests/suites/services/vcs/gitlab/__init__.py +0 -0
- xai_review-0.22.0/ai_review/tests/suites/services/vcs/gitlab/test_service.py +123 -0
- xai_review-0.22.0/ai_review/tests/suites/services/vcs/test_factory.py +23 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/pyproject.toml +2 -2
- {xai_review-0.20.0 → xai_review-0.22.0}/xai_review.egg-info/PKG-INFO +5 -2
- {xai_review-0.20.0 → xai_review-0.22.0}/xai_review.egg-info/SOURCES.txt +46 -1
- {xai_review-0.20.0 → xai_review-0.22.0}/xai_review.egg-info/requires.txt +1 -0
- xai_review-0.20.0/ai_review/libs/asynchronous/gather.py +0 -14
- xai_review-0.20.0/ai_review/libs/http/transports/retry.py +0 -34
- xai_review-0.20.0/ai_review/tests/suites/clients/claude/test_client.py +0 -31
- xai_review-0.20.0/ai_review/tests/suites/clients/github/test_client.py +0 -36
- xai_review-0.20.0/ai_review/tests/suites/clients/gitlab/test_client.py +0 -35
- {xai_review-0.20.0 → xai_review-0.22.0}/LICENSE +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/cli/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/cli/commands/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/cli/commands/run_context_review.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/cli/commands/run_inline_review.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/cli/commands/run_review.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/cli/commands/run_summary_review.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/cli/main.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/claude/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/claude/schema.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/gemini/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/gemini/schema.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/github/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/github/pr/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/github/pr/schema/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/github/pr/schema/pull_request.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/gitlab/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/gitlab/mr/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/gitlab/mr/schema/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/gitlab/mr/schema/changes.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/openai/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/clients/openai/schema.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/asynchronous/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/config/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/config/artifacts.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/config/base.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/config/claude.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/config/gemini.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/config/github.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/config/gitlab.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/config/http.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/config/llm.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/config/logger.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/config/openai.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/config/prompt.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/config/review.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/config/vcs.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/constants/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/constants/llm_provider.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/constants/vcs_provider.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/diff/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/diff/models.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/diff/parser.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/diff/tools.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/http/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/http/client.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/http/event_hooks/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/http/event_hooks/base.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/http/handlers.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/http/transports/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/json.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/logger.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/resources.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/template/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/libs/template/render.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/prompts/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/prompts/default_context.md +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/prompts/default_inline.md +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/prompts/default_summary.md +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/prompts/default_system_context.md +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/prompts/default_system_inline.md +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/prompts/default_system_summary.md +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/resources/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/resources/pricing.yaml +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/artifacts/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/artifacts/schema.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/artifacts/tools.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/cost/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/cost/schema.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/diff/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/diff/renderers.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/diff/schema.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/diff/tools.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/git/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/git/service.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/git/types.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/llm/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/llm/claude/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/llm/gemini/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/llm/openai/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/prompt/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/prompt/adapter.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/prompt/schema.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/prompt/tools.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/review/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/services/review/inline → xai_review-0.22.0/ai_review/services/review/gateway}/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/services/review/policy → xai_review-0.22.0/ai_review/services/review/inline}/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/services/review/summary → xai_review-0.22.0/ai_review/services/review/policy}/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/review/policy/service.py +0 -0
- {xai_review-0.20.0/ai_review/services/vcs → xai_review-0.22.0/ai_review/services/review/summary}/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/services/review/summary/schema.py +0 -0
- {xai_review-0.20.0/ai_review/services/vcs/github → xai_review-0.22.0/ai_review/services/vcs}/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/services/vcs/gitlab → xai_review-0.22.0/ai_review/services/vcs/github}/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/tests → xai_review-0.22.0/ai_review/services/vcs/gitlab}/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/tests/fixtures → xai_review-0.22.0/ai_review/tests}/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites → xai_review-0.22.0/ai_review/tests/fixtures}/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites → xai_review-0.22.0/ai_review/tests/fixtures}/clients/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/clients/claude → xai_review-0.22.0/ai_review/tests/fixtures/services}/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/clients/gemini → xai_review-0.22.0/ai_review/tests/fixtures/services/review}/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/clients/github → xai_review-0.22.0/ai_review/tests/suites}/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/clients/gitlab → xai_review-0.22.0/ai_review/tests/suites/clients}/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/clients/openai → xai_review-0.22.0/ai_review/tests/suites/clients/claude}/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/clients/claude/test_schema.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/libs → xai_review-0.22.0/ai_review/tests/suites/clients/gemini}/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/clients/gemini/test_schema.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/libs/config → xai_review-0.22.0/ai_review/tests/suites/clients/github}/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/libs/diff → xai_review-0.22.0/ai_review/tests/suites/clients/gitlab}/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/libs/template → xai_review-0.22.0/ai_review/tests/suites/clients/openai}/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/clients/openai/test_schema.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/services → xai_review-0.22.0/ai_review/tests/suites/libs}/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/services/cost → xai_review-0.22.0/ai_review/tests/suites/libs/asynchronous}/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/services/diff → xai_review-0.22.0/ai_review/tests/suites/libs/config}/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/libs/config/test_prompt.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/services/prompt → xai_review-0.22.0/ai_review/tests/suites/libs/diff}/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/libs/diff/test_models.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/libs/diff/test_parser.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/libs/diff/test_tools.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/services/review → xai_review-0.22.0/ai_review/tests/suites/libs/template}/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/libs/template/test_render.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/libs/test_json.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/services/review/inline → xai_review-0.22.0/ai_review/tests/suites/services}/__init__.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/services/review/policy → xai_review-0.22.0/ai_review/tests/suites/services/cost}/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/services/cost/test_schema.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/services/cost/test_service.py +0 -0
- {xai_review-0.20.0/ai_review/tests/suites/services/review/summary → xai_review-0.22.0/ai_review/tests/suites/services/diff}/__init__.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/services/diff/test_renderers.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/services/prompt/test_adapter.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/services/prompt/test_schema.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/services/prompt/test_service.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/services/prompt/test_tools.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/services/review/inline/test_service.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/ai_review/tests/suites/services/review/policy/test_service.py +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/setup.cfg +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/xai_review.egg-info/dependency_links.txt +0 -0
- {xai_review-0.20.0 → xai_review-0.22.0}/xai_review.egg-info/entry_points.txt +0 -0
- {xai_review-0.20.0 → xai_review-0.22.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.
|
|
3
|
+
Version: 0.22.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>
|
|
@@ -29,6 +29,7 @@ Requires-Dist: pydantic
|
|
|
29
29
|
Requires-Dist: pydantic-settings
|
|
30
30
|
Provides-Extra: test
|
|
31
31
|
Requires-Dist: pytest; extra == "test"
|
|
32
|
+
Requires-Dist: pytest-asyncio; extra == "test"
|
|
32
33
|
Dynamic: license-file
|
|
33
34
|
|
|
34
35
|
# AI Review
|
|
@@ -192,6 +193,7 @@ Add a workflow like this (manual trigger from **Actions** tab):
|
|
|
192
193
|
|
|
193
194
|
```yaml
|
|
194
195
|
name: AI Review
|
|
196
|
+
|
|
195
197
|
on:
|
|
196
198
|
workflow_dispatch:
|
|
197
199
|
inputs:
|
|
@@ -207,7 +209,7 @@ jobs:
|
|
|
207
209
|
runs-on: ubuntu-latest
|
|
208
210
|
steps:
|
|
209
211
|
- uses: actions/checkout@v4
|
|
210
|
-
- uses: Nikita-Filonov/ai-review@v0.
|
|
212
|
+
- uses: Nikita-Filonov/ai-review@v0.22.0
|
|
211
213
|
with:
|
|
212
214
|
review-command: ${{ inputs.review-command }}
|
|
213
215
|
env:
|
|
@@ -272,6 +274,7 @@ ai-review:
|
|
|
272
274
|
See these folders for reference templates and full configuration options:
|
|
273
275
|
|
|
274
276
|
- [./docs/ci](./docs/ci) — CI/CD integration templates (GitHub Actions, GitLab CI)
|
|
277
|
+
- [./docs/hooks](./docs/hooks) — hook reference and lifecycle events
|
|
275
278
|
- [./docs/configs](./docs/configs) — full configuration examples (`.yaml`, `.json`, `.env`)
|
|
276
279
|
- [./docs/prompts](./docs/prompts) — prompt templates for Python/Go (light & strict modes)
|
|
277
280
|
|
|
@@ -159,6 +159,7 @@ Add a workflow like this (manual trigger from **Actions** tab):
|
|
|
159
159
|
|
|
160
160
|
```yaml
|
|
161
161
|
name: AI Review
|
|
162
|
+
|
|
162
163
|
on:
|
|
163
164
|
workflow_dispatch:
|
|
164
165
|
inputs:
|
|
@@ -174,7 +175,7 @@ jobs:
|
|
|
174
175
|
runs-on: ubuntu-latest
|
|
175
176
|
steps:
|
|
176
177
|
- uses: actions/checkout@v4
|
|
177
|
-
- uses: Nikita-Filonov/ai-review@v0.
|
|
178
|
+
- uses: Nikita-Filonov/ai-review@v0.22.0
|
|
178
179
|
with:
|
|
179
180
|
review-command: ${{ inputs.review-command }}
|
|
180
181
|
env:
|
|
@@ -239,6 +240,7 @@ ai-review:
|
|
|
239
240
|
See these folders for reference templates and full configuration options:
|
|
240
241
|
|
|
241
242
|
- [./docs/ci](./docs/ci) — CI/CD integration templates (GitHub Actions, GitLab CI)
|
|
243
|
+
- [./docs/hooks](./docs/hooks) — hook reference and lifecycle events
|
|
242
244
|
- [./docs/configs](./docs/configs) — full configuration examples (`.yaml`, `.json`, `.env`)
|
|
243
245
|
- [./docs/prompts](./docs/prompts) — prompt templates for Python/Go (light & strict modes)
|
|
244
246
|
|
|
@@ -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
|
|
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
|
|
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(
|
|
28
|
-
|
|
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(
|
|
32
|
-
|
|
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(
|
|
36
|
-
|
|
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(
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -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)
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
from
|
|
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
|
|
@@ -6,11 +6,12 @@ from ai_review.config import settings
|
|
|
6
6
|
from ai_review.libs.logger import get_logger
|
|
7
7
|
from ai_review.services.artifacts.schema import LLMArtifactSchema
|
|
8
8
|
from ai_review.services.artifacts.tools import make_artifact_id
|
|
9
|
+
from ai_review.services.artifacts.types import ArtifactsServiceProtocol
|
|
9
10
|
|
|
10
11
|
logger = get_logger("ARTIFACTS_SERVICE")
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
class ArtifactsService:
|
|
14
|
+
class ArtifactsService(ArtifactsServiceProtocol):
|
|
14
15
|
@classmethod
|
|
15
16
|
async def save_llm_interaction(cls, prompt: str, prompt_system: str, response: str | None = None) -> str | None:
|
|
16
17
|
if not settings.artifacts.llm_enabled:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Protocol
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ArtifactsServiceProtocol(Protocol):
|
|
6
|
+
async def save_llm_interaction(
|
|
7
|
+
self,
|
|
8
|
+
prompt: str,
|
|
9
|
+
prompt_system: str,
|
|
10
|
+
response: str | None = None
|
|
11
|
+
) -> str | None:
|
|
12
|
+
...
|
|
13
|
+
|
|
14
|
+
async def save_artifact(
|
|
15
|
+
self,
|
|
16
|
+
file: Path,
|
|
17
|
+
content: str,
|
|
18
|
+
kind: str = "artifact"
|
|
19
|
+
) -> Path | None:
|
|
20
|
+
...
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from ai_review.config import settings
|
|
2
2
|
from ai_review.libs.logger import get_logger
|
|
3
3
|
from ai_review.services.cost.schema import CostReportSchema
|
|
4
|
+
from ai_review.services.cost.types import CostServiceProtocol
|
|
4
5
|
from ai_review.services.llm.types import ChatResultSchema
|
|
5
6
|
|
|
6
7
|
logger = get_logger("COST_SERVICE")
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
class CostService:
|
|
10
|
+
class CostService(CostServiceProtocol):
|
|
10
11
|
def __init__(self):
|
|
11
12
|
self.pricing = settings.llm.load_pricing()
|
|
12
13
|
self.reports: list[CostReportSchema] = []
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from typing import Protocol
|
|
2
|
+
|
|
3
|
+
from ai_review.services.cost.schema import CostReportSchema
|
|
4
|
+
from ai_review.services.llm.types import ChatResultSchema
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CostServiceProtocol(Protocol):
|
|
8
|
+
def calculate(self, result: ChatResultSchema) -> CostReportSchema | None:
|
|
9
|
+
...
|
|
10
|
+
|
|
11
|
+
def aggregate(self) -> CostReportSchema | None:
|
|
12
|
+
...
|