git-commit-message 0.6.0__py3-none-any.whl → 0.7.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.
- git_commit_message/_cli.py +25 -7
- git_commit_message/_gemini.py +122 -0
- git_commit_message/_gpt.py +43 -557
- git_commit_message/_llm.py +594 -0
- {git_commit_message-0.6.0.dist-info → git_commit_message-0.7.0.dist-info}/METADATA +36 -4
- git_commit_message-0.7.0.dist-info/RECORD +12 -0
- git_commit_message-0.6.0.dist-info/RECORD +0 -10
- {git_commit_message-0.6.0.dist-info → git_commit_message-0.7.0.dist-info}/WHEEL +0 -0
- {git_commit_message-0.6.0.dist-info → git_commit_message-0.7.0.dist-info}/entry_points.txt +0 -0
- {git_commit_message-0.6.0.dist-info → git_commit_message-0.7.0.dist-info}/top_level.txt +0 -0
git_commit_message/_cli.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Command-line interface entry point.
|
|
2
2
|
|
|
3
|
-
Collect staged changes from the repository and call an
|
|
3
|
+
Collect staged changes from the repository and call an LLM provider
|
|
4
4
|
to generate a commit message, or create a commit straight away.
|
|
5
5
|
"""
|
|
6
6
|
|
|
@@ -19,10 +19,11 @@ from ._git import (
|
|
|
19
19
|
get_staged_diff,
|
|
20
20
|
has_staged_changes,
|
|
21
21
|
)
|
|
22
|
-
from .
|
|
22
|
+
from ._llm import (
|
|
23
|
+
CommitMessageResult,
|
|
24
|
+
UnsupportedProviderError,
|
|
23
25
|
generate_commit_message,
|
|
24
26
|
generate_commit_message_with_info,
|
|
25
|
-
CommitMessageResult,
|
|
26
27
|
)
|
|
27
28
|
|
|
28
29
|
|
|
@@ -50,7 +51,7 @@ def _build_parser() -> ArgumentParser:
|
|
|
50
51
|
parser: ArgumentParser = ArgumentParser(
|
|
51
52
|
prog="git-commit-message",
|
|
52
53
|
description=(
|
|
53
|
-
"Generate a commit message
|
|
54
|
+
"Generate a commit message based on the staged changes."
|
|
54
55
|
),
|
|
55
56
|
)
|
|
56
57
|
|
|
@@ -72,11 +73,21 @@ def _build_parser() -> ArgumentParser:
|
|
|
72
73
|
help="Open an editor to amend the message before committing. Use with '--commit'.",
|
|
73
74
|
)
|
|
74
75
|
|
|
76
|
+
parser.add_argument(
|
|
77
|
+
"--provider",
|
|
78
|
+
default=None,
|
|
79
|
+
help=(
|
|
80
|
+
"LLM provider to use (default: openai). "
|
|
81
|
+
"You may also set GIT_COMMIT_MESSAGE_PROVIDER. "
|
|
82
|
+
"The CLI flag overrides the environment variable."
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
|
|
75
86
|
parser.add_argument(
|
|
76
87
|
"--model",
|
|
77
88
|
default=None,
|
|
78
89
|
help=(
|
|
79
|
-
"
|
|
90
|
+
"Model name to use. If unspecified, uses GIT_COMMIT_MESSAGE_MODEL or a provider-specific default (openai: gpt-5-mini; google: gemini-2.5-flash)."
|
|
80
91
|
),
|
|
81
92
|
)
|
|
82
93
|
|
|
@@ -170,6 +181,7 @@ def _run(
|
|
|
170
181
|
getattr(args, "max_length", None),
|
|
171
182
|
getattr(args, "language", None),
|
|
172
183
|
chunk_tokens,
|
|
184
|
+
getattr(args, "provider", None),
|
|
173
185
|
)
|
|
174
186
|
message = result.message
|
|
175
187
|
else:
|
|
@@ -181,7 +193,11 @@ def _run(
|
|
|
181
193
|
getattr(args, "max_length", None),
|
|
182
194
|
getattr(args, "language", None),
|
|
183
195
|
chunk_tokens,
|
|
196
|
+
getattr(args, "provider", None),
|
|
184
197
|
)
|
|
198
|
+
except UnsupportedProviderError as exc:
|
|
199
|
+
print(str(exc), file=stderr)
|
|
200
|
+
return 3
|
|
185
201
|
except Exception as exc: # noqa: BLE001 - to preserve standard output messaging
|
|
186
202
|
print(f"Failed to generate commit message: {exc}", file=stderr)
|
|
187
203
|
return 3
|
|
@@ -199,7 +215,8 @@ def _run(
|
|
|
199
215
|
if not args.commit:
|
|
200
216
|
if args.debug and result is not None:
|
|
201
217
|
# Print debug information
|
|
202
|
-
print("====
|
|
218
|
+
print(f"==== {result.provider} Usage ====")
|
|
219
|
+
print(f"provider: {result.provider}")
|
|
203
220
|
print(f"model: {result.model}")
|
|
204
221
|
print(f"response_id: {getattr(result, 'response_id', '(n/a)')}")
|
|
205
222
|
if result.total_tokens is not None:
|
|
@@ -220,7 +237,8 @@ def _run(
|
|
|
220
237
|
|
|
221
238
|
if args.debug and result is not None:
|
|
222
239
|
# Also print debug info before commit
|
|
223
|
-
print("====
|
|
240
|
+
print(f"==== {result.provider} Usage ====")
|
|
241
|
+
print(f"provider: {result.provider}")
|
|
224
242
|
print(f"model: {result.model}")
|
|
225
243
|
print(f"response_id: {getattr(result, 'response_id', '(n/a)')}")
|
|
226
244
|
if result.total_tokens is not None:
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Google (Gemini) provider implementation.
|
|
2
|
+
|
|
3
|
+
This module contains only Google GenAI-specific API calls and token counting.
|
|
4
|
+
Provider-agnostic orchestration/prompt logic lives in `_llm.py`.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from os import environ
|
|
10
|
+
|
|
11
|
+
from google import genai
|
|
12
|
+
from google.genai import types
|
|
13
|
+
|
|
14
|
+
from ._llm import LLMTextResult, LLMUsage
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GoogleGenAIProvider:
|
|
18
|
+
name = "google"
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
/,
|
|
23
|
+
*,
|
|
24
|
+
api_key: str | None = None,
|
|
25
|
+
) -> None:
|
|
26
|
+
key = api_key or environ.get("GOOGLE_API_KEY")
|
|
27
|
+
if not key:
|
|
28
|
+
raise RuntimeError("The GOOGLE_API_KEY environment variable is required.")
|
|
29
|
+
self._client = genai.Client(api_key=key)
|
|
30
|
+
|
|
31
|
+
def count_tokens(
|
|
32
|
+
self,
|
|
33
|
+
/,
|
|
34
|
+
*,
|
|
35
|
+
model: str,
|
|
36
|
+
text: str,
|
|
37
|
+
) -> int:
|
|
38
|
+
try:
|
|
39
|
+
resp = self._client.models.count_tokens(
|
|
40
|
+
model=model,
|
|
41
|
+
contents=text,
|
|
42
|
+
)
|
|
43
|
+
except Exception as exc:
|
|
44
|
+
raise RuntimeError(
|
|
45
|
+
"Token counting failed for the Google provider. "
|
|
46
|
+
"Try `--chunk-tokens 0` (default) or `--chunk-tokens -1` to disable summarisation."
|
|
47
|
+
) from exc
|
|
48
|
+
|
|
49
|
+
total = getattr(resp, "total_tokens", None)
|
|
50
|
+
if not isinstance(total, int):
|
|
51
|
+
raise RuntimeError(
|
|
52
|
+
"Token counting returned an unexpected response from the Google provider. "
|
|
53
|
+
"Try `--chunk-tokens 0` (default) or `--chunk-tokens -1` to disable summarisation."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return total
|
|
57
|
+
|
|
58
|
+
def generate_text(
|
|
59
|
+
self,
|
|
60
|
+
/,
|
|
61
|
+
*,
|
|
62
|
+
model: str,
|
|
63
|
+
instructions: str,
|
|
64
|
+
user_text: str,
|
|
65
|
+
) -> LLMTextResult:
|
|
66
|
+
config = types.GenerateContentConfig(
|
|
67
|
+
system_instruction=instructions,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
resp = self._client.models.generate_content(
|
|
71
|
+
model=model,
|
|
72
|
+
contents=user_text,
|
|
73
|
+
config=config,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
text = self._extract_text(resp)
|
|
77
|
+
if not text:
|
|
78
|
+
raise RuntimeError("An empty response text was generated by the provider.")
|
|
79
|
+
|
|
80
|
+
usage = self._extract_usage(resp)
|
|
81
|
+
|
|
82
|
+
return LLMTextResult(
|
|
83
|
+
text=text,
|
|
84
|
+
response_id=getattr(resp, "response_id", None),
|
|
85
|
+
usage=usage,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def _extract_text(
|
|
90
|
+
resp: types.GenerateContentResponse,
|
|
91
|
+
/,
|
|
92
|
+
) -> str:
|
|
93
|
+
candidates = getattr(resp, "candidates", None)
|
|
94
|
+
if not candidates:
|
|
95
|
+
return ""
|
|
96
|
+
|
|
97
|
+
parts = getattr(candidates[0].content, "parts", None) if candidates[0].content else None
|
|
98
|
+
if not parts:
|
|
99
|
+
return ""
|
|
100
|
+
|
|
101
|
+
texts: list[str] = []
|
|
102
|
+
for part in parts:
|
|
103
|
+
t = getattr(part, "text", None)
|
|
104
|
+
if isinstance(t, str) and t.strip():
|
|
105
|
+
texts.append(t)
|
|
106
|
+
|
|
107
|
+
return "\n".join(texts).strip()
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def _extract_usage(
|
|
111
|
+
resp: types.GenerateContentResponse,
|
|
112
|
+
/,
|
|
113
|
+
) -> LLMUsage | None:
|
|
114
|
+
metadata = getattr(resp, "usage_metadata", None)
|
|
115
|
+
if metadata is None:
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
return LLMUsage(
|
|
119
|
+
prompt_tokens=getattr(metadata, "prompt_token_count", None),
|
|
120
|
+
completion_tokens=getattr(metadata, "candidates_token_count", None),
|
|
121
|
+
total_tokens=getattr(metadata, "total_token_count", None),
|
|
122
|
+
)
|