code-review-ai-cli 2.2.2__tar.gz → 2.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/PKG-INFO +16 -1
  2. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/README.md +11 -0
  3. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/code_review_ai_cli.egg-info/PKG-INFO +16 -1
  4. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/code_review_ai_cli.egg-info/SOURCES.txt +3 -1
  5. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/code_review_ai_cli.egg-info/requires.txt +4 -0
  6. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/pyproject.toml +5 -1
  7. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/src/__init__.py +3 -0
  8. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/src/ai_review.py +13 -0
  9. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/src/config.py +3 -0
  10. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/src/context_extractor.py +3 -0
  11. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/src/formatter.py +3 -0
  12. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/src/git_utils.py +3 -0
  13. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/src/llm_client.py +18 -0
  14. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/src/prompt_utils.py +3 -0
  15. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/src/tfs_client.py +3 -0
  16. code_review_ai_cli-2.2.3/src/token_estimator.py +98 -0
  17. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/tests/test_ai_review.py +3 -0
  18. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/tests/test_config.py +389 -386
  19. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/tests/test_context_extractor.py +3 -0
  20. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/tests/test_formatter.py +3 -0
  21. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/tests/test_git_utils.py +159 -156
  22. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/tests/test_llm_client.py +3 -0
  23. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/tests/test_prompt_utils.py +3 -0
  24. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/tests/test_tfs_client.py +3 -0
  25. code_review_ai_cli-2.2.3/tests/test_token_estimator.py +102 -0
  26. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/code_review_ai_cli.egg-info/dependency_links.txt +0 -0
  27. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/code_review_ai_cli.egg-info/entry_points.txt +0 -0
  28. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/code_review_ai_cli.egg-info/top_level.txt +0 -0
  29. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/setup.cfg +0 -0
  30. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/src/prompts/config.yaml.template +0 -0
  31. {code_review_ai_cli-2.2.2 → code_review_ai_cli-2.2.3}/src/prompts/review_prompt.md.template +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-review-ai-cli
3
- Version: 2.2.2
3
+ Version: 2.2.3
4
4
  Summary: Automated AI-powered code review CLI for Azure DevOps / TFS Pull Requests
5
5
  License: MIT
6
6
  Keywords: code-review,ai,azure-devops,tfs,pull-request,llm
@@ -17,6 +17,8 @@ Requires-Python: >=3.10
17
17
  Description-Content-Type: text/markdown
18
18
  Requires-Dist: requests>=2.28.0
19
19
  Requires-Dist: PyYAML>=6.0
20
+ Requires-Dist: litellm>=1.40.0
21
+ Requires-Dist: tiktoken>=0.7.0
20
22
  Provides-Extra: bedrock
21
23
  Requires-Dist: boto3>=1.34.0; extra == "bedrock"
22
24
  Provides-Extra: openai
@@ -29,6 +31,8 @@ Provides-Extra: tree-sitter
29
31
  Requires-Dist: tree-sitter-languages>=1.10.2; extra == "tree-sitter"
30
32
  Provides-Extra: all
31
33
  Requires-Dist: boto3>=1.34.0; extra == "all"
34
+ Requires-Dist: litellm>=1.40.0; extra == "all"
35
+ Requires-Dist: tiktoken>=0.7.0; extra == "all"
32
36
  Requires-Dist: openai>=1.0.0; extra == "all"
33
37
  Requires-Dist: google-generativeai>=0.3.0; extra == "all"
34
38
  Requires-Dist: anthropic>=0.18.0; extra == "all"
@@ -51,6 +55,7 @@ AI Code Review is an automated AI-powered code review CLI, compatible with vario
51
55
  - Configuration exclusively via `config.yaml`
52
56
  - Providers LLM: OpenAI, Azure OpenAI, Gemini, Claude, Ollama, GitHub Copilot, AWS Bedrock
53
57
  - Diff filtering by **excluded path prefixes** (`excluded_paths`) and/or **file extensions** (`file_extensions_filter`)
58
+ - Prompt token estimation with LiteLLM (`Total Prompt`)
54
59
 
55
60
  ## Documentation
56
61
 
@@ -63,6 +68,13 @@ AI Code Review is an automated AI-powered code review CLI, compatible with vario
63
68
 
64
69
  ![Review Output Format Example](/imgs/review_output.png)
65
70
 
71
+ During a PR review, the terminal also displays `Total Prompt`, an estimate of
72
+ the input tokens in the complete payload sent to the configured provider. The
73
+ provider comes from `llm.provider` and the model comes from `llm.model` or its
74
+ default one. If LiteLLM cannot resolve the provider/model combination,
75
+ the tool tries the model without a provider prefix and then uses local
76
+ `tiktoken` as a final fallback.
77
+
66
78
  ## Installation
67
79
 
68
80
  Install from PyPI:
@@ -144,3 +156,6 @@ output:
144
156
  - `AI Review: PR (Dry-Run)`
145
157
  - `AI Review: List Active PRs`
146
158
  - `AI Review: Interactive Mode`
159
+
160
+ ## Licence
161
+ This project is licensed under the Apache Licence 2.0 — see the [LICENSE](/License).
@@ -11,6 +11,7 @@ AI Code Review is an automated AI-powered code review CLI, compatible with vario
11
11
  - Configuration exclusively via `config.yaml`
12
12
  - Providers LLM: OpenAI, Azure OpenAI, Gemini, Claude, Ollama, GitHub Copilot, AWS Bedrock
13
13
  - Diff filtering by **excluded path prefixes** (`excluded_paths`) and/or **file extensions** (`file_extensions_filter`)
14
+ - Prompt token estimation with LiteLLM (`Total Prompt`)
14
15
 
15
16
  ## Documentation
16
17
 
@@ -23,6 +24,13 @@ AI Code Review is an automated AI-powered code review CLI, compatible with vario
23
24
 
24
25
  ![Review Output Format Example](/imgs/review_output.png)
25
26
 
27
+ During a PR review, the terminal also displays `Total Prompt`, an estimate of
28
+ the input tokens in the complete payload sent to the configured provider. The
29
+ provider comes from `llm.provider` and the model comes from `llm.model` or its
30
+ default one. If LiteLLM cannot resolve the provider/model combination,
31
+ the tool tries the model without a provider prefix and then uses local
32
+ `tiktoken` as a final fallback.
33
+
26
34
  ## Installation
27
35
 
28
36
  Install from PyPI:
@@ -104,3 +112,6 @@ output:
104
112
  - `AI Review: PR (Dry-Run)`
105
113
  - `AI Review: List Active PRs`
106
114
  - `AI Review: Interactive Mode`
115
+
116
+ ## Licence
117
+ This project is licensed under the Apache Licence 2.0 — see the [LICENSE](/License).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-review-ai-cli
3
- Version: 2.2.2
3
+ Version: 2.2.3
4
4
  Summary: Automated AI-powered code review CLI for Azure DevOps / TFS Pull Requests
5
5
  License: MIT
6
6
  Keywords: code-review,ai,azure-devops,tfs,pull-request,llm
@@ -17,6 +17,8 @@ Requires-Python: >=3.10
17
17
  Description-Content-Type: text/markdown
18
18
  Requires-Dist: requests>=2.28.0
19
19
  Requires-Dist: PyYAML>=6.0
20
+ Requires-Dist: litellm>=1.40.0
21
+ Requires-Dist: tiktoken>=0.7.0
20
22
  Provides-Extra: bedrock
21
23
  Requires-Dist: boto3>=1.34.0; extra == "bedrock"
22
24
  Provides-Extra: openai
@@ -29,6 +31,8 @@ Provides-Extra: tree-sitter
29
31
  Requires-Dist: tree-sitter-languages>=1.10.2; extra == "tree-sitter"
30
32
  Provides-Extra: all
31
33
  Requires-Dist: boto3>=1.34.0; extra == "all"
34
+ Requires-Dist: litellm>=1.40.0; extra == "all"
35
+ Requires-Dist: tiktoken>=0.7.0; extra == "all"
32
36
  Requires-Dist: openai>=1.0.0; extra == "all"
33
37
  Requires-Dist: google-generativeai>=0.3.0; extra == "all"
34
38
  Requires-Dist: anthropic>=0.18.0; extra == "all"
@@ -51,6 +55,7 @@ AI Code Review is an automated AI-powered code review CLI, compatible with vario
51
55
  - Configuration exclusively via `config.yaml`
52
56
  - Providers LLM: OpenAI, Azure OpenAI, Gemini, Claude, Ollama, GitHub Copilot, AWS Bedrock
53
57
  - Diff filtering by **excluded path prefixes** (`excluded_paths`) and/or **file extensions** (`file_extensions_filter`)
58
+ - Prompt token estimation with LiteLLM (`Total Prompt`)
54
59
 
55
60
  ## Documentation
56
61
 
@@ -63,6 +68,13 @@ AI Code Review is an automated AI-powered code review CLI, compatible with vario
63
68
 
64
69
  ![Review Output Format Example](/imgs/review_output.png)
65
70
 
71
+ During a PR review, the terminal also displays `Total Prompt`, an estimate of
72
+ the input tokens in the complete payload sent to the configured provider. The
73
+ provider comes from `llm.provider` and the model comes from `llm.model` or its
74
+ default one. If LiteLLM cannot resolve the provider/model combination,
75
+ the tool tries the model without a provider prefix and then uses local
76
+ `tiktoken` as a final fallback.
77
+
66
78
  ## Installation
67
79
 
68
80
  Install from PyPI:
@@ -144,3 +156,6 @@ output:
144
156
  - `AI Review: PR (Dry-Run)`
145
157
  - `AI Review: List Active PRs`
146
158
  - `AI Review: Interactive Mode`
159
+
160
+ ## Licence
161
+ This project is licensed under the Apache Licence 2.0 — see the [LICENSE](/License).
@@ -15,6 +15,7 @@ src/git_utils.py
15
15
  src/llm_client.py
16
16
  src/prompt_utils.py
17
17
  src/tfs_client.py
18
+ src/token_estimator.py
18
19
  src/prompts/config.yaml.template
19
20
  src/prompts/review_prompt.md.template
20
21
  tests/test_ai_review.py
@@ -24,4 +25,5 @@ tests/test_formatter.py
24
25
  tests/test_git_utils.py
25
26
  tests/test_llm_client.py
26
27
  tests/test_prompt_utils.py
27
- tests/test_tfs_client.py
28
+ tests/test_tfs_client.py
29
+ tests/test_token_estimator.py
@@ -1,8 +1,12 @@
1
1
  requests>=2.28.0
2
2
  PyYAML>=6.0
3
+ litellm>=1.40.0
4
+ tiktoken>=0.7.0
3
5
 
4
6
  [all]
5
7
  boto3>=1.34.0
8
+ litellm>=1.40.0
9
+ tiktoken>=0.7.0
6
10
  openai>=1.0.0
7
11
  google-generativeai>=0.3.0
8
12
  anthropic>=0.18.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code-review-ai-cli"
7
- version = "2.2.2"
7
+ version = "2.2.3"
8
8
  description = "Automated AI-powered code review CLI for Azure DevOps / TFS Pull Requests"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -24,6 +24,8 @@ classifiers = [
24
24
  dependencies = [
25
25
  "requests>=2.28.0",
26
26
  "PyYAML>=6.0",
27
+ "litellm>=1.40.0",
28
+ "tiktoken>=0.7.0",
27
29
  ]
28
30
 
29
31
  [project.optional-dependencies]
@@ -34,6 +36,8 @@ claude = ["anthropic>=0.18.0"]
34
36
  tree-sitter = ["tree-sitter-languages>=1.10.2"]
35
37
  all = [
36
38
  "boto3>=1.34.0",
39
+ "litellm>=1.40.0",
40
+ "tiktoken>=0.7.0",
37
41
  "openai>=1.0.0",
38
42
  "google-generativeai>=0.3.0",
39
43
  "anthropic>=0.18.0",
@@ -1,3 +1,6 @@
1
+ # License: Apache-2.0
2
+ # Copyright 2026 Sérgio Sereno
3
+
1
4
  # AI Code Review - Internal modules
2
5
  from importlib.metadata import version, PackageNotFoundError
3
6
 
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env python3
2
+ # License: Apache-2.0
3
+ # Copyright 2026 Sérgio Sereno
4
+
2
5
  """
3
6
  AI Code Review - Main Script
4
7
  ==============================
@@ -516,6 +519,16 @@ def run_pr_review_workflow(args: argparse.Namespace, config: ReviewConfig,
516
519
  elapsed = time.time() - start_time
517
520
  progress.stop(formatter.format_success(f"Review completed in {elapsed:.1f}s"))
518
521
 
522
+ token_estimate = getattr(llm, "last_token_estimate", None)
523
+ if token_estimate is not None and token_estimate.total_prompt_tokens is not None:
524
+ print(formatter.format_info(
525
+ f"Total Prompt: {token_estimate.total_prompt_tokens} tokens"
526
+ ))
527
+ else:
528
+ print(formatter.format_warning(
529
+ "Total Prompt: unavailable (LiteLLM and local tiktoken could not count the model)"
530
+ ))
531
+
519
532
  # --- Show structured comments preview ---
520
533
  print(formatter.format_structured_comments(
521
534
  structured_comments,
@@ -1,3 +1,6 @@
1
+ # License: Apache-2.0
2
+ # Copyright 2026 Sérgio Sereno
3
+
1
4
  """
2
5
  Configuration Module - AI Code Review
3
6
  =======================================
@@ -1,3 +1,6 @@
1
+ # License: Apache-2.0
2
+ # Copyright 2026 Sérgio Sereno
3
+
1
4
  """
2
5
  Context Extractor Module - AI Code Review
3
6
  ==========================================
@@ -1,3 +1,6 @@
1
+ # License: Apache-2.0
2
+ # Copyright 2026 Sérgio Sereno
3
+
1
4
  """
2
5
  Formatting Module - AI Code Review
3
6
  ====================================
@@ -1,3 +1,6 @@
1
+ # License: Apache-2.0
2
+ # Copyright 2026 Sérgio Sereno
3
+
1
4
  """
2
5
  Git Utilities Module - AI Code Review
3
6
  ========================================
@@ -1,3 +1,6 @@
1
+ # License: Apache-2.0
2
+ # Copyright 2026 Sérgio Sereno
3
+
1
4
  """
2
5
  LLM Client Module - AI Code Review
3
6
  =====================================
@@ -18,6 +21,7 @@ import os
18
21
 
19
22
  from .config import ReviewConfig
20
23
  from .prompt_utils import detect_langs, filter_prompt_by_langs
24
+ from .token_estimator import TokenEstimate, TokenEstimator
21
25
 
22
26
 
23
27
  class LLMError(Exception):
@@ -317,6 +321,11 @@ class LLMClient:
317
321
 
318
322
  def __init__(self, config: ReviewConfig):
319
323
  self.config = config
324
+ self.last_token_estimate = TokenEstimate(
325
+ total_prompt_tokens=None,
326
+ model=config.get_effective_model(),
327
+ error="No review has been performed yet.",
328
+ )
320
329
 
321
330
  def _dump_prompt_debug(self, system_prompt: str, user_message: str) -> None:
322
331
  """
@@ -420,6 +429,15 @@ class LLMClient:
420
429
  user_message = build_user_message(diff, files_summary, merged_context)
421
430
  self._dump_prompt_debug(system_prompt, user_message)
422
431
 
432
+ self.last_token_estimate = TokenEstimator().estimate(
433
+ provider=self.config.llm_provider,
434
+ model=self.config.get_effective_model(),
435
+ messages=[
436
+ {"role": "system", "content": system_prompt},
437
+ {"role": "user", "content": user_message},
438
+ ],
439
+ )
440
+
423
441
  provider = self.config.llm_provider.lower()
424
442
 
425
443
  if provider == "openai":
@@ -1,3 +1,6 @@
1
+ # License: Apache-2.0
2
+ # Copyright 2026 Sérgio Sereno
3
+
1
4
  """
2
5
  Prompt Utilities Module - AI Code Review
3
6
  ========================================
@@ -1,3 +1,6 @@
1
+ # License: Apache-2.0
2
+ # Copyright 2026 Sérgio Sereno
3
+
1
4
  """
2
5
  TFS/Azure DevOps Module - AI Code Review
3
6
  ==========================================
@@ -0,0 +1,98 @@
1
+ # License: Apache-2.0
2
+ # Copyright 2026 Sérgio Sereno
3
+
4
+ from __future__ import annotations
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class TokenEstimate:
10
+ """Result of estimating the input tokens for an LLM request."""
11
+
12
+ total_prompt_tokens: int | None
13
+ model: str
14
+ error: str = ""
15
+
16
+
17
+ class TokenEstimator:
18
+ """Estimates prompt tokens with LiteLLM for configured providers."""
19
+
20
+ _PROVIDER_PREFIXES = {
21
+ "azure_openai": "azure",
22
+ "gemini": "gemini",
23
+ "claude": "anthropic",
24
+ "ollama": "ollama",
25
+ "copilot": "github",
26
+ "bedrock": "bedrock",
27
+ }
28
+
29
+ def estimate(
30
+ self,
31
+ provider: str,
32
+ model: str,
33
+ messages: list[dict[str, str]],
34
+ ) -> TokenEstimate:
35
+ """Estimates input tokens, falling back to the unqualified model.
36
+
37
+ Args:
38
+ provider: Provider configured for the LLM request.
39
+ model: Effective model name, including configured defaults.
40
+ messages: Complete chat payload sent to the provider.
41
+
42
+ Returns:
43
+ A token estimate, or an unavailable result when LiteLLM cannot
44
+ resolve either the provider-qualified or raw model name.
45
+ """
46
+ qualified_model = self._qualified_model(provider, model)
47
+ errors: list[str] = []
48
+
49
+ for candidate in dict.fromkeys((qualified_model, model)):
50
+ try:
51
+ token_count = self._count(candidate, messages)
52
+ return TokenEstimate(token_count, candidate)
53
+ except Exception as exc:
54
+ errors.append(f"{candidate}: {exc}")
55
+
56
+ try:
57
+ token_count = self._count_locally(model, messages)
58
+ return TokenEstimate(token_count, "local/tiktoken")
59
+ except Exception as exc:
60
+ errors.append(f"local/tiktoken: {exc}")
61
+
62
+ return TokenEstimate(
63
+ total_prompt_tokens=None,
64
+ model=qualified_model,
65
+ error="; ".join(errors),
66
+ )
67
+
68
+ @classmethod
69
+ def _qualified_model(cls, provider: str, model: str) -> str:
70
+ """Builds the LiteLLM model name for a configured provider."""
71
+ normalized_provider = provider.lower().strip()
72
+ prefix = cls._PROVIDER_PREFIXES.get(normalized_provider)
73
+ if not prefix:
74
+ return model
75
+ return f"{prefix}/{model}"
76
+
77
+ @staticmethod
78
+ def _count(model: str, messages: list[dict[str, str]]) -> int:
79
+ """Calls LiteLLM lazily so importing the CLI remains lightweight."""
80
+ from litellm import token_counter
81
+
82
+ return int(token_counter(model=model, messages=messages))
83
+
84
+ @staticmethod
85
+ def _count_locally(model: str, messages: list[dict[str, str]]) -> int:
86
+ """Counts message text locally when LiteLLM has no model support."""
87
+ import tiktoken
88
+
89
+ try:
90
+ encoding = tiktoken.encoding_for_model(model)
91
+ except KeyError:
92
+ encoding = tiktoken.get_encoding("cl100k_base")
93
+
94
+ serialized_messages = "\n".join(
95
+ f"{message.get('role', '')}: {message.get('content', '')}"
96
+ for message in messages
97
+ )
98
+ return len(encoding.encode(serialized_messages))
@@ -1,3 +1,6 @@
1
+ # License: Apache-2.0
2
+ # Copyright 2026 Sérgio Sereno
3
+
1
4
  """Tests for the AI review CLI workflow."""
2
5
 
3
6
  from __future__ import annotations