xai-review 0.33.0__py3-none-any.whl → 0.34.0__py3-none-any.whl

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 (28) hide show
  1. ai_review/clients/openrouter/__init__.py +0 -0
  2. ai_review/clients/openrouter/client.py +50 -0
  3. ai_review/clients/openrouter/schema.py +36 -0
  4. ai_review/clients/openrouter/types.py +11 -0
  5. ai_review/libs/config/llm/base.py +8 -1
  6. ai_review/libs/config/llm/openrouter.py +12 -0
  7. ai_review/libs/constants/llm_provider.py +1 -0
  8. ai_review/services/llm/factory.py +3 -0
  9. ai_review/services/llm/openrouter/__init__.py +0 -0
  10. ai_review/services/llm/openrouter/client.py +31 -0
  11. ai_review/tests/fixtures/clients/openai.py +1 -1
  12. ai_review/tests/fixtures/clients/openrouter.py +72 -0
  13. ai_review/tests/suites/clients/openrouter/__init__.py +0 -0
  14. ai_review/tests/suites/clients/openrouter/test_client.py +12 -0
  15. ai_review/tests/suites/clients/openrouter/test_schema.py +57 -0
  16. ai_review/tests/suites/services/llm/openrouter/__init__.py +0 -0
  17. ai_review/tests/suites/services/llm/openrouter/test_client.py +22 -0
  18. ai_review/tests/suites/services/llm/test_factory.py +7 -0
  19. ai_review/tests/suites/services/review/gateway/test_review_comment_gateway.py +5 -5
  20. ai_review/tests/suites/services/review/gateway/test_review_dry_run_comment_gateway.py +5 -5
  21. ai_review/tests/suites/services/review/gateway/test_review_llm_gateway.py +2 -2
  22. ai_review/tests/suites/services/review/test_service.py +2 -2
  23. {xai_review-0.33.0.dist-info → xai_review-0.34.0.dist-info}/METADATA +11 -10
  24. {xai_review-0.33.0.dist-info → xai_review-0.34.0.dist-info}/RECORD +28 -15
  25. {xai_review-0.33.0.dist-info → xai_review-0.34.0.dist-info}/WHEEL +0 -0
  26. {xai_review-0.33.0.dist-info → xai_review-0.34.0.dist-info}/entry_points.txt +0 -0
  27. {xai_review-0.33.0.dist-info → xai_review-0.34.0.dist-info}/licenses/LICENSE +0 -0
  28. {xai_review-0.33.0.dist-info → xai_review-0.34.0.dist-info}/top_level.txt +0 -0
File without changes
@@ -0,0 +1,50 @@
1
+ from httpx import AsyncClient, Response, AsyncHTTPTransport
2
+
3
+ from ai_review.clients.openrouter.schema import OpenRouterChatRequestSchema, OpenRouterChatResponseSchema
4
+ from ai_review.clients.openrouter.types import OpenRouterHTTPClientProtocol
5
+ from ai_review.config import settings
6
+ from ai_review.libs.http.client import HTTPClient
7
+ from ai_review.libs.http.event_hooks.logger import LoggerEventHook
8
+ from ai_review.libs.http.handlers import HTTPClientError, handle_http_error
9
+ from ai_review.libs.http.transports.retry import RetryTransport
10
+ from ai_review.libs.logger import get_logger
11
+
12
+
13
+ class OpenRouterHTTPClientError(HTTPClientError):
14
+ pass
15
+
16
+
17
+ class OpenRouterHTTPClient(HTTPClient, OpenRouterHTTPClientProtocol):
18
+ @handle_http_error(client="OpenRouterHTTPClient", exception=OpenRouterHTTPClientError)
19
+ async def chat_api(self, request: OpenRouterChatRequestSchema) -> Response:
20
+ return await self.post("/chat/completions", json=request.model_dump())
21
+
22
+ async def chat(self, request: OpenRouterChatRequestSchema) -> OpenRouterChatResponseSchema:
23
+ response = await self.chat_api(request)
24
+ return OpenRouterChatResponseSchema.model_validate_json(response.text)
25
+
26
+
27
+ def get_openrouter_http_client() -> OpenRouterHTTPClient:
28
+ logger = get_logger("OPENROUTER_HTTP_CLIENT")
29
+ logger_event_hook = LoggerEventHook(logger=logger)
30
+ retry_transport = RetryTransport(logger=logger, transport=AsyncHTTPTransport())
31
+
32
+ headers = {"Authorization": f"Bearer {settings.llm.http_client.api_token_value}"}
33
+ if settings.llm.meta.title:
34
+ headers["X-Title"] = settings.llm.meta.title
35
+
36
+ if settings.llm.meta.referer:
37
+ headers["Referer"] = settings.llm.meta.referer
38
+
39
+ client = AsyncClient(
40
+ timeout=settings.llm.http_client.timeout,
41
+ headers=headers,
42
+ base_url=settings.llm.http_client.api_url_value,
43
+ transport=retry_transport,
44
+ event_hooks={
45
+ "request": [logger_event_hook.request],
46
+ "response": [logger_event_hook.response],
47
+ },
48
+ )
49
+
50
+ return OpenRouterHTTPClient(client=client)
@@ -0,0 +1,36 @@
1
+ from typing import Literal
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class OpenRouterUsageSchema(BaseModel):
7
+ total_tokens: int
8
+ prompt_tokens: int
9
+ completion_tokens: int
10
+
11
+
12
+ class OpenRouterMessageSchema(BaseModel):
13
+ role: Literal["system", "user", "assistant"]
14
+ content: str
15
+
16
+
17
+ class OpenRouterChoiceSchema(BaseModel):
18
+ message: OpenRouterMessageSchema
19
+
20
+
21
+ class OpenRouterChatRequestSchema(BaseModel):
22
+ model: str
23
+ messages: list[OpenRouterMessageSchema]
24
+ max_tokens: int
25
+ temperature: float
26
+
27
+
28
+ class OpenRouterChatResponseSchema(BaseModel):
29
+ usage: OpenRouterUsageSchema
30
+ choices: list[OpenRouterChoiceSchema]
31
+
32
+ @property
33
+ def first_text(self) -> str:
34
+ if not self.choices:
35
+ return ""
36
+ return (self.choices[0].message.content or "").strip()
@@ -0,0 +1,11 @@
1
+ from typing import Protocol
2
+
3
+ from ai_review.clients.openrouter.schema import (
4
+ OpenRouterChatRequestSchema,
5
+ OpenRouterChatResponseSchema
6
+ )
7
+
8
+
9
+ class OpenRouterHTTPClientProtocol(Protocol):
10
+ async def chat(self, request: OpenRouterChatRequestSchema) -> OpenRouterChatResponseSchema:
11
+ ...
@@ -8,6 +8,7 @@ from ai_review.libs.config.llm.claude import ClaudeHTTPClientConfig, ClaudeMetaC
8
8
  from ai_review.libs.config.llm.gemini import GeminiHTTPClientConfig, GeminiMetaConfig
9
9
  from ai_review.libs.config.llm.ollama import OllamaHTTPClientConfig, OllamaMetaConfig
10
10
  from ai_review.libs.config.llm.openai import OpenAIHTTPClientConfig, OpenAIMetaConfig
11
+ from ai_review.libs.config.llm.openrouter import OpenRouterHTTPClientConfig, OpenRouterMetaConfig
11
12
  from ai_review.libs.constants.llm_provider import LLMProvider
12
13
  from ai_review.libs.resources import load_resource
13
14
 
@@ -62,7 +63,13 @@ class OllamaLLMConfig(LLMConfigBase):
62
63
  http_client: OllamaHTTPClientConfig
63
64
 
64
65
 
66
+ class OpenRouterLLMConfig(LLMConfigBase):
67
+ meta: OpenRouterMetaConfig
68
+ provider: Literal[LLMProvider.OPENROUTER]
69
+ http_client: OpenRouterHTTPClientConfig
70
+
71
+
65
72
  LLMConfig = Annotated[
66
- OpenAILLMConfig | GeminiLLMConfig | ClaudeLLMConfig | OllamaLLMConfig,
73
+ OpenAILLMConfig | GeminiLLMConfig | ClaudeLLMConfig | OllamaLLMConfig | OpenRouterLLMConfig,
67
74
  Field(discriminator="provider")
68
75
  ]
@@ -0,0 +1,12 @@
1
+ from ai_review.libs.config.http import HTTPClientWithTokenConfig
2
+ from ai_review.libs.config.llm.meta import LLMMetaConfig
3
+
4
+
5
+ class OpenRouterMetaConfig(LLMMetaConfig):
6
+ model: str = "openai/gpt-4o-mini"
7
+ title: str | None = None
8
+ referer: str | None = None
9
+
10
+
11
+ class OpenRouterHTTPClientConfig(HTTPClientWithTokenConfig):
12
+ pass
@@ -6,3 +6,4 @@ class LLMProvider(StrEnum):
6
6
  GEMINI = "GEMINI"
7
7
  CLAUDE = "CLAUDE"
8
8
  OLLAMA = "OLLAMA"
9
+ OPENROUTER = "OPENROUTER"
@@ -4,6 +4,7 @@ from ai_review.services.llm.claude.client import ClaudeLLMClient
4
4
  from ai_review.services.llm.gemini.client import GeminiLLMClient
5
5
  from ai_review.services.llm.ollama.client import OllamaLLMClient
6
6
  from ai_review.services.llm.openai.client import OpenAILLMClient
7
+ from ai_review.services.llm.openrouter.client import OpenRouterLLMClient
7
8
  from ai_review.services.llm.types import LLMClientProtocol
8
9
 
9
10
 
@@ -17,5 +18,7 @@ def get_llm_client() -> LLMClientProtocol:
17
18
  return ClaudeLLMClient()
18
19
  case LLMProvider.OLLAMA:
19
20
  return OllamaLLMClient()
21
+ case LLMProvider.OPENROUTER:
22
+ return OpenRouterLLMClient()
20
23
  case _:
21
24
  raise ValueError(f"Unsupported LLM provider: {settings.llm.provider}")
File without changes
@@ -0,0 +1,31 @@
1
+ from ai_review.clients.openrouter.client import get_openrouter_http_client
2
+ from ai_review.clients.openrouter.schema import (
3
+ OpenRouterMessageSchema,
4
+ OpenRouterChatRequestSchema,
5
+ )
6
+ from ai_review.config import settings
7
+ from ai_review.services.llm.types import LLMClientProtocol, ChatResultSchema
8
+
9
+
10
+ class OpenRouterLLMClient(LLMClientProtocol):
11
+ def __init__(self):
12
+ self.http_client = get_openrouter_http_client()
13
+
14
+ async def chat(self, prompt: str, prompt_system: str) -> ChatResultSchema:
15
+ meta = settings.llm.meta
16
+ request = OpenRouterChatRequestSchema(
17
+ model=meta.model,
18
+ messages=[
19
+ OpenRouterMessageSchema(role="system", content=prompt_system),
20
+ OpenRouterMessageSchema(role="user", content=prompt),
21
+ ],
22
+ max_tokens=meta.max_tokens,
23
+ temperature=meta.temperature,
24
+ )
25
+ response = await self.http_client.chat(request)
26
+ return ChatResultSchema(
27
+ text=response.first_text,
28
+ total_tokens=response.usage.total_tokens,
29
+ prompt_tokens=response.usage.prompt_tokens,
30
+ completion_tokens=response.usage.completion_tokens,
31
+ )
@@ -39,7 +39,7 @@ class FakeOpenAIHTTPClient(OpenAIHTTPClientProtocol):
39
39
 
40
40
 
41
41
  @pytest.fixture
42
- def fake_openai_http_client():
42
+ def fake_openai_http_client() -> FakeOpenAIHTTPClient:
43
43
  return FakeOpenAIHTTPClient()
44
44
 
45
45
 
@@ -0,0 +1,72 @@
1
+ from typing import Any
2
+
3
+ import pytest
4
+ from pydantic import HttpUrl, SecretStr
5
+
6
+ from ai_review.clients.openrouter.schema import (
7
+ OpenRouterUsageSchema,
8
+ OpenRouterChoiceSchema,
9
+ OpenRouterMessageSchema,
10
+ OpenRouterChatRequestSchema,
11
+ OpenRouterChatResponseSchema,
12
+ )
13
+ from ai_review.clients.openrouter.types import OpenRouterHTTPClientProtocol
14
+ from ai_review.config import settings
15
+ from ai_review.libs.config.llm.base import OpenRouterLLMConfig
16
+ from ai_review.libs.config.llm.openrouter import OpenRouterMetaConfig, OpenRouterHTTPClientConfig
17
+ from ai_review.libs.constants.llm_provider import LLMProvider
18
+ from ai_review.services.llm.openrouter.client import OpenRouterLLMClient
19
+
20
+
21
+ class FakeOpenRouterHTTPClient(OpenRouterHTTPClientProtocol):
22
+ def __init__(self, responses: dict[str, Any] | None = None) -> None:
23
+ self.calls: list[tuple[str, dict]] = []
24
+ self.responses = responses or {}
25
+
26
+ async def chat(self, request: OpenRouterChatRequestSchema) -> OpenRouterChatResponseSchema:
27
+ self.calls.append(("chat", {"request": request}))
28
+ return self.responses.get(
29
+ "chat",
30
+ OpenRouterChatResponseSchema(
31
+ usage=OpenRouterUsageSchema(total_tokens=12, prompt_tokens=5, completion_tokens=7),
32
+ choices=[
33
+ OpenRouterChoiceSchema(
34
+ message=OpenRouterMessageSchema(
35
+ role="assistant",
36
+ content="FAKE_OPENROUTER_RESPONSE"
37
+ )
38
+ )
39
+ ],
40
+ ),
41
+ )
42
+
43
+
44
+ @pytest.fixture
45
+ def fake_openrouter_http_client() -> FakeOpenRouterHTTPClient:
46
+ return FakeOpenRouterHTTPClient()
47
+
48
+
49
+ @pytest.fixture
50
+ def openrouter_llm_client(
51
+ monkeypatch: pytest.MonkeyPatch,
52
+ fake_openrouter_http_client: FakeOpenRouterHTTPClient
53
+ ) -> OpenRouterLLMClient:
54
+ monkeypatch.setattr(
55
+ "ai_review.services.llm.openrouter.client.get_openrouter_http_client",
56
+ lambda: fake_openrouter_http_client,
57
+ )
58
+ return OpenRouterLLMClient()
59
+
60
+
61
+ @pytest.fixture
62
+ def openrouter_http_client_config(monkeypatch: pytest.MonkeyPatch):
63
+ fake_config = OpenRouterLLMConfig(
64
+ meta=OpenRouterMetaConfig(),
65
+ provider=LLMProvider.OPENROUTER,
66
+ http_client=OpenRouterHTTPClientConfig(
67
+ timeout=10,
68
+ api_url=HttpUrl("https://openrouter.ai/api/v1"),
69
+ api_token=SecretStr("fake-token"),
70
+ )
71
+ )
72
+ monkeypatch.setattr(settings, "llm", fake_config)
File without changes
@@ -0,0 +1,12 @@
1
+ import pytest
2
+ from httpx import AsyncClient
3
+
4
+ from ai_review.clients.openrouter.client import get_openrouter_http_client, OpenRouterHTTPClient
5
+
6
+
7
+ @pytest.mark.usefixtures('openrouter_http_client_config')
8
+ def test_get_openrouter_http_client_builds_ok():
9
+ openrouter_http_client = get_openrouter_http_client()
10
+
11
+ assert isinstance(openrouter_http_client, OpenRouterHTTPClient)
12
+ assert isinstance(openrouter_http_client.client, AsyncClient)
@@ -0,0 +1,57 @@
1
+ from ai_review.clients.openrouter.schema import (
2
+ OpenRouterUsageSchema,
3
+ OpenRouterChoiceSchema,
4
+ OpenRouterMessageSchema,
5
+ OpenRouterChatRequestSchema,
6
+ OpenRouterChatResponseSchema,
7
+ )
8
+
9
+
10
+ # ---------- OpenRouterChatResponseSchema ----------
11
+
12
+ def test_first_text_returns_text():
13
+ resp = OpenRouterChatResponseSchema(
14
+ usage=OpenRouterUsageSchema(total_tokens=5, prompt_tokens=2, completion_tokens=3),
15
+ choices=[
16
+ OpenRouterChoiceSchema(
17
+ message=OpenRouterMessageSchema(role="assistant", content=" hello world ")
18
+ )
19
+ ],
20
+ )
21
+ assert resp.first_text == "hello world"
22
+
23
+
24
+ def test_first_text_empty_if_no_choices():
25
+ resp = OpenRouterChatResponseSchema(
26
+ usage=OpenRouterUsageSchema(total_tokens=1, prompt_tokens=1, completion_tokens=0),
27
+ choices=[],
28
+ )
29
+ assert resp.first_text == ""
30
+
31
+
32
+ def test_first_text_strips_and_handles_empty_content():
33
+ resp = OpenRouterChatResponseSchema(
34
+ usage=OpenRouterUsageSchema(total_tokens=1, prompt_tokens=1, completion_tokens=0),
35
+ choices=[
36
+ OpenRouterChoiceSchema(
37
+ message=OpenRouterMessageSchema(role="assistant", content=" ")
38
+ )
39
+ ],
40
+ )
41
+ assert resp.first_text == ""
42
+
43
+
44
+ # ---------- OpenRouterChatRequestSchema ----------
45
+
46
+ def test_chat_request_schema_builds_ok():
47
+ msg = OpenRouterMessageSchema(role="user", content="hello")
48
+ req = OpenRouterChatRequestSchema(
49
+ model="gpt-4o-mini",
50
+ messages=[msg],
51
+ max_tokens=100,
52
+ temperature=0.3,
53
+ )
54
+ assert req.model == "gpt-4o-mini"
55
+ assert req.messages[0].content == "hello"
56
+ assert req.max_tokens == 100
57
+ assert req.temperature == 0.3
@@ -0,0 +1,22 @@
1
+ import pytest
2
+
3
+ from ai_review.services.llm.openrouter.client import OpenRouterLLMClient
4
+ from ai_review.services.llm.types import ChatResultSchema
5
+ from ai_review.tests.fixtures.clients.openrouter import FakeOpenRouterHTTPClient
6
+
7
+
8
+ @pytest.mark.asyncio
9
+ @pytest.mark.usefixtures("openrouter_http_client_config")
10
+ async def test_openrouter_llm_chat(
11
+ openrouter_llm_client: OpenRouterLLMClient,
12
+ fake_openrouter_http_client: FakeOpenRouterHTTPClient
13
+ ):
14
+ result = await openrouter_llm_client.chat("prompt", "prompt_system")
15
+
16
+ assert isinstance(result, ChatResultSchema)
17
+ assert result.text == "FAKE_OPENROUTER_RESPONSE"
18
+ assert result.total_tokens == 12
19
+ assert result.prompt_tokens == 5
20
+ assert result.completion_tokens == 7
21
+
22
+ assert fake_openrouter_http_client.calls[0][0] == "chat"
@@ -5,6 +5,7 @@ from ai_review.services.llm.factory import get_llm_client
5
5
  from ai_review.services.llm.gemini.client import GeminiLLMClient
6
6
  from ai_review.services.llm.ollama.client import OllamaLLMClient
7
7
  from ai_review.services.llm.openai.client import OpenAILLMClient
8
+ from ai_review.services.llm.openrouter.client import OpenRouterLLMClient
8
9
 
9
10
 
10
11
  @pytest.mark.usefixtures("openai_http_client_config")
@@ -31,6 +32,12 @@ def test_get_llm_client_returns_ollama(monkeypatch: pytest.MonkeyPatch):
31
32
  assert isinstance(client, OllamaLLMClient)
32
33
 
33
34
 
35
+ @pytest.mark.usefixtures("openrouter_http_client_config")
36
+ def test_get_llm_client_returns_openrouter(monkeypatch: pytest.MonkeyPatch):
37
+ client = get_llm_client()
38
+ assert isinstance(client, OpenRouterLLMClient)
39
+
40
+
34
41
  def test_get_llm_client_unsupported_provider(monkeypatch: pytest.MonkeyPatch):
35
42
  monkeypatch.setattr("ai_review.services.llm.factory.settings.llm.provider", "UNSUPPORTED")
36
43
  with pytest.raises(ValueError):
@@ -72,7 +72,7 @@ async def test_get_summary_threads_filters_by_tag(
72
72
 
73
73
  @pytest.mark.asyncio
74
74
  async def test_has_existing_inline_comments_true(
75
- capsys,
75
+ capsys: pytest.CaptureFixture,
76
76
  fake_vcs_client: FakeVCSClient,
77
77
  review_comment_gateway: ReviewCommentGateway,
78
78
  ):
@@ -118,7 +118,7 @@ async def test_process_inline_reply_happy_path(
118
118
 
119
119
  @pytest.mark.asyncio
120
120
  async def test_process_inline_reply_error(
121
- capsys,
121
+ capsys: pytest.CaptureFixture,
122
122
  fake_vcs_client: FakeVCSClient,
123
123
  review_comment_gateway: ReviewCommentGateway,
124
124
  ):
@@ -151,7 +151,7 @@ async def test_process_summary_reply_success(
151
151
 
152
152
  @pytest.mark.asyncio
153
153
  async def test_process_summary_reply_error(
154
- capsys,
154
+ capsys: pytest.CaptureFixture,
155
155
  fake_vcs_client: FakeVCSClient,
156
156
  review_comment_gateway: ReviewCommentGateway,
157
157
  ):
@@ -184,7 +184,7 @@ async def test_process_inline_comment_happy_path(
184
184
 
185
185
  @pytest.mark.asyncio
186
186
  async def test_process_inline_comment_error_fallback(
187
- capsys,
187
+ capsys: pytest.CaptureFixture,
188
188
  fake_vcs_client: FakeVCSClient,
189
189
  review_comment_gateway: ReviewCommentGateway,
190
190
  ):
@@ -218,7 +218,7 @@ async def test_process_summary_comment_happy_path(
218
218
 
219
219
  @pytest.mark.asyncio
220
220
  async def test_process_summary_comment_error(
221
- capsys,
221
+ capsys: pytest.CaptureFixture,
222
222
  fake_vcs_client: FakeVCSClient,
223
223
  review_comment_gateway: ReviewCommentGateway,
224
224
  ):
@@ -10,7 +10,7 @@ from ai_review.tests.fixtures.services.vcs import FakeVCSClient
10
10
 
11
11
  @pytest.mark.asyncio
12
12
  async def test_process_inline_reply_dry_run_logs_and_no_vcs_calls(
13
- capsys,
13
+ capsys: pytest.CaptureFixture,
14
14
  fake_vcs_client: FakeVCSClient,
15
15
  review_dry_run_comment_gateway: ReviewDryRunCommentGateway
16
16
  ):
@@ -26,7 +26,7 @@ async def test_process_inline_reply_dry_run_logs_and_no_vcs_calls(
26
26
 
27
27
  @pytest.mark.asyncio
28
28
  async def test_process_summary_reply_dry_run_logs_and_no_vcs_calls(
29
- capsys,
29
+ capsys: pytest.CaptureFixture,
30
30
  fake_vcs_client: FakeVCSClient,
31
31
  review_dry_run_comment_gateway: ReviewDryRunCommentGateway
32
32
  ):
@@ -42,7 +42,7 @@ async def test_process_summary_reply_dry_run_logs_and_no_vcs_calls(
42
42
 
43
43
  @pytest.mark.asyncio
44
44
  async def test_process_inline_comment_dry_run_logs_and_no_vcs_calls(
45
- capsys,
45
+ capsys: pytest.CaptureFixture,
46
46
  fake_vcs_client: FakeVCSClient,
47
47
  review_dry_run_comment_gateway: ReviewDryRunCommentGateway
48
48
  ):
@@ -59,7 +59,7 @@ async def test_process_inline_comment_dry_run_logs_and_no_vcs_calls(
59
59
 
60
60
  @pytest.mark.asyncio
61
61
  async def test_process_summary_comment_dry_run_logs_and_no_vcs_calls(
62
- capsys,
62
+ capsys: pytest.CaptureFixture,
63
63
  fake_vcs_client: FakeVCSClient,
64
64
  review_dry_run_comment_gateway: ReviewDryRunCommentGateway
65
65
  ):
@@ -75,7 +75,7 @@ async def test_process_summary_comment_dry_run_logs_and_no_vcs_calls(
75
75
 
76
76
  @pytest.mark.asyncio
77
77
  async def test_process_inline_comments_iterates_all(
78
- capsys,
78
+ capsys: pytest.CaptureFixture,
79
79
  fake_vcs_client: FakeVCSClient,
80
80
  review_dry_run_comment_gateway: ReviewDryRunCommentGateway
81
81
  ):
@@ -27,7 +27,7 @@ async def test_ask_happy_path(
27
27
 
28
28
  @pytest.mark.asyncio
29
29
  async def test_ask_warns_on_empty_response(
30
- capsys,
30
+ capsys: pytest.CaptureFixture,
31
31
  review_llm_gateway: ReviewLLMGateway,
32
32
  fake_llm_client: FakeLLMClient,
33
33
  fake_cost_service: FakeCostService,
@@ -49,7 +49,7 @@ async def test_ask_warns_on_empty_response(
49
49
 
50
50
  @pytest.mark.asyncio
51
51
  async def test_ask_handles_llm_error(
52
- capsys,
52
+ capsys: pytest.CaptureFixture,
53
53
  fake_llm_client: FakeLLMClient,
54
54
  review_llm_gateway: ReviewLLMGateway,
55
55
  ):
@@ -63,7 +63,7 @@ async def test_run_summary_reply_review_invokes_runner(
63
63
 
64
64
 
65
65
  def test_report_total_cost_with_data(
66
- capsys,
66
+ capsys: pytest.CaptureFixture,
67
67
  review_service: ReviewService,
68
68
  fake_cost_service: FakeCostService
69
69
  ):
@@ -87,7 +87,7 @@ def test_report_total_cost_with_data(
87
87
  assert "0.006" in output
88
88
 
89
89
 
90
- def test_report_total_cost_no_data(capsys, review_service: ReviewService):
90
+ def test_report_total_cost_no_data(capsys: pytest.CaptureFixture, review_service: ReviewService):
91
91
  """Should log message when no cost data is available."""
92
92
  review_service.report_total_cost()
93
93
  output = capsys.readouterr().out
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xai-review
3
- Version: 0.33.0
4
- Summary: AI-powered code review tool
3
+ Version: 0.34.0
4
+ Summary: AI-powered code review tool for GitHub, GitLab, Bitbucket and Gitea — built with LLMs like OpenAI, Claude, Gemini, Ollama, and OpenRouter
5
5
  Author-email: Nikita Filonov <nikita.filonov@example.com>
6
6
  Maintainer-email: Nikita Filonov <nikita.filonov@example.com>
7
7
  License: Apache-2.0
8
8
  Project-URL: Issues, https://github.com/Nikita-Filonov/ai-review/issues
9
9
  Project-URL: Homepage, https://github.com/Nikita-Filonov/ai-review
10
10
  Project-URL: Repository, https://github.com/Nikita-Filonov/ai-review
11
- Keywords: ai,code review,llm,openai,claude,gemini
11
+ Keywords: ai,code review,llm,openai,claude,gemini,ollama,openrouter,ci/cd,gitlab,github,gitea,bitbucket
12
12
  Classifier: Programming Language :: Python :: 3
13
13
  Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
@@ -66,7 +66,8 @@ improve code quality, enforce consistency, and speed up the review process.
66
66
 
67
67
  ✨ Key features:
68
68
 
69
- - **Multiple LLM providers** — choose between **OpenAI**, **Claude**, **Gemini**, or **Ollama**, and switch anytime.
69
+ - **Multiple LLM providers** — choose between **OpenAI**, **Claude**, **Gemini**, **Ollama**, or **OpenRouter**, and
70
+ switch anytime.
70
71
  - **VCS integration** — works out of the box with **GitLab**, **GitHub**, **Bitbucket**, and **Gitea**.
71
72
  - **Customizable prompts** — adapt inline, context, and summary reviews to match your team’s coding guidelines.
72
73
  - **Reply modes** — AI can now **participate in existing review threads**, adding follow-up replies in both inline and
@@ -120,7 +121,7 @@ pip install xai-review
120
121
  Or run directly via Docker:
121
122
 
122
123
  ```bash
123
- docker run --rm -v $(pwd):/app nikitafilonov/ai-review:latest run-summary
124
+ docker run --rm -v $(pwd):/app nikitafilonov/ai-review:latest ai-review run-summary
124
125
  ```
125
126
 
126
127
  🐳 Pull from [DockerHub](https://hub.docker.com/r/nikitafilonov/ai-review)
@@ -146,8 +147,8 @@ vcs:
146
147
  provider: GITLAB
147
148
 
148
149
  pipeline:
149
- project_id: 1
150
- merge_request_id: 100
150
+ project_id: "1"
151
+ merge_request_id: "100"
151
152
 
152
153
  http_client:
153
154
  timeout: 120
@@ -176,7 +177,7 @@ for complete, ready-to-use examples.
176
177
 
177
178
  Key things you can customize:
178
179
 
179
- - **LLM provider** — OpenAI, Gemini, Claude, or Ollama
180
+ - **LLM provider** — OpenAI, Gemini, Claude, Ollama, or OpenRouter
180
181
  - **Model settings** — model name, temperature, max tokens
181
182
  - **VCS integration** — works out of the box with **GitLab**, **GitHub**, **Bitbucket**, and **Gitea**
182
183
  - **Review policy** — which files to include/exclude, review modes
@@ -220,7 +221,7 @@ jobs:
220
221
  with:
221
222
  fetch-depth: 0
222
223
 
223
- - uses: Nikita-Filonov/ai-review@v0.33.0
224
+ - uses: Nikita-Filonov/ai-review@v0.34.0
224
225
  with:
225
226
  review-command: ${{ inputs.review-command }}
226
227
  env:
@@ -298,7 +299,7 @@ AI Review does **not store**, **log**, or **transmit** your source code to any e
298
299
  provider** explicitly configured in your `.ai-review.yaml`.
299
300
 
300
301
  All data is sent **directly** from your CI/CD environment to the selected LLM API endpoint (e.g. OpenAI, Gemini,
301
- Claude). No intermediary servers or storage layers are involved.
302
+ Claude, OpenRouter). No intermediary servers or storage layers are involved.
302
303
 
303
304
  If you use **Ollama**, requests are sent to your **local or self-hosted Ollama runtime**
304
305
  (by default `http://localhost:11434`). This allows you to run reviews completely **offline**, keeping all data strictly
@@ -72,6 +72,10 @@ ai_review/clients/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
72
72
  ai_review/clients/openai/client.py,sha256=jY1XG_5GtNboNjkXu3UtuXFx5V9rD6UskK7VT0lOzP8,1816
73
73
  ai_review/clients/openai/schema.py,sha256=glxwMtBrDA6W0BQgH-ruKe0bKH3Ps1P-Y1-2jGdqaUM,764
74
74
  ai_review/clients/openai/types.py,sha256=4VRY45ihKjii8w0d5XLnUGnHuBSh9wRsOP6lmkseC0Q,267
75
+ ai_review/clients/openrouter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
+ ai_review/clients/openrouter/client.py,sha256=HfcfOuUs08w7Na344i197I_WrNNjyB1xSBzirxkPGO0,2088
77
+ ai_review/clients/openrouter/schema.py,sha256=U4c1wNhhAVLQ85J69IjR5g5tLly8KfrwGP7T5tsJPwI,799
78
+ ai_review/clients/openrouter/types.py,sha256=9CFUy52GnfjjLRufz7SwY_fnzhQnn8czLl-XLWBSKGc,303
75
79
  ai_review/libs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
80
  ai_review/libs/json.py,sha256=g-P5_pNUomQ-bGHCXASvPKj9Og0s9MaLFVEAkzqGp1A,350
77
81
  ai_review/libs/logger.py,sha256=LbXR2Zk1btJ-83I-vHee7cUETgT1mHToSsqEI_8uM0U,370
@@ -87,12 +91,13 @@ ai_review/libs/config/logger.py,sha256=oPmjpjf6EZwW7CgOjT8mOQdGnT98CLwXepiGB_ajZ
87
91
  ai_review/libs/config/prompt.py,sha256=jPBYvi75u6BUIHOZnXCg5CiL0XRONDfW5T4WaYlxPEE,6066
88
92
  ai_review/libs/config/review.py,sha256=jCfHfGfHs7Sc6H92hVUBd8bMRbLiIiThVmk6lFNlv40,1224
89
93
  ai_review/libs/config/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
- ai_review/libs/config/llm/base.py,sha256=ovvULFhfwH66_705D1O87ZGMeaQOZO7ZQhRUzzfzguU,2089
94
+ ai_review/libs/config/llm/base.py,sha256=yhRxizyRwwYH1swKeVzZaGAVm3I3nNME_y-h0iW5WTw,2374
91
95
  ai_review/libs/config/llm/claude.py,sha256=MoalXkBA6pEp01znS8ohTRopfea9RUcqhZX5lOIuek8,293
92
96
  ai_review/libs/config/llm/gemini.py,sha256=SKtlzsRuNWOlM9m3SFvcqOIjnml8lpPidp7FiGmIEz4,265
93
97
  ai_review/libs/config/llm/meta.py,sha256=cEcAHOwy-mQBKo9_KJrQe0I7qppq6h99lSmoWX4ElJI,195
94
98
  ai_review/libs/config/llm/ollama.py,sha256=M6aiPb5GvYvkiGcgHTsh9bOw5JsBLqmfSKoIbHCejrU,372
95
99
  ai_review/libs/config/llm/openai.py,sha256=jGVL4gJ2wIacoKeK9Zc9LCgY95TxdeYOThdglVPErFU,262
100
+ ai_review/libs/config/llm/openrouter.py,sha256=6G5fApCOv0fKRHCUpsuiPOcEdyUpDe5qiUUbHjA6TbE,337
96
101
  ai_review/libs/config/vcs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
97
102
  ai_review/libs/config/vcs/base.py,sha256=RJZhKysD-d8oYZQ2v1H74jyqdqtOCc8zZ0n9S4ovfHk,1471
98
103
  ai_review/libs/config/vcs/bitbucket.py,sha256=on5sQaE57kM_zSmqdDUNrttVtTPGOzqLHM5s7eFN7DA,275
@@ -101,7 +106,7 @@ ai_review/libs/config/vcs/github.py,sha256=hk-kuDLd8wecqtEb8PSqF7Yy_pkihplJhi6nB
101
106
  ai_review/libs/config/vcs/gitlab.py,sha256=ecYfU158VgVlM6P5mgZn8FOqk3Xt60xx7gUqT5e22a4,252
102
107
  ai_review/libs/config/vcs/pagination.py,sha256=S-XxWQYkIjhu3ffpHQ44d7UtRHH81fh9GaJ-xQXUUy4,175
103
108
  ai_review/libs/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
104
- ai_review/libs/constants/llm_provider.py,sha256=k7GzctIZ-TDsRlhTPbpGYgym_CO2YKVFp_oXG9dTBW0,143
109
+ ai_review/libs/constants/llm_provider.py,sha256=_ysUQFFspG1oCuCyRwdEE9kVlqLyP1hxL_Kc-tWV2F0,173
105
110
  ai_review/libs/constants/vcs_provider.py,sha256=7A30fTSs9GM_A4J9B84MNY78c7do0RaoKytuiRwdhDY,147
106
111
  ai_review/libs/diff/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
107
112
  ai_review/libs/diff/models.py,sha256=RT4YJboOPA-AjNJGRj_HIZaJLEmROOhOgMh1wIGpIwY,2344
@@ -157,7 +162,7 @@ ai_review/services/hook/constants.py,sha256=GthpBviiqcb3Oj5uhiJhyYGmWuwKgLlToC8p
157
162
  ai_review/services/hook/service.py,sha256=00qai06eFqkeKtIfqxlJkWTLCpO5d2wZzc2rFsPLKTs,10680
158
163
  ai_review/services/hook/types.py,sha256=RrGFCPwRkOOLAoHpGlkcnT6Cao0B4PyPMN-Wxj6N9Uo,2461
159
164
  ai_review/services/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
160
- ai_review/services/llm/factory.py,sha256=AszDqufYPaZdVVR99UZBEFvnGdOildBFQ9uVOiBI1Tc,876
165
+ ai_review/services/llm/factory.py,sha256=VF5xkGgYQFiDJJ0WNHUoXFtP4yWK6uN5qMTFPYXdTLU,1027
161
166
  ai_review/services/llm/types.py,sha256=OvbJWYRDThBgLhn9TWU0mliuanOW01CS3e8ermtuS-s,353
162
167
  ai_review/services/llm/claude/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
163
168
  ai_review/services/llm/claude/client.py,sha256=JJD0FWiXjCCpO7NW3vVoBMXhTQ9VBA4Q93QqkeQqON0,1082
@@ -167,6 +172,8 @@ ai_review/services/llm/ollama/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
167
172
  ai_review/services/llm/ollama/client.py,sha256=817nOQRsnaVqoY6LdO95l5JkRHkGvvS8TX7hezT2gqk,1479
168
173
  ai_review/services/llm/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
169
174
  ai_review/services/llm/openai/client.py,sha256=c3DWwLnwTheERdSGnMiQIbg5SaICouUAGClcQZSh1fE,1159
175
+ ai_review/services/llm/openrouter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
176
+ ai_review/services/llm/openrouter/client.py,sha256=wrUEETrer3XmNRtu3YzLIRNa_3ODTzsR_LU9kDkl-7I,1212
170
177
  ai_review/services/prompt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
171
178
  ai_review/services/prompt/adapter.py,sha256=9ntRxG5iON2vU54k8-BmLpiPCH5jRv2GGl01nmHj-SI,988
172
179
  ai_review/services/prompt/schema.py,sha256=ttla_lkvBQ-jfaZXzWSAAUrJROYRsozHHd5IoZc59zA,1324
@@ -232,7 +239,8 @@ ai_review/tests/fixtures/clients/gitea.py,sha256=WQLbOyFTqqtVQGHuLFgk9qANYS03eeC
232
239
  ai_review/tests/fixtures/clients/github.py,sha256=kC1L-nWZMn9O_uRfuT_B8R4sn8FRvISlBJMkRKaioS0,7814
233
240
  ai_review/tests/fixtures/clients/gitlab.py,sha256=AD6NJOJSw76hjAEiWewQ6Vu5g-cfQn0GTtdchuDBH9o,8042
234
241
  ai_review/tests/fixtures/clients/ollama.py,sha256=UUHDDPUraQAG8gBC-0UvftaK0BDYir5cJDlRKJymSQg,2109
235
- ai_review/tests/fixtures/clients/openai.py,sha256=UgfuRZWzl3X7ZVHMLKP4mZxNXVpcccitkc9tuUyffXE,2267
242
+ ai_review/tests/fixtures/clients/openai.py,sha256=PfnaYdrKwGiuAx8fnSJBZamAZEYJR1y8I4oHBj2SmU4,2291
243
+ ai_review/tests/fixtures/clients/openrouter.py,sha256=TWCojwXP0y0_dlzFMzJra4uXSQ3Dv5wZQnm_Hbvxodg,2532
236
244
  ai_review/tests/fixtures/libs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
237
245
  ai_review/tests/fixtures/libs/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
238
246
  ai_review/tests/fixtures/libs/llm/output_json_parser.py,sha256=Eu0tCK5cD1d9X1829ruahih7jQoehGozIEugS71AAA8,275
@@ -291,6 +299,9 @@ ai_review/tests/suites/clients/ollama/test_schema.py,sha256=A93wCmxwGdvudfbA97VC
291
299
  ai_review/tests/suites/clients/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
292
300
  ai_review/tests/suites/clients/openai/test_client.py,sha256=6Wsxw6-6Uk0uPYFkzpWSwsxfCYUZhT3UYznayo-xlPI,404
293
301
  ai_review/tests/suites/clients/openai/test_schema.py,sha256=x1tamS4GC9pOTpjieKDbK2D73CVV4BkATppytwMevLo,1599
302
+ ai_review/tests/suites/clients/openrouter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
303
+ ai_review/tests/suites/clients/openrouter/test_client.py,sha256=WNH0p1Bl5J6zLKB1gSQ9smQMRTOo5-U-A60iJ0n4_DI,444
304
+ ai_review/tests/suites/clients/openrouter/test_schema.py,sha256=9wt8-lR1u2KGvd6Iget_Yy-r33BllYLA-3AKe-S2E-c,1731
294
305
  ai_review/tests/suites/libs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
295
306
  ai_review/tests/suites/libs/test_json.py,sha256=q_tvFcL9EaN5jr48Ed_mxMEmW6xXS0xlZGqSooPZBa4,713
296
307
  ai_review/tests/suites/libs/asynchronous/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -321,7 +332,7 @@ ai_review/tests/suites/services/diff/test_tools.py,sha256=vsOSSIDZKkuD8dMCoBBEBt
321
332
  ai_review/tests/suites/services/hook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
322
333
  ai_review/tests/suites/services/hook/test_service.py,sha256=TjNU2xiOQfUZZa8M3L2eHbtTwxse_B7QNn2h4118z1U,6637
323
334
  ai_review/tests/suites/services/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
324
- ai_review/tests/suites/services/llm/test_factory.py,sha256=Lp37aXM08fHaLzgwRBw5xZEDZkPDNhJ4qwjtEwGENv8,1394
335
+ ai_review/tests/suites/services/llm/test_factory.py,sha256=e3pND1Rq4lIXpm_R8J1mRiEc7moEqJ9cGU3rK5tk4Bo,1685
325
336
  ai_review/tests/suites/services/llm/claude/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
326
337
  ai_review/tests/suites/services/llm/claude/test_client.py,sha256=ymIeuIax0Bp_CuXBSApK1RDl1JmbGc97uzXZToQOZO8,761
327
338
  ai_review/tests/suites/services/llm/gemini/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -330,17 +341,19 @@ ai_review/tests/suites/services/llm/ollama/__init__.py,sha256=47DEQpj8HBSa-_TImW
330
341
  ai_review/tests/suites/services/llm/ollama/test_client.py,sha256=Eu4OERB00SJwCKznyOCyqSFTDBp9J2Lw-BcW7sPJQM4,760
331
342
  ai_review/tests/suites/services/llm/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
332
343
  ai_review/tests/suites/services/llm/openai/test_client.py,sha256=yzIL8GYHyX9iLKIlaF__87aue9w0cr66feoMaCv5gms,761
344
+ ai_review/tests/suites/services/llm/openrouter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
345
+ ai_review/tests/suites/services/llm/openrouter/test_client.py,sha256=9YRwhkgeRDdRi_EMFh_T0u4wgEFj2AMgAiusrYWzeEc,813
333
346
  ai_review/tests/suites/services/prompt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
334
347
  ai_review/tests/suites/services/prompt/test_adapter.py,sha256=3BhaGW4G-72dN_ajp5rJst39k1VNiKZq5FUB9LGPNVQ,2168
335
348
  ai_review/tests/suites/services/prompt/test_schema.py,sha256=rm2__LA2_4qQwSmNAZ_Wnpy11T3yYRkYUkRUrqxUQKE,5421
336
349
  ai_review/tests/suites/services/prompt/test_service.py,sha256=jfpmM--eZH43NylwltX-ijmF58ZJ4WA83kmGshUbJfs,8312
337
350
  ai_review/tests/suites/services/prompt/test_tools.py,sha256=SmweFvrpxd-3RO5v18vCl7zonEqFE1n4eqHH7-6auYM,4511
338
351
  ai_review/tests/suites/services/review/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
339
- ai_review/tests/suites/services/review/test_service.py,sha256=9XBywDoVTzLXlfqMmrxwJbPoj8QoGFW5wDwirqu2UHI,4206
352
+ ai_review/tests/suites/services/review/test_service.py,sha256=8LYlLk4sX6R86qS9kGRPO7EJcUlXr0L1JLczADpS-nE,4252
340
353
  ai_review/tests/suites/services/review/gateway/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
341
- ai_review/tests/suites/services/review/gateway/test_review_comment_gateway.py,sha256=eueGnKY3YcLvLQ8hSdhKQuVfOlFwxP82cwgEHKyRPiQ,8831
342
- ai_review/tests/suites/services/review/gateway/test_review_dry_run_comment_gateway.py,sha256=YGSCZRyyV9BJaKpI66viHYZdM-GtuODJt4ZzEW1-29g,3862
343
- ai_review/tests/suites/services/review/gateway/test_review_llm_gateway.py,sha256=m4HKvqDQYNesY5F2wuOMeHqXwQe99K4K_depdyTs37Q,2508
354
+ ai_review/tests/suites/services/review/gateway/test_review_comment_gateway.py,sha256=-EG6bWwFX46QKROYmIxPw0WLQsYkn0munTD2Wxykm6Q,8946
355
+ ai_review/tests/suites/services/review/gateway/test_review_dry_run_comment_gateway.py,sha256=UzRo41pI0vBcHcr7u2hi8zH2Msd25MXhxLWXodsaNLA,3977
356
+ ai_review/tests/suites/services/review/gateway/test_review_llm_gateway.py,sha256=Wy9nYO_YNlBNKC2w4TvXvPqtiRUSvRfyZ7cGS-LD7Y8,2554
344
357
  ai_review/tests/suites/services/review/internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
345
358
  ai_review/tests/suites/services/review/internal/inline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
346
359
  ai_review/tests/suites/services/review/internal/inline/test_schema.py,sha256=2SGF8yNHsNv8lwG-MUlgO3RT4juTt0PQSl10qNhSt7o,2377
@@ -376,9 +389,9 @@ ai_review/tests/suites/services/vcs/github/test_client.py,sha256=mNt1bA6aVU3REsJ
376
389
  ai_review/tests/suites/services/vcs/gitlab/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
377
390
  ai_review/tests/suites/services/vcs/gitlab/test_adapter.py,sha256=BYBP2g1AKF_jCSJYJj16pW7M_6PprwD9reYEpdw3StU,4340
378
391
  ai_review/tests/suites/services/vcs/gitlab/test_client.py,sha256=dnI-YxYADmVF2GS9rp6-JPkcqsn4sN8Fjbe4MkeYMaE,8476
379
- xai_review-0.33.0.dist-info/licenses/LICENSE,sha256=p-v8m7Kmz4KKc7PcvsGiGEmCw9AiSXY4_ylOPy_u--Y,11343
380
- xai_review-0.33.0.dist-info/METADATA,sha256=olP2J_qC--KQKEyDvq1JNIUtqgd029Lli6fiI6zKAc0,12689
381
- xai_review-0.33.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
382
- xai_review-0.33.0.dist-info/entry_points.txt,sha256=JyC5URanMi5io5P_PXQf7H_I1OGIpk5cZQhaPQ0g4Zs,53
383
- xai_review-0.33.0.dist-info/top_level.txt,sha256=sTsZbfzLoqvRZKdKa-BcxWvjlHdrpbeJ6DrGY0EuR0E,10
384
- xai_review-0.33.0.dist-info/RECORD,,
392
+ xai_review-0.34.0.dist-info/licenses/LICENSE,sha256=p-v8m7Kmz4KKc7PcvsGiGEmCw9AiSXY4_ylOPy_u--Y,11343
393
+ xai_review-0.34.0.dist-info/METADATA,sha256=Zpuk0sHxSQKsA84Xdv4Ba1V6Seghvxd_XCh48f5BKDw,12911
394
+ xai_review-0.34.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
395
+ xai_review-0.34.0.dist-info/entry_points.txt,sha256=JyC5URanMi5io5P_PXQf7H_I1OGIpk5cZQhaPQ0g4Zs,53
396
+ xai_review-0.34.0.dist-info/top_level.txt,sha256=sTsZbfzLoqvRZKdKa-BcxWvjlHdrpbeJ6DrGY0EuR0E,10
397
+ xai_review-0.34.0.dist-info/RECORD,,