gac 2.7.3__tar.gz → 3.10.11__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.
- {gac-2.7.3 → gac-3.10.11}/PKG-INFO +15 -8
- {gac-2.7.3 → gac-3.10.11}/README.md +14 -6
- {gac-2.7.3 → gac-3.10.11}/pyproject.toml +2 -7
- {gac-2.7.3 → gac-3.10.11}/src/gac/__init__.py +4 -6
- {gac-2.7.3 → gac-3.10.11}/src/gac/__version__.py +1 -1
- {gac-2.7.3 → gac-3.10.11}/src/gac/ai.py +5 -49
- {gac-2.7.3 → gac-3.10.11}/src/gac/ai_utils.py +80 -76
- gac-3.10.11/src/gac/auth_cli.py +214 -0
- {gac-2.7.3 → gac-3.10.11}/src/gac/cli.py +46 -10
- gac-3.10.11/src/gac/commit_executor.py +59 -0
- gac-3.10.11/src/gac/config.py +125 -0
- gac-3.10.11/src/gac/config_cli.py +95 -0
- gac-3.10.11/src/gac/constants/__init__.py +34 -0
- gac-3.10.11/src/gac/constants/commit.py +63 -0
- gac-3.10.11/src/gac/constants/defaults.py +40 -0
- gac-3.10.11/src/gac/constants/file_patterns.py +110 -0
- gac-3.10.11/src/gac/constants/languages.py +119 -0
- {gac-2.7.3 → gac-3.10.11}/src/gac/diff_cli.py +0 -22
- {gac-2.7.3 → gac-3.10.11}/src/gac/errors.py +8 -2
- {gac-2.7.3 → gac-3.10.11}/src/gac/git.py +53 -6
- gac-3.10.11/src/gac/git_state_validator.py +193 -0
- gac-3.10.11/src/gac/grouped_commit_workflow.py +458 -0
- gac-3.10.11/src/gac/init_cli.py +70 -0
- gac-3.10.11/src/gac/interactive_mode.py +179 -0
- gac-3.10.11/src/gac/language_cli.py +377 -0
- gac-3.10.11/src/gac/main.py +328 -0
- gac-2.7.3/src/gac/init_cli.py → gac-3.10.11/src/gac/model_cli.py +161 -214
- gac-3.10.11/src/gac/model_identifier.py +70 -0
- gac-3.10.11/src/gac/oauth/__init__.py +27 -0
- {gac-2.7.3 → gac-3.10.11}/src/gac/oauth/claude_code.py +89 -22
- gac-3.10.11/src/gac/oauth/qwen_oauth.py +327 -0
- gac-3.10.11/src/gac/oauth/token_store.py +81 -0
- gac-3.10.11/src/gac/oauth_retry.py +161 -0
- gac-3.10.11/src/gac/postprocess.py +155 -0
- gac-3.10.11/src/gac/prompt.py +425 -0
- gac-3.10.11/src/gac/prompt_builder.py +88 -0
- gac-3.10.11/src/gac/providers/README.md +437 -0
- gac-3.10.11/src/gac/providers/__init__.py +80 -0
- gac-3.10.11/src/gac/providers/anthropic.py +17 -0
- gac-3.10.11/src/gac/providers/azure_openai.py +57 -0
- gac-3.10.11/src/gac/providers/base.py +329 -0
- gac-3.10.11/src/gac/providers/cerebras.py +15 -0
- gac-3.10.11/src/gac/providers/chutes.py +25 -0
- gac-3.10.11/src/gac/providers/claude_code.py +79 -0
- gac-3.10.11/src/gac/providers/custom_anthropic.py +103 -0
- gac-3.10.11/src/gac/providers/custom_openai.py +44 -0
- gac-3.10.11/src/gac/providers/deepseek.py +15 -0
- gac-3.10.11/src/gac/providers/error_handler.py +139 -0
- gac-3.10.11/src/gac/providers/fireworks.py +15 -0
- gac-3.10.11/src/gac/providers/gemini.py +90 -0
- gac-3.10.11/src/gac/providers/groq.py +15 -0
- gac-3.10.11/src/gac/providers/kimi_coding.py +27 -0
- gac-3.10.11/src/gac/providers/lmstudio.py +80 -0
- gac-3.10.11/src/gac/providers/minimax.py +15 -0
- gac-3.10.11/src/gac/providers/mistral.py +15 -0
- gac-3.10.11/src/gac/providers/moonshot.py +15 -0
- gac-3.10.11/src/gac/providers/ollama.py +73 -0
- gac-3.10.11/src/gac/providers/openai.py +32 -0
- gac-3.10.11/src/gac/providers/openrouter.py +21 -0
- gac-3.10.11/src/gac/providers/protocol.py +71 -0
- gac-3.10.11/src/gac/providers/qwen.py +64 -0
- gac-3.10.11/src/gac/providers/registry.py +58 -0
- gac-3.10.11/src/gac/providers/replicate.py +156 -0
- gac-3.10.11/src/gac/providers/streamlake.py +31 -0
- gac-3.10.11/src/gac/providers/synthetic.py +40 -0
- gac-3.10.11/src/gac/providers/together.py +15 -0
- gac-3.10.11/src/gac/providers/zai.py +31 -0
- gac-3.10.11/src/gac/py.typed +0 -0
- {gac-2.7.3 → gac-3.10.11}/src/gac/security.py +1 -1
- gac-3.10.11/src/gac/templates/__init__.py +1 -0
- gac-3.10.11/src/gac/templates/question_generation.txt +60 -0
- gac-3.10.11/src/gac/templates/system_prompt.txt +224 -0
- gac-3.10.11/src/gac/templates/user_prompt.txt +28 -0
- {gac-2.7.3 → gac-3.10.11}/src/gac/utils.py +36 -6
- gac-3.10.11/src/gac/workflow_context.py +162 -0
- {gac-2.7.3 → gac-3.10.11}/src/gac/workflow_utils.py +89 -6
- gac-2.7.3/src/gac/auth_cli.py +0 -69
- gac-2.7.3/src/gac/config.py +0 -49
- gac-2.7.3/src/gac/config_cli.py +0 -62
- gac-2.7.3/src/gac/constants.py +0 -321
- gac-2.7.3/src/gac/language_cli.py +0 -253
- gac-2.7.3/src/gac/main.py +0 -767
- gac-2.7.3/src/gac/oauth/__init__.py +0 -1
- gac-2.7.3/src/gac/prompt.py +0 -785
- gac-2.7.3/src/gac/providers/__init__.py +0 -46
- gac-2.7.3/src/gac/providers/anthropic.py +0 -51
- gac-2.7.3/src/gac/providers/cerebras.py +0 -38
- gac-2.7.3/src/gac/providers/chutes.py +0 -71
- gac-2.7.3/src/gac/providers/claude_code.py +0 -102
- gac-2.7.3/src/gac/providers/custom_anthropic.py +0 -133
- gac-2.7.3/src/gac/providers/custom_openai.py +0 -99
- gac-2.7.3/src/gac/providers/deepseek.py +0 -38
- gac-2.7.3/src/gac/providers/fireworks.py +0 -38
- gac-2.7.3/src/gac/providers/gemini.py +0 -87
- gac-2.7.3/src/gac/providers/groq.py +0 -63
- gac-2.7.3/src/gac/providers/lmstudio.py +0 -59
- gac-2.7.3/src/gac/providers/minimax.py +0 -38
- gac-2.7.3/src/gac/providers/mistral.py +0 -38
- gac-2.7.3/src/gac/providers/ollama.py +0 -50
- gac-2.7.3/src/gac/providers/openai.py +0 -38
- gac-2.7.3/src/gac/providers/openrouter.py +0 -58
- gac-2.7.3/src/gac/providers/streamlake.py +0 -51
- gac-2.7.3/src/gac/providers/synthetic.py +0 -42
- gac-2.7.3/src/gac/providers/together.py +0 -38
- gac-2.7.3/src/gac/providers/zai.py +0 -59
- {gac-2.7.3 → gac-3.10.11}/.gitignore +0 -0
- {gac-2.7.3 → gac-3.10.11}/LICENSE +0 -0
- {gac-2.7.3 → gac-3.10.11}/src/gac/preprocess.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gac
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.10.11
|
|
4
4
|
Summary: LLM-powered Git commit message generator with multi-provider support
|
|
5
5
|
Project-URL: Homepage, https://github.com/cellwebb/gac
|
|
6
6
|
Project-URL: Documentation, https://github.com/cellwebb/gac#readme
|
|
@@ -22,7 +22,6 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
|
22
22
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
23
23
|
Requires-Python: >=3.10
|
|
24
24
|
Requires-Dist: click>=8.3.0
|
|
25
|
-
Requires-Dist: halo
|
|
26
25
|
Requires-Dist: httpcore>=1.0.9
|
|
27
26
|
Requires-Dist: httpx>=0.28.0
|
|
28
27
|
Requires-Dist: prompt-toolkit>=3.0.36
|
|
@@ -48,7 +47,7 @@ Description-Content-Type: text/markdown
|
|
|
48
47
|
# 🚀 Git Auto Commit (gac)
|
|
49
48
|
|
|
50
49
|
[](https://pypi.org/project/gac/)
|
|
51
|
-
[](https://www.python.org/downloads/)
|
|
52
51
|
[](https://github.com/cellwebb/gac/actions)
|
|
53
52
|
[](https://app.codecov.io/gh/cellwebb/gac)
|
|
54
53
|
[](https://github.com/astral-sh/ruff)
|
|
@@ -105,11 +104,12 @@ uv tool upgrade gac
|
|
|
105
104
|
|
|
106
105
|
## Key Features
|
|
107
106
|
|
|
108
|
-
### 🌐 **Supported Providers**
|
|
107
|
+
### 🌐 **25+ Supported Providers**
|
|
109
108
|
|
|
110
|
-
- **Anthropic** • **Cerebras** • **Chutes.ai** • **Claude Code
|
|
111
|
-
- **
|
|
112
|
-
- **
|
|
109
|
+
- **Anthropic** • **Azure OpenAI** • **Cerebras** • **Chutes.ai** • **Claude Code (OAuth)**
|
|
110
|
+
- **DeepSeek** • **Fireworks** • **Gemini** • **Groq** • **Kimi for Coding** • **LM Studio**
|
|
111
|
+
- **MiniMax.io** • **Mistral AI** • **Moonshot AI** • **Ollama** • **OpenAI** • **OpenRouter**
|
|
112
|
+
- **Qwen.ai (OAuth)** • **Replicate** • **Streamlake** • **Synthetic.new** • **Together AI**
|
|
113
113
|
- **Z.AI** • **Z.AI Coding** • **Custom Endpoints (Anthropic/OpenAI)**
|
|
114
114
|
|
|
115
115
|
### 🧠 **Smart LLM Analysis**
|
|
@@ -130,11 +130,12 @@ uv tool upgrade gac
|
|
|
130
130
|
- **25+ languages**: Generate commit messages in English, Chinese, Japanese, Korean, Spanish, French, German, and 20+ more languages
|
|
131
131
|
- **Flexible translation**: Choose to keep conventional commit prefixes in English for tool compatibility, or fully translate them
|
|
132
132
|
- **Multiple workflows**: Set a default language with `gac language`, or use `-l <language>` flag for one-time overrides
|
|
133
|
-
- **Native script support**: Full support for non-Latin scripts including CJK, Cyrillic,
|
|
133
|
+
- **Native script support**: Full support for non-Latin scripts including CJK, Cyrillic, Thai, and more
|
|
134
134
|
|
|
135
135
|
### 💻 **Developer Experience**
|
|
136
136
|
|
|
137
137
|
- **Interactive feedback**: Type `r` to reroll, `e` to edit in-place with vi/emacs keybindings, or directly type your feedback like `make it shorter` or `focus on the bug fix`
|
|
138
|
+
- **Interactive questioning**: Use `--interactive` (`-i`) to answer targeted questions about your changes for more contextual commit messages
|
|
138
139
|
- **One-command workflows**: Complete workflows with flags like `gac -ayp` (stage all, auto-confirm, push)
|
|
139
140
|
- **Git integration**: Respects pre-commit and lefthook hooks, running them before expensive LLM operations
|
|
140
141
|
|
|
@@ -171,6 +172,7 @@ gac
|
|
|
171
172
|
| `gac -v` | Verbose format with Motivation, Technical Approach, and Impact Analysis |
|
|
172
173
|
| `gac -h "hint"` | Add context for LLM (e.g., `gac -h "bug fix"`) |
|
|
173
174
|
| `gac -s` | Include scope (e.g., feat(auth):) |
|
|
175
|
+
| `gac -i` | Ask questions about changes for better context |
|
|
174
176
|
| `gac -p` | Commit and push |
|
|
175
177
|
|
|
176
178
|
### Power User Examples
|
|
@@ -188,6 +190,9 @@ gac -o
|
|
|
188
190
|
# Group changes into logically related commits
|
|
189
191
|
gac -ag
|
|
190
192
|
|
|
193
|
+
# Interactive mode with verbose output for detailed explanations
|
|
194
|
+
gac -iv
|
|
195
|
+
|
|
191
196
|
# Debug what the LLM sees
|
|
192
197
|
gac --show-prompt
|
|
193
198
|
|
|
@@ -257,6 +262,8 @@ Track real-time installation metrics and package download statistics.
|
|
|
257
262
|
## Getting Help
|
|
258
263
|
|
|
259
264
|
- **Full documentation**: [docs/USAGE.md](docs/en/USAGE.md) - Complete CLI reference
|
|
265
|
+
- **Claude Code OAuth**: [docs/CLAUDE_CODE.md](docs/en/CLAUDE_CODE.md) - Claude Code setup and authentication
|
|
266
|
+
- **Qwen.ai OAuth**: [docs/QWEN.md](docs/en/QWEN.md) - Qwen.ai setup and authentication
|
|
260
267
|
- **Custom prompts**: [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/en/CUSTOM_SYSTEM_PROMPTS.md) - Customize commit message style
|
|
261
268
|
- **Troubleshooting**: [docs/TROUBLESHOOTING.md](docs/en/TROUBLESHOOTING.md) - Common issues and solutions
|
|
262
269
|
- **Contributing**: [docs/CONTRIBUTING.md](docs/en/CONTRIBUTING.md) - Development setup and guidelines
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# 🚀 Git Auto Commit (gac)
|
|
7
7
|
|
|
8
8
|
[](https://pypi.org/project/gac/)
|
|
9
|
-
[](https://www.python.org/downloads/)
|
|
10
10
|
[](https://github.com/cellwebb/gac/actions)
|
|
11
11
|
[](https://app.codecov.io/gh/cellwebb/gac)
|
|
12
12
|
[](https://github.com/astral-sh/ruff)
|
|
@@ -63,11 +63,12 @@ uv tool upgrade gac
|
|
|
63
63
|
|
|
64
64
|
## Key Features
|
|
65
65
|
|
|
66
|
-
### 🌐 **Supported Providers**
|
|
66
|
+
### 🌐 **25+ Supported Providers**
|
|
67
67
|
|
|
68
|
-
- **Anthropic** • **Cerebras** • **Chutes.ai** • **Claude Code
|
|
69
|
-
- **
|
|
70
|
-
- **
|
|
68
|
+
- **Anthropic** • **Azure OpenAI** • **Cerebras** • **Chutes.ai** • **Claude Code (OAuth)**
|
|
69
|
+
- **DeepSeek** • **Fireworks** • **Gemini** • **Groq** • **Kimi for Coding** • **LM Studio**
|
|
70
|
+
- **MiniMax.io** • **Mistral AI** • **Moonshot AI** • **Ollama** • **OpenAI** • **OpenRouter**
|
|
71
|
+
- **Qwen.ai (OAuth)** • **Replicate** • **Streamlake** • **Synthetic.new** • **Together AI**
|
|
71
72
|
- **Z.AI** • **Z.AI Coding** • **Custom Endpoints (Anthropic/OpenAI)**
|
|
72
73
|
|
|
73
74
|
### 🧠 **Smart LLM Analysis**
|
|
@@ -88,11 +89,12 @@ uv tool upgrade gac
|
|
|
88
89
|
- **25+ languages**: Generate commit messages in English, Chinese, Japanese, Korean, Spanish, French, German, and 20+ more languages
|
|
89
90
|
- **Flexible translation**: Choose to keep conventional commit prefixes in English for tool compatibility, or fully translate them
|
|
90
91
|
- **Multiple workflows**: Set a default language with `gac language`, or use `-l <language>` flag for one-time overrides
|
|
91
|
-
- **Native script support**: Full support for non-Latin scripts including CJK, Cyrillic,
|
|
92
|
+
- **Native script support**: Full support for non-Latin scripts including CJK, Cyrillic, Thai, and more
|
|
92
93
|
|
|
93
94
|
### 💻 **Developer Experience**
|
|
94
95
|
|
|
95
96
|
- **Interactive feedback**: Type `r` to reroll, `e` to edit in-place with vi/emacs keybindings, or directly type your feedback like `make it shorter` or `focus on the bug fix`
|
|
97
|
+
- **Interactive questioning**: Use `--interactive` (`-i`) to answer targeted questions about your changes for more contextual commit messages
|
|
96
98
|
- **One-command workflows**: Complete workflows with flags like `gac -ayp` (stage all, auto-confirm, push)
|
|
97
99
|
- **Git integration**: Respects pre-commit and lefthook hooks, running them before expensive LLM operations
|
|
98
100
|
|
|
@@ -129,6 +131,7 @@ gac
|
|
|
129
131
|
| `gac -v` | Verbose format with Motivation, Technical Approach, and Impact Analysis |
|
|
130
132
|
| `gac -h "hint"` | Add context for LLM (e.g., `gac -h "bug fix"`) |
|
|
131
133
|
| `gac -s` | Include scope (e.g., feat(auth):) |
|
|
134
|
+
| `gac -i` | Ask questions about changes for better context |
|
|
132
135
|
| `gac -p` | Commit and push |
|
|
133
136
|
|
|
134
137
|
### Power User Examples
|
|
@@ -146,6 +149,9 @@ gac -o
|
|
|
146
149
|
# Group changes into logically related commits
|
|
147
150
|
gac -ag
|
|
148
151
|
|
|
152
|
+
# Interactive mode with verbose output for detailed explanations
|
|
153
|
+
gac -iv
|
|
154
|
+
|
|
149
155
|
# Debug what the LLM sees
|
|
150
156
|
gac --show-prompt
|
|
151
157
|
|
|
@@ -215,6 +221,8 @@ Track real-time installation metrics and package download statistics.
|
|
|
215
221
|
## Getting Help
|
|
216
222
|
|
|
217
223
|
- **Full documentation**: [docs/USAGE.md](docs/en/USAGE.md) - Complete CLI reference
|
|
224
|
+
- **Claude Code OAuth**: [docs/CLAUDE_CODE.md](docs/en/CLAUDE_CODE.md) - Claude Code setup and authentication
|
|
225
|
+
- **Qwen.ai OAuth**: [docs/QWEN.md](docs/en/QWEN.md) - Qwen.ai setup and authentication
|
|
218
226
|
- **Custom prompts**: [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/en/CUSTOM_SYSTEM_PROMPTS.md) - Customize commit message style
|
|
219
227
|
- **Troubleshooting**: [docs/TROUBLESHOOTING.md](docs/en/TROUBLESHOOTING.md) - Common issues and solutions
|
|
220
228
|
- **Contributing**: [docs/CONTRIBUTING.md](docs/en/CONTRIBUTING.md) - Development setup and guidelines
|
|
@@ -38,7 +38,6 @@ dependencies = [
|
|
|
38
38
|
|
|
39
39
|
# CLI and formatting
|
|
40
40
|
"click>=8.3.0",
|
|
41
|
-
"halo",
|
|
42
41
|
"questionary",
|
|
43
42
|
"rich>=14.1.0",
|
|
44
43
|
"prompt_toolkit>=3.0.36",
|
|
@@ -203,8 +202,8 @@ addopts = "-m 'not integration'"
|
|
|
203
202
|
python_version = "3.10"
|
|
204
203
|
warn_return_any = true
|
|
205
204
|
warn_unused_configs = true
|
|
206
|
-
disallow_untyped_defs =
|
|
207
|
-
disallow_incomplete_defs =
|
|
205
|
+
disallow_untyped_defs = true
|
|
206
|
+
disallow_incomplete_defs = true
|
|
208
207
|
check_untyped_defs = true
|
|
209
208
|
no_implicit_optional = true
|
|
210
209
|
warn_redundant_casts = true
|
|
@@ -218,10 +217,6 @@ show_error_codes = true
|
|
|
218
217
|
module = "gac.providers.*"
|
|
219
218
|
warn_return_any = false
|
|
220
219
|
|
|
221
|
-
[[tool.mypy.overrides]]
|
|
222
|
-
module = "halo"
|
|
223
|
-
ignore_missing_imports = true
|
|
224
|
-
|
|
225
220
|
[template.plugins.default]
|
|
226
221
|
tests = true
|
|
227
222
|
src-layout = true
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
"""Git Auto Commit (gac) - Generate commit messages using AI."""
|
|
2
2
|
|
|
3
|
+
from gac import init_cli
|
|
3
4
|
from gac.__version__ import __version__
|
|
4
5
|
from gac.ai import generate_commit_message
|
|
5
|
-
from gac.
|
|
6
|
-
from gac.prompt import build_prompt, clean_commit_message
|
|
6
|
+
from gac.prompt import build_prompt
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
9
9
|
"__version__",
|
|
10
|
-
"generate_commit_message",
|
|
11
10
|
"build_prompt",
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"push_changes",
|
|
11
|
+
"generate_commit_message",
|
|
12
|
+
"init_cli",
|
|
15
13
|
]
|
|
@@ -9,29 +9,7 @@ import logging
|
|
|
9
9
|
from gac.ai_utils import generate_with_retries
|
|
10
10
|
from gac.constants import EnvDefaults
|
|
11
11
|
from gac.errors import AIError
|
|
12
|
-
from gac.providers import
|
|
13
|
-
call_anthropic_api,
|
|
14
|
-
call_cerebras_api,
|
|
15
|
-
call_chutes_api,
|
|
16
|
-
call_claude_code_api,
|
|
17
|
-
call_custom_anthropic_api,
|
|
18
|
-
call_custom_openai_api,
|
|
19
|
-
call_deepseek_api,
|
|
20
|
-
call_fireworks_api,
|
|
21
|
-
call_gemini_api,
|
|
22
|
-
call_groq_api,
|
|
23
|
-
call_lmstudio_api,
|
|
24
|
-
call_minimax_api,
|
|
25
|
-
call_mistral_api,
|
|
26
|
-
call_ollama_api,
|
|
27
|
-
call_openai_api,
|
|
28
|
-
call_openrouter_api,
|
|
29
|
-
call_streamlake_api,
|
|
30
|
-
call_synthetic_api,
|
|
31
|
-
call_together_api,
|
|
32
|
-
call_zai_api,
|
|
33
|
-
call_zai_coding_api,
|
|
34
|
-
)
|
|
12
|
+
from gac.providers import PROVIDER_REGISTRY
|
|
35
13
|
|
|
36
14
|
logger = logging.getLogger(__name__)
|
|
37
15
|
|
|
@@ -45,6 +23,7 @@ def generate_commit_message(
|
|
|
45
23
|
quiet: bool = False,
|
|
46
24
|
is_group: bool = False,
|
|
47
25
|
skip_success_message: bool = False,
|
|
26
|
+
task_description: str = "commit message",
|
|
48
27
|
) -> str:
|
|
49
28
|
"""Generate a commit message using direct API calls to AI providers.
|
|
50
29
|
|
|
@@ -85,35 +64,10 @@ def generate_commit_message(
|
|
|
85
64
|
{"role": "user", "content": user_prompt},
|
|
86
65
|
]
|
|
87
66
|
|
|
88
|
-
# Provider functions mapping
|
|
89
|
-
provider_funcs = {
|
|
90
|
-
"anthropic": call_anthropic_api,
|
|
91
|
-
"cerebras": call_cerebras_api,
|
|
92
|
-
"claude-code": call_claude_code_api,
|
|
93
|
-
"chutes": call_chutes_api,
|
|
94
|
-
"custom-anthropic": call_custom_anthropic_api,
|
|
95
|
-
"custom-openai": call_custom_openai_api,
|
|
96
|
-
"deepseek": call_deepseek_api,
|
|
97
|
-
"fireworks": call_fireworks_api,
|
|
98
|
-
"gemini": call_gemini_api,
|
|
99
|
-
"groq": call_groq_api,
|
|
100
|
-
"lm-studio": call_lmstudio_api,
|
|
101
|
-
"minimax": call_minimax_api,
|
|
102
|
-
"mistral": call_mistral_api,
|
|
103
|
-
"ollama": call_ollama_api,
|
|
104
|
-
"openai": call_openai_api,
|
|
105
|
-
"openrouter": call_openrouter_api,
|
|
106
|
-
"streamlake": call_streamlake_api,
|
|
107
|
-
"synthetic": call_synthetic_api,
|
|
108
|
-
"together": call_together_api,
|
|
109
|
-
"zai": call_zai_api,
|
|
110
|
-
"zai-coding": call_zai_coding_api,
|
|
111
|
-
}
|
|
112
|
-
|
|
113
67
|
# Generate the commit message using centralized retry logic
|
|
114
68
|
try:
|
|
115
69
|
return generate_with_retries(
|
|
116
|
-
provider_funcs=
|
|
70
|
+
provider_funcs=PROVIDER_REGISTRY,
|
|
117
71
|
model=model,
|
|
118
72
|
messages=messages,
|
|
119
73
|
temperature=temperature,
|
|
@@ -122,6 +76,7 @@ def generate_commit_message(
|
|
|
122
76
|
quiet=quiet,
|
|
123
77
|
is_group=is_group,
|
|
124
78
|
skip_success_message=skip_success_message,
|
|
79
|
+
task_description=task_description,
|
|
125
80
|
)
|
|
126
81
|
except AIError:
|
|
127
82
|
# Re-raise AIError exceptions as-is to preserve error classification
|
|
@@ -150,4 +105,5 @@ def generate_grouped_commits(
|
|
|
150
105
|
quiet=quiet,
|
|
151
106
|
is_group=True,
|
|
152
107
|
skip_success_message=skip_success_message,
|
|
108
|
+
task_description="commit message",
|
|
153
109
|
)
|
|
@@ -3,19 +3,26 @@
|
|
|
3
3
|
This module provides utility functions that support the AI provider implementations.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
import json
|
|
6
7
|
import logging
|
|
7
8
|
import os
|
|
8
9
|
import time
|
|
10
|
+
from collections.abc import Callable
|
|
9
11
|
from functools import lru_cache
|
|
10
|
-
from typing import Any
|
|
12
|
+
from typing import Any, cast
|
|
11
13
|
|
|
12
14
|
import tiktoken
|
|
13
|
-
from
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.status import Status
|
|
14
17
|
|
|
15
18
|
from gac.constants import EnvDefaults, Utility
|
|
16
19
|
from gac.errors import AIError
|
|
20
|
+
from gac.oauth import QwenOAuthProvider, refresh_token_if_expired
|
|
21
|
+
from gac.oauth.token_store import TokenStore
|
|
22
|
+
from gac.providers import SUPPORTED_PROVIDERS
|
|
17
23
|
|
|
18
24
|
logger = logging.getLogger(__name__)
|
|
25
|
+
console = Console()
|
|
19
26
|
|
|
20
27
|
|
|
21
28
|
@lru_cache(maxsize=1)
|
|
@@ -37,7 +44,7 @@ def count_tokens(content: str | list[dict[str, str]] | dict[str, Any], model: st
|
|
|
37
44
|
try:
|
|
38
45
|
encoding = get_encoding(model)
|
|
39
46
|
return len(encoding.encode(text))
|
|
40
|
-
except
|
|
47
|
+
except (KeyError, UnicodeError, ValueError) as e:
|
|
41
48
|
logger.error(f"Error counting tokens: {e}")
|
|
42
49
|
# Fallback to rough estimation (4 chars per token on average)
|
|
43
50
|
return len(text) // 4
|
|
@@ -50,7 +57,7 @@ def extract_text_content(content: str | list[dict[str, str]] | dict[str, Any]) -
|
|
|
50
57
|
elif isinstance(content, list):
|
|
51
58
|
return "\n".join(msg["content"] for msg in content if isinstance(msg, dict) and "content" in msg)
|
|
52
59
|
elif isinstance(content, dict) and "content" in content:
|
|
53
|
-
return content["content"]
|
|
60
|
+
return cast(str, content["content"])
|
|
54
61
|
return ""
|
|
55
62
|
|
|
56
63
|
|
|
@@ -67,36 +74,13 @@ def get_encoding(model: str) -> tiktoken.Encoding:
|
|
|
67
74
|
except KeyError:
|
|
68
75
|
# Fall back to default encoding if model not found
|
|
69
76
|
return tiktoken.get_encoding(Utility.DEFAULT_ENCODING)
|
|
70
|
-
except
|
|
77
|
+
except (OSError, ConnectionError):
|
|
71
78
|
# If there are any network/SSL issues, fall back to default encoding
|
|
72
79
|
return tiktoken.get_encoding(Utility.DEFAULT_ENCODING)
|
|
73
80
|
|
|
74
81
|
|
|
75
|
-
def _classify_error(error_str: str) -> str:
|
|
76
|
-
"""Classify error types based on error message content."""
|
|
77
|
-
error_str = error_str.lower()
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
"api key" in error_str
|
|
81
|
-
or "unauthorized" in error_str
|
|
82
|
-
or "authentication" in error_str
|
|
83
|
-
or "invalid api key" in error_str
|
|
84
|
-
):
|
|
85
|
-
return "authentication"
|
|
86
|
-
elif "timeout" in error_str or "timed out" in error_str or "request timeout" in error_str:
|
|
87
|
-
return "timeout"
|
|
88
|
-
elif "rate limit" in error_str or "too many requests" in error_str or "rate limit exceeded" in error_str:
|
|
89
|
-
return "rate_limit"
|
|
90
|
-
elif "connect" in error_str or "network" in error_str or "network connection failed" in error_str:
|
|
91
|
-
return "connection"
|
|
92
|
-
elif "model" in error_str or "not found" in error_str or "model not found" in error_str:
|
|
93
|
-
return "model"
|
|
94
|
-
else:
|
|
95
|
-
return "unknown"
|
|
96
|
-
|
|
97
|
-
|
|
98
82
|
def generate_with_retries(
|
|
99
|
-
provider_funcs: dict,
|
|
83
|
+
provider_funcs: dict[str, Callable[..., str]],
|
|
100
84
|
model: str,
|
|
101
85
|
messages: list[dict[str, str]],
|
|
102
86
|
temperature: float,
|
|
@@ -105,6 +89,7 @@ def generate_with_retries(
|
|
|
105
89
|
quiet: bool = False,
|
|
106
90
|
is_group: bool = False,
|
|
107
91
|
skip_success_message: bool = False,
|
|
92
|
+
task_description: str = "commit message",
|
|
108
93
|
) -> str:
|
|
109
94
|
"""Generate content with retry logic using direct API calls."""
|
|
110
95
|
# Parse model string to determine provider and actual model
|
|
@@ -114,51 +99,74 @@ def generate_with_retries(
|
|
|
114
99
|
provider, model_name = model.split(":", 1)
|
|
115
100
|
|
|
116
101
|
# Validate provider
|
|
117
|
-
|
|
118
|
-
"
|
|
119
|
-
"cerebras",
|
|
120
|
-
"chutes",
|
|
121
|
-
"claude-code",
|
|
122
|
-
"deepseek",
|
|
123
|
-
"fireworks",
|
|
124
|
-
"gemini",
|
|
125
|
-
"groq",
|
|
126
|
-
"lm-studio",
|
|
127
|
-
"minimax",
|
|
128
|
-
"mistral",
|
|
129
|
-
"ollama",
|
|
130
|
-
"openai",
|
|
131
|
-
"openrouter",
|
|
132
|
-
"streamlake",
|
|
133
|
-
"synthetic",
|
|
134
|
-
"together",
|
|
135
|
-
"zai",
|
|
136
|
-
"zai-coding",
|
|
137
|
-
"custom-anthropic",
|
|
138
|
-
"custom-openai",
|
|
139
|
-
]
|
|
140
|
-
if provider not in supported_providers:
|
|
141
|
-
raise AIError.model_error(f"Unsupported provider: {provider}. Supported providers: {supported_providers}")
|
|
102
|
+
if provider not in SUPPORTED_PROVIDERS:
|
|
103
|
+
raise AIError.model_error(f"Unsupported provider: {provider}. Supported providers: {SUPPORTED_PROVIDERS}")
|
|
142
104
|
|
|
143
105
|
if not messages:
|
|
144
106
|
raise AIError.model_error("No messages provided for AI generation")
|
|
145
107
|
|
|
108
|
+
# Load Claude Code token from TokenStore if needed
|
|
109
|
+
if provider == "claude-code":
|
|
110
|
+
# Check token expiry and refresh if needed
|
|
111
|
+
if not refresh_token_if_expired(quiet=True):
|
|
112
|
+
raise AIError.authentication_error(
|
|
113
|
+
"Claude Code token not found or expired. Please authenticate with 'gac auth claude-code login'."
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Load the (possibly refreshed) token
|
|
117
|
+
token_store = TokenStore()
|
|
118
|
+
token_data = token_store.get_token("claude-code")
|
|
119
|
+
if token_data and "access_token" in token_data:
|
|
120
|
+
os.environ["CLAUDE_CODE_ACCESS_TOKEN"] = token_data["access_token"]
|
|
121
|
+
else:
|
|
122
|
+
raise AIError.authentication_error(
|
|
123
|
+
"Claude Code token not found. Please authenticate with 'gac auth claude-code login'."
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Check Qwen OAuth token expiry and refresh if needed
|
|
127
|
+
if provider == "qwen":
|
|
128
|
+
oauth_provider = QwenOAuthProvider(TokenStore())
|
|
129
|
+
token = oauth_provider.get_token()
|
|
130
|
+
if not token:
|
|
131
|
+
if not quiet:
|
|
132
|
+
console.print("[yellow]⚠ Qwen authentication not found or expired[/yellow]")
|
|
133
|
+
console.print("[cyan]🔐 Starting automatic authentication...[/cyan]")
|
|
134
|
+
try:
|
|
135
|
+
oauth_provider.initiate_auth(open_browser=True)
|
|
136
|
+
token = oauth_provider.get_token()
|
|
137
|
+
if not token:
|
|
138
|
+
raise AIError.authentication_error(
|
|
139
|
+
"Qwen authentication failed. Run 'gac auth qwen login' to authenticate manually."
|
|
140
|
+
)
|
|
141
|
+
if not quiet:
|
|
142
|
+
console.print("[green]✓ Authentication successful![/green]\n")
|
|
143
|
+
except AIError:
|
|
144
|
+
raise
|
|
145
|
+
except (ValueError, KeyError, json.JSONDecodeError, ConnectionError, OSError) as e:
|
|
146
|
+
raise AIError.authentication_error(
|
|
147
|
+
f"Qwen authentication failed: {e}. Run 'gac auth qwen login' to authenticate manually."
|
|
148
|
+
) from e
|
|
149
|
+
|
|
146
150
|
# Set up spinner
|
|
147
|
-
|
|
151
|
+
if is_group:
|
|
152
|
+
message_type = f"grouped {task_description}s"
|
|
153
|
+
else:
|
|
154
|
+
message_type = task_description
|
|
155
|
+
|
|
148
156
|
if quiet:
|
|
149
157
|
spinner = None
|
|
150
158
|
else:
|
|
151
|
-
spinner =
|
|
159
|
+
spinner = Status(f"Generating {message_type} with {provider} {model_name}...")
|
|
152
160
|
spinner.start()
|
|
153
161
|
|
|
154
|
-
last_exception = None
|
|
162
|
+
last_exception: Exception | None = None
|
|
155
163
|
last_error_type = "unknown"
|
|
156
164
|
|
|
157
165
|
for attempt in range(max_retries):
|
|
158
166
|
try:
|
|
159
167
|
if not quiet and not skip_success_message and attempt > 0:
|
|
160
168
|
if spinner:
|
|
161
|
-
spinner.
|
|
169
|
+
spinner.update(f"Retry {attempt + 1}/{max_retries} with {provider} {model_name}...")
|
|
162
170
|
logger.info(f"Retry attempt {attempt + 1}/{max_retries}")
|
|
163
171
|
|
|
164
172
|
# Call the appropriate provider function
|
|
@@ -172,54 +180,50 @@ def generate_with_retries(
|
|
|
172
180
|
if skip_success_message:
|
|
173
181
|
spinner.stop() # Stop spinner without showing success/failure
|
|
174
182
|
else:
|
|
175
|
-
spinner.
|
|
183
|
+
spinner.stop()
|
|
184
|
+
console.print(f"✓ Generated {message_type} with {provider} {model_name}")
|
|
176
185
|
|
|
177
186
|
if content is not None and content.strip():
|
|
178
|
-
return content.strip()
|
|
187
|
+
return content.strip()
|
|
179
188
|
else:
|
|
180
189
|
logger.warning(f"Empty or None content received from {provider} {model_name}: {repr(content)}")
|
|
181
190
|
raise AIError.model_error("Empty response from AI model")
|
|
182
191
|
|
|
183
|
-
except
|
|
192
|
+
except AIError as e:
|
|
184
193
|
last_exception = e
|
|
185
|
-
error_type =
|
|
194
|
+
error_type = e.error_type
|
|
186
195
|
last_error_type = error_type
|
|
187
196
|
|
|
188
197
|
# For authentication and model errors, don't retry
|
|
189
198
|
if error_type in ["authentication", "model"]:
|
|
190
199
|
if spinner and not skip_success_message:
|
|
191
|
-
spinner.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if error_type == "authentication":
|
|
195
|
-
raise AIError.authentication_error(f"AI generation failed: {str(e)}") from e
|
|
196
|
-
elif error_type == "model":
|
|
197
|
-
raise AIError.model_error(f"AI generation failed: {str(e)}") from e
|
|
200
|
+
spinner.stop()
|
|
201
|
+
console.print(f"✗ Failed to generate {message_type} with {provider} {model_name}")
|
|
202
|
+
raise
|
|
198
203
|
|
|
199
204
|
if attempt < max_retries - 1:
|
|
200
205
|
# Exponential backoff
|
|
201
206
|
wait_time = 2**attempt
|
|
202
207
|
if not quiet and not skip_success_message:
|
|
203
208
|
if attempt == 0:
|
|
204
|
-
logger.warning(f"AI generation failed, retrying in {wait_time}s: {
|
|
209
|
+
logger.warning(f"AI generation failed, retrying in {wait_time}s: {e}")
|
|
205
210
|
else:
|
|
206
|
-
logger.warning(
|
|
207
|
-
f"AI generation failed (attempt {attempt + 1}), retrying in {wait_time}s: {str(e)}"
|
|
208
|
-
)
|
|
211
|
+
logger.warning(f"AI generation failed (attempt {attempt + 1}), retrying in {wait_time}s: {e}")
|
|
209
212
|
|
|
210
213
|
if spinner and not skip_success_message:
|
|
211
214
|
for i in range(wait_time, 0, -1):
|
|
212
|
-
spinner.
|
|
215
|
+
spinner.update(f"Retry {attempt + 1}/{max_retries} in {i}s...")
|
|
213
216
|
time.sleep(1)
|
|
214
217
|
else:
|
|
215
218
|
time.sleep(wait_time)
|
|
216
219
|
else:
|
|
217
220
|
num_retries = max_retries
|
|
218
221
|
retry_word = "retry" if num_retries == 1 else "retries"
|
|
219
|
-
logger.error(f"AI generation failed after {num_retries} {retry_word}: {
|
|
222
|
+
logger.error(f"AI generation failed after {num_retries} {retry_word}: {e}")
|
|
220
223
|
|
|
221
224
|
if spinner and not skip_success_message:
|
|
222
|
-
spinner.
|
|
225
|
+
spinner.stop()
|
|
226
|
+
console.print(f"✗ Failed to generate {message_type} with {provider} {model_name}")
|
|
223
227
|
|
|
224
228
|
# If we get here, all retries failed - use the last classified error type
|
|
225
229
|
num_retries = max_retries
|