devgen-cli 0.2.9__tar.gz → 0.3.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.
Files changed (59) hide show
  1. {devgen_cli-0.2.9/devgen_cli.egg-info → devgen_cli-0.3.0}/PKG-INFO +7 -7
  2. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/modules/commit_generator.py +15 -7
  3. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/modules/diff_builder.py +11 -10
  4. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/modules/git_ops.py +4 -3
  5. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/providers/base.py +43 -20
  6. devgen_cli-0.3.0/devgen/providers/gemini.py +25 -0
  7. devgen_cli-0.3.0/devgen/templates/commit/commit_message.tpl +29 -0
  8. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/utils.py +5 -2
  9. {devgen_cli-0.2.9 → devgen_cli-0.3.0/devgen_cli.egg-info}/PKG-INFO +7 -7
  10. devgen_cli-0.3.0/devgen_cli.egg-info/requires.txt +9 -0
  11. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/pyproject.toml +9 -9
  12. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/setup.py +1 -1
  13. devgen_cli-0.2.9/devgen/providers/gemini.py +0 -63
  14. devgen_cli-0.2.9/devgen/templates/commit/commit_message.tpl +0 -14
  15. devgen_cli-0.2.9/devgen_cli.egg-info/requires.txt +0 -9
  16. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/LICENSE +0 -0
  17. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/MANIFEST.in +0 -0
  18. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/README.md +0 -0
  19. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/__init__.py +0 -0
  20. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/ai.py +0 -0
  21. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/cli/__init__.py +0 -0
  22. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/cli/changelog.py +0 -0
  23. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/cli/commit.py +0 -0
  24. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/cli/config.py +0 -0
  25. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/cli/gitignore.py +0 -0
  26. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/cli/license.py +0 -0
  27. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/cli/main.py +0 -0
  28. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/cli/release.py +0 -0
  29. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/cli/setup.py +0 -0
  30. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/modules/__init__.py +0 -0
  31. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/modules/changelog_generator.py +0 -0
  32. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/modules/changelog_sections.py +0 -0
  33. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/modules/gitignore_generator.py +0 -0
  34. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/modules/license_generator.py +0 -0
  35. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/modules/release_note_generator.py +0 -0
  36. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/providers/__init__.py +0 -0
  37. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/providers/anthropic.py +0 -0
  38. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/providers/huggingface.py +0 -0
  39. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/providers/ollama.py +0 -0
  40. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/providers/openai.py +0 -0
  41. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/providers/openrouter.py +0 -0
  42. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/templates/licenses/agpl-3.0.json +0 -0
  43. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/templates/licenses/apache-2.0.json +0 -0
  44. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/templates/licenses/bsd-2-clause.json +0 -0
  45. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/templates/licenses/bsd-3-clause.json +0 -0
  46. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/templates/licenses/bsl-1.0.json +0 -0
  47. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/templates/licenses/cc0-1.0.json +0 -0
  48. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/templates/licenses/epl-2.0.json +0 -0
  49. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/templates/licenses/gpl-2.0.json +0 -0
  50. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/templates/licenses/gpl-3.0.json +0 -0
  51. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/templates/licenses/lgpl-2.1.json +0 -0
  52. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/templates/licenses/mit.json +0 -0
  53. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/templates/licenses/mpl-2.0.json +0 -0
  54. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen/templates/licenses/unlicense.json +0 -0
  55. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen_cli.egg-info/SOURCES.txt +0 -0
  56. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen_cli.egg-info/dependency_links.txt +0 -0
  57. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen_cli.egg-info/entry_points.txt +0 -0
  58. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/devgen_cli.egg-info/top_level.txt +0 -0
  59. {devgen_cli-0.2.9 → devgen_cli-0.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devgen-cli
3
- Version: 0.2.9
3
+ Version: 0.3.0
4
4
  Summary: A collection of developer tools
5
5
  Home-page: https://github.com/S4NKALP/devgen
6
6
  Author: Sankalp Tharu
@@ -20,15 +20,15 @@ Classifier: Programming Language :: Python :: 3.10
20
20
  Requires-Python: >=3.10
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
- Requires-Dist: anthropic>=0.75.0
24
- Requires-Dist: google-genai>=0.1.0
25
- Requires-Dist: openai>=2.11.0
23
+ Requires-Dist: anthropic>=0.116.0
24
+ Requires-Dist: google-genai>=2.10.0
25
+ Requires-Dist: openai>=2.44.0
26
26
  Requires-Dist: pyyaml>=6.0.3
27
27
  Requires-Dist: questionary>=2.1.1
28
- Requires-Dist: requests>=2.32.5
29
- Requires-Dist: rich>=14.2.0
28
+ Requires-Dist: requests>=2.34.2
29
+ Requires-Dist: rich>=15.0.0
30
30
  Requires-Dist: toml>=0.10.2
31
- Requires-Dist: typer>=0.20.0
31
+ Requires-Dist: typer>=0.26.8
32
32
  Dynamic: author
33
33
  Dynamic: home-page
34
34
  Dynamic: license-file
@@ -33,6 +33,8 @@ from devgen.utils import (
33
33
  extract_commit_messages,
34
34
  get_commit_dry_run_path,
35
35
  is_file_recent,
36
+ is_token_limit_error,
37
+ format_token_limit_error,
36
38
  load_template,
37
39
  render_template,
38
40
  sanitize_ai_commit_message,
@@ -241,9 +243,10 @@ class CommitEngine:
241
243
  if not self._process_group(group, group_files, cache_map):
242
244
  failed.append(group)
243
245
  if len(groups) > 1 and i < len(groups) - 1:
244
- import time
246
+ if self.provider != "ollama":
247
+ import time
245
248
 
246
- time.sleep(15) # Conservative 4 RPM guard for free-tier
249
+ time.sleep(15) # Conservative 4 RPM guard for free-tier
247
250
  return failed
248
251
 
249
252
  def _process_group(
@@ -265,7 +268,7 @@ class CommitEngine:
265
268
  return self._apply_message(group, cache[group], files)
266
269
 
267
270
  try:
268
- manifest_context = ManifestInspector.summary()
271
+ manifest_context = ManifestInspector.summary(cwd=self.git.cwd)
269
272
  if manifest_context:
270
273
  self.logger.info("Including manifest context in prompt")
271
274
  with self.console.status(
@@ -279,10 +282,15 @@ class CommitEngine:
279
282
  )
280
283
  message = sanitize_ai_commit_message(raw)
281
284
  except Exception as e:
282
- self.logger.error(
283
- f"Failed to generate message for group {group!r}: {e}",
284
- exc_info=True,
285
- )
285
+ if is_token_limit_error(e):
286
+ msg = format_token_limit_error(self.provider, e, group=group)
287
+ self.console.print(f"[bold red]{msg}[/bold red]")
288
+ self.logger.error(msg)
289
+ else:
290
+ self.logger.error(
291
+ f"Failed to generate message for group {group!r}: {e}",
292
+ exc_info=True,
293
+ )
286
294
  self.git.reset(files)
287
295
  return False
288
296
 
@@ -143,13 +143,14 @@ class ManifestInspector:
143
143
  """
144
144
 
145
145
  @staticmethod
146
- def summary() -> str:
146
+ def summary(cwd: Optional[Path] = None) -> str:
147
+ cwd = cwd or get_git_root() or Path(".")
147
148
  lines = [
148
149
  s
149
150
  for s in (
150
- ManifestInspector._pyproject(),
151
- ManifestInspector._package_json(),
152
- ManifestInspector._requirements(),
151
+ ManifestInspector._pyproject(cwd),
152
+ ManifestInspector._package_json(cwd),
153
+ ManifestInspector._requirements(cwd),
153
154
  )
154
155
  if s
155
156
  ]
@@ -164,8 +165,8 @@ class ManifestInspector:
164
165
  return re.split(r"[><=!~\[]", spec, 1)[0].strip()
165
166
 
166
167
  @staticmethod
167
- def _pyproject() -> Optional[str]:
168
- path = Path("pyproject.toml")
168
+ def _pyproject(cwd: Path) -> Optional[str]:
169
+ path = cwd / "pyproject.toml"
169
170
  if not path.exists():
170
171
  return None
171
172
  try:
@@ -186,8 +187,8 @@ class ManifestInspector:
186
187
  return f"[pyproject.toml] {' '.join(parts)}" if parts else None
187
188
 
188
189
  @staticmethod
189
- def _package_json() -> Optional[str]:
190
- path = Path("package.json")
190
+ def _package_json(cwd: Path) -> Optional[str]:
191
+ path = cwd / "package.json"
191
192
  if not path.exists():
192
193
  return None
193
194
  try:
@@ -203,8 +204,8 @@ class ManifestInspector:
203
204
  return f"[package.json] {' '.join(parts)}" if parts else None
204
205
 
205
206
  @staticmethod
206
- def _requirements() -> Optional[str]:
207
- path = Path("requirements.txt")
207
+ def _requirements(cwd: Path) -> Optional[str]:
208
+ path = cwd / "requirements.txt"
208
209
  if not path.exists():
209
210
  return None
210
211
  try:
@@ -91,15 +91,16 @@ class GitOperator:
91
91
  [
92
92
  "git",
93
93
  "ls-files",
94
+ "-z",
94
95
  "--deleted",
95
96
  "--modified",
96
97
  "--others",
97
98
  "--exclude-standard",
98
99
  ]
99
100
  )
100
- files = {f.strip() for f in out.split("\n") if f.strip()}
101
- staged_out = self._run(["git", "diff", "--name-only", "--cached"])
102
- files.update(f.strip() for f in staged_out.split("\n") if f.strip())
101
+ files = {f for f in out.split("\0") if f}
102
+ staged_out = self._run(["git", "diff", "-z", "--name-only", "--cached"])
103
+ files.update(f for f in staged_out.split("\0") if f)
103
104
  return sorted(files)
104
105
 
105
106
  # ------------------------------------------------------------------ actions
@@ -30,33 +30,56 @@ class BaseProvider(ABC):
30
30
  model: str | None = None,
31
31
  **kwargs: Any,
32
32
  ) -> str:
33
- """Public entry point. Validates input, dispatches, and wraps errors.
33
+ """Public entry point. Validates input, dispatches, and wraps errors."""
34
+ import time
34
35
 
35
- Args:
36
- prompt: The prompt to send to the model.
37
- api_key: Provider API key. May be ``None`` for local providers
38
- (see :attr:`REQUIRES_API_KEY`).
39
- model: Model id. Falls back to :attr:`DEFAULT_MODEL` when ``None``.
40
- **kwargs: Forwarded to :meth:`_generate`.
41
-
42
- Returns:
43
- The generated text, stripped.
44
-
45
- Raises:
46
- ValueError: If the provider requires an API key and none was given.
47
- RuntimeError: For any underlying failure, with a friendly message.
48
- """
49
36
  if self.REQUIRES_API_KEY and not api_key:
50
37
  raise ValueError(
51
38
  f"{self.DISPLAY_NAME} API key is missing. "
52
39
  "Set it via `devgen setup config` or pass --api-key."
53
40
  )
54
41
  chosen_model = model or self.DEFAULT_MODEL
55
- try:
56
- text = self._generate(prompt, api_key, chosen_model, **kwargs)
57
- return (text or "").strip()
58
- except Exception as e:
59
- self._handle_error(e)
42
+
43
+ max_retries = kwargs.get("max_retries", 5)
44
+ base_delay = kwargs.get("retry_delay", 10)
45
+
46
+ for attempt in range(max_retries + 1):
47
+ try:
48
+ text = self._generate(prompt, api_key, chosen_model, **kwargs)
49
+ return (text or "").strip()
50
+ except Exception as e:
51
+ if is_token_limit_error(e):
52
+ self._handle_error(e)
53
+ if self._is_quota_error(e) and attempt < max_retries:
54
+ delay = base_delay * (2**attempt)
55
+ print(
56
+ f"\n[{self.DISPLAY_NAME}] Quota/Rate limit hit (429). "
57
+ f"Waiting {delay}s and retrying... "
58
+ f"(Attempt {attempt + 1}/{max_retries})"
59
+ )
60
+ time.sleep(delay)
61
+ continue
62
+ if self._is_quota_error(e) and attempt >= max_retries:
63
+ raise RuntimeError(
64
+ f"{self.DISPLAY_NAME} API quota exhausted (429) after multiple retries."
65
+ ) from e
66
+ self._handle_error(e)
67
+ return ""
68
+
69
+ @staticmethod
70
+ def _is_quota_error(error: Exception) -> bool:
71
+ text = str(error).upper()
72
+ return any(
73
+ marker in text
74
+ for marker in (
75
+ "429",
76
+ "RESOURCE_EXHAUSTED",
77
+ "QUOTA_EXCEEDED",
78
+ "THROTTLED",
79
+ "RATE_LIMIT_EXCEEDED",
80
+ "TOO_MANY_REQUESTS",
81
+ )
82
+ )
60
83
 
61
84
  def _handle_error(self, error: Exception) -> None:
62
85
  """Translate an internal exception into a user-facing RuntimeError.
@@ -0,0 +1,25 @@
1
+ from google import genai
2
+ from google.genai import types
3
+
4
+ from devgen.providers.base import BaseProvider
5
+
6
+
7
+ class GeminiProvider(BaseProvider):
8
+ """Generates content using Google's Gemini models."""
9
+
10
+ DISPLAY_NAME = "Gemini"
11
+ DEFAULT_MODEL = "gemini-2.5-flash"
12
+
13
+ def _generate(self, prompt, api_key, model, **kwargs):
14
+ client = genai.Client(api_key=api_key)
15
+ response = client.models.generate_content(
16
+ model=model,
17
+ contents=prompt,
18
+ config=types.GenerateContentConfig(
19
+ temperature=kwargs.get("temperature", 0.7),
20
+ top_p=kwargs.get("top_p", 0.95),
21
+ top_k=kwargs.get("top_k", 40),
22
+ max_output_tokens=kwargs.get("max_output_tokens", 2048),
23
+ ),
24
+ )
25
+ return response.text or ""
@@ -0,0 +1,29 @@
1
+ You write Conventional Commits. Output ONLY the message — no preamble, no labels, no quotes, no markdown.
2
+
3
+ Format:
4
+ <type>[optional scope][optional !]: <description>
5
+ [optional blank line]
6
+ [optional body]
7
+ [optional blank line]
8
+ [optional footer(s)]
9
+
10
+ types: feat fix refactor perf docs test build ci chore style
11
+ scope: {% if group_name and group_name != 'root' %}({{ group_name }}){% else %}optional{% endif %}{% if use_emoji %} (prefix description with one matching emoji){% endif %}
12
+
13
+ Examples:
14
+ feat: add email notifications on new direct messages
15
+ feat(shopping cart): add the amazing button
16
+ feat!: remove ticket list endpoint
17
+ refers to JIRA-1337
18
+ BREAKING CHANGE: ticket endpoints no longer supports list all entities.
19
+ fix(shopping-cart): prevent order an empty shopping cart
20
+ fix(api): fix wrong calculation of request body checksum
21
+ fix: add missing parameter to service call
22
+ The error occurred due to <reasons>.
23
+ perf: decrease memory footprint for determine unique visitors by using HyperLogLog
24
+ build: update dependencies
25
+ build(release): bump version to 1.0.0
26
+ refactor: implement fibonacci number calculation as recursion
27
+ style: remove empty line
28
+
29
+ {{ diff_text }}{{ context }}
@@ -46,7 +46,7 @@ def sanitize_ai_commit_message(raw_text: str) -> str:
46
46
  # - **fix: bug fix**
47
47
  # - chore(deps)!: breaking change
48
48
  header_pattern = re.compile(
49
- r"^(\*\*)?(feat|fix|chore|refactor|docs|style|test|build|ci)(\(.*\))?!?: .*",
49
+ r"^(\*\*)?(feat|fix|chore|refactor|docs|style|test|build|ci|perf|revert|deps|wip)(\(.*\))?!?: .*",
50
50
  re.IGNORECASE,
51
51
  )
52
52
 
@@ -75,6 +75,9 @@ def sanitize_ai_commit_message(raw_text: str) -> str:
75
75
  # If we hit another header or a known separator, we stop
76
76
  if "**Sponsor**" in line:
77
77
  break
78
+ # Ignore trailing markdown code block markers
79
+ if line.strip() in ("```", "```md", "```markdown"):
80
+ continue
78
81
  cleaned_lines.append(line)
79
82
 
80
83
  if cleaned_lines:
@@ -196,7 +199,7 @@ def get_git_staged_files() -> list[str]:
196
199
  try:
197
200
  output = run_git_command(["git", "diff", "--name-only", "--cached"])
198
201
  return [f for f in output.splitlines() if f.strip()]
199
- except subprocess.CalledProcessError:
202
+ except (subprocess.CalledProcessError, FileNotFoundError):
200
203
  return []
201
204
 
202
205
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devgen-cli
3
- Version: 0.2.9
3
+ Version: 0.3.0
4
4
  Summary: A collection of developer tools
5
5
  Home-page: https://github.com/S4NKALP/devgen
6
6
  Author: Sankalp Tharu
@@ -20,15 +20,15 @@ Classifier: Programming Language :: Python :: 3.10
20
20
  Requires-Python: >=3.10
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
- Requires-Dist: anthropic>=0.75.0
24
- Requires-Dist: google-genai>=0.1.0
25
- Requires-Dist: openai>=2.11.0
23
+ Requires-Dist: anthropic>=0.116.0
24
+ Requires-Dist: google-genai>=2.10.0
25
+ Requires-Dist: openai>=2.44.0
26
26
  Requires-Dist: pyyaml>=6.0.3
27
27
  Requires-Dist: questionary>=2.1.1
28
- Requires-Dist: requests>=2.32.5
29
- Requires-Dist: rich>=14.2.0
28
+ Requires-Dist: requests>=2.34.2
29
+ Requires-Dist: rich>=15.0.0
30
30
  Requires-Dist: toml>=0.10.2
31
- Requires-Dist: typer>=0.20.0
31
+ Requires-Dist: typer>=0.26.8
32
32
  Dynamic: author
33
33
  Dynamic: home-page
34
34
  Dynamic: license-file
@@ -0,0 +1,9 @@
1
+ anthropic>=0.116.0
2
+ google-genai>=2.10.0
3
+ openai>=2.44.0
4
+ pyyaml>=6.0.3
5
+ questionary>=2.1.1
6
+ requests>=2.34.2
7
+ rich>=15.0.0
8
+ toml>=0.10.2
9
+ typer>=0.26.8
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devgen-cli"
7
- version = "0.2.9"
7
+ version = "0.3.0"
8
8
  description = "A collection of developer tools"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -25,15 +25,15 @@ classifiers = [
25
25
  "Programming Language :: Python :: 3.10",
26
26
  ]
27
27
  dependencies = [
28
- "anthropic>=0.75.0",
29
- "google-genai>=0.1.0",
30
- "openai>=2.11.0",
28
+ "anthropic>=0.116.0",
29
+ "google-genai>=2.10.0",
30
+ "openai>=2.44.0",
31
31
  "pyyaml>=6.0.3",
32
32
  "questionary>=2.1.1",
33
- "requests>=2.32.5",
34
- "rich>=14.2.0",
33
+ "requests>=2.34.2",
34
+ "rich>=15.0.0",
35
35
  "toml>=0.10.2",
36
- "typer>=0.20.0",
36
+ "typer>=0.26.8",
37
37
  ]
38
38
  [project.urls]
39
39
  Homepage = "https://github.com/S4NKALP/devgen"
@@ -49,6 +49,6 @@ include = ["devgen*"]
49
49
 
50
50
  [dependency-groups]
51
51
  dev = [
52
- "pre-commit>=4.5.0",
53
- "ruff>=0.14.9",
52
+ "pre-commit>=4.6.0",
53
+ "ruff>=0.15.20",
54
54
  ]
@@ -6,7 +6,7 @@ from setuptools import find_packages, setup
6
6
 
7
7
  setup(
8
8
  name="devgen-cli",
9
- version="0.2.9",
9
+ version="0.3.0",
10
10
  packages=find_packages(),
11
11
  include_package_data=True,
12
12
  install_requires=[
@@ -1,63 +0,0 @@
1
- import time
2
-
3
- from google import genai
4
- from google.genai import types
5
-
6
- from devgen.providers.base import BaseProvider
7
- from devgen.utils import is_token_limit_error
8
-
9
-
10
- class GeminiProvider(BaseProvider):
11
- """Generates content using Google's Gemini models."""
12
-
13
- DISPLAY_NAME = "Gemini"
14
- DEFAULT_MODEL = "gemini-2.5-flash"
15
-
16
- def _generate(self, prompt, api_key, model, **kwargs):
17
- max_retries = kwargs.get("max_retries", 5)
18
- base_delay = kwargs.get("retry_delay", 10) # 10s base for 429 backoff
19
- client = genai.Client(api_key=api_key)
20
-
21
- for attempt in range(max_retries + 1):
22
- try:
23
- response = client.models.generate_content(
24
- model=model,
25
- contents=prompt,
26
- config=types.GenerateContentConfig(
27
- temperature=kwargs.get("temperature", 0.7),
28
- top_p=kwargs.get("top_p", 0.95),
29
- top_k=kwargs.get("top_k", 40),
30
- max_output_tokens=kwargs.get("max_output_tokens", 2048),
31
- ),
32
- )
33
- return response.text or ""
34
- except Exception as e:
35
- if is_token_limit_error(e):
36
- raise # base class handles with friendly message
37
- if self._is_quota_error(e) and attempt < max_retries:
38
- delay = base_delay * (2**attempt)
39
- print(
40
- f"\n[Gemini] Quota limit hit (429). This is often a temporary "
41
- f"rate limit (RPM). Waiting {delay}s and retrying... "
42
- f"(Attempt {attempt + 1}/{max_retries})"
43
- )
44
- time.sleep(delay)
45
- continue
46
- if self._is_quota_error(e):
47
- raise RuntimeError(
48
- "Gemini API quota exhausted (429) after multiple retries.\n\n"
49
- "POSSIBLE CAUSES:\n"
50
- "1. Rate Limit (RPM/RPD): You are sending requests too fast for the Free Tier.\n"
51
- "2. Daily Limit: You have reached the maximum free requests allowed for today (typically 1,500).\n"
52
- "3. Credit/Billing: If on a paid plan, your credit may be exhausted.\n\n"
53
- "Please check your usage metrics at: https://aistudio.google.com/app/usage"
54
- ) from e
55
- raise
56
-
57
- @staticmethod
58
- def _is_quota_error(error: Exception) -> bool:
59
- text = str(error).upper()
60
- return any(
61
- marker in text
62
- for marker in ("429", "RESOURCE_EXHAUSTED", "QUOTA_EXCEEDED", "THROTTLED")
63
- )
@@ -1,14 +0,0 @@
1
- You write Conventional Commits. Output ONLY the message — no preamble, no labels, no quotes, no markdown.
2
-
3
- Format:
4
- <type>(<scope>): <summary>
5
- [optional 1-2 line body on WHY]
6
-
7
- types: feat fix refactor perf docs test build ci chore style
8
- scope: {{ group_name }}{% if use_emoji %} (prefix summary with one matching emoji){% endif %}
9
-
10
- Example:
11
- feat(auth): add login rate limit
12
- Prevents brute-force attacks.
13
-
14
- {{ diff_text }}{{ context }}
@@ -1,9 +0,0 @@
1
- anthropic>=0.75.0
2
- google-genai>=0.1.0
3
- openai>=2.11.0
4
- pyyaml>=6.0.3
5
- questionary>=2.1.1
6
- requests>=2.32.5
7
- rich>=14.2.0
8
- toml>=0.10.2
9
- typer>=0.20.0
File without changes
File without changes
File without changes
File without changes
File without changes