gac 1.11.1__tar.gz → 1.12.1__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-1.11.1 → gac-1.12.1}/PKG-INFO +2 -2
- {gac-1.11.1 → gac-1.12.1}/README.md +1 -1
- {gac-1.11.1 → gac-1.12.1}/src/gac/__version__.py +1 -1
- {gac-1.11.1 → gac-1.12.1}/src/gac/ai.py +2 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/ai_utils.py +1 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/init_cli.py +1 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/providers/__init__.py +2 -0
- gac-1.12.1/src/gac/providers/deepseek.py +38 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/providers/gemini.py +35 -22
- {gac-1.11.1 → gac-1.12.1}/.gitignore +0 -0
- {gac-1.11.1 → gac-1.12.1}/LICENSE +0 -0
- {gac-1.11.1 → gac-1.12.1}/pyproject.toml +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/__init__.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/cli.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/config.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/config_cli.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/constants.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/diff_cli.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/errors.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/git.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/main.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/preprocess.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/prompt.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/providers/anthropic.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/providers/cerebras.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/providers/chutes.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/providers/fireworks.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/providers/groq.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/providers/lmstudio.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/providers/minimax.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/providers/ollama.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/providers/openai.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/providers/openrouter.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/providers/streamlake.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/providers/synthetic.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/providers/together.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/providers/zai.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/security.py +0 -0
- {gac-1.11.1 → gac-1.12.1}/src/gac/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gac
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.12.1
|
|
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
|
|
@@ -91,7 +91,7 @@ gac
|
|
|
91
91
|
|
|
92
92
|
### 🌐 **Supported Providers**
|
|
93
93
|
|
|
94
|
-
- **Anthropic** • **Cerebras** • **Chutes.ai** • **Fireworks** • **Gemini**
|
|
94
|
+
- **Anthropic** • **Cerebras** • **Chutes.ai** • **DeepSeek** • **Fireworks** • **Gemini**
|
|
95
95
|
- **Groq** • **LM Studio** • **MiniMax** • **Ollama** • **OpenAI** • **OpenRouter**
|
|
96
96
|
- **Streamlake** • **Synthetic.new** • **Together AI** • **Z.AI** • **Z.AI Coding**
|
|
97
97
|
|
|
@@ -50,7 +50,7 @@ gac
|
|
|
50
50
|
|
|
51
51
|
### 🌐 **Supported Providers**
|
|
52
52
|
|
|
53
|
-
- **Anthropic** • **Cerebras** • **Chutes.ai** • **Fireworks** • **Gemini**
|
|
53
|
+
- **Anthropic** • **Cerebras** • **Chutes.ai** • **DeepSeek** • **Fireworks** • **Gemini**
|
|
54
54
|
- **Groq** • **LM Studio** • **MiniMax** • **Ollama** • **OpenAI** • **OpenRouter**
|
|
55
55
|
- **Streamlake** • **Synthetic.new** • **Together AI** • **Z.AI** • **Z.AI Coding**
|
|
56
56
|
|
|
@@ -13,6 +13,7 @@ from gac.providers import (
|
|
|
13
13
|
call_anthropic_api,
|
|
14
14
|
call_cerebras_api,
|
|
15
15
|
call_chutes_api,
|
|
16
|
+
call_deepseek_api,
|
|
16
17
|
call_fireworks_api,
|
|
17
18
|
call_gemini_api,
|
|
18
19
|
call_groq_api,
|
|
@@ -83,6 +84,7 @@ def generate_commit_message(
|
|
|
83
84
|
"anthropic": call_anthropic_api,
|
|
84
85
|
"cerebras": call_cerebras_api,
|
|
85
86
|
"chutes": call_chutes_api,
|
|
87
|
+
"deepseek": call_deepseek_api,
|
|
86
88
|
"fireworks": call_fireworks_api,
|
|
87
89
|
"gemini": call_gemini_api,
|
|
88
90
|
"groq": call_groq_api,
|
|
@@ -35,6 +35,7 @@ def init() -> None:
|
|
|
35
35
|
("Anthropic", "claude-haiku-4-5"),
|
|
36
36
|
("Cerebras", "qwen-3-coder-480b"),
|
|
37
37
|
("Chutes", "zai-org/GLM-4.6-FP8"),
|
|
38
|
+
("DeepSeek", "deepseek-chat"),
|
|
38
39
|
("Fireworks", "accounts/fireworks/models/gpt-oss-20b"),
|
|
39
40
|
("Gemini", "gemini-2.5-flash"),
|
|
40
41
|
("Groq", "meta-llama/llama-4-maverick-17b-128e-instruct"),
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from .anthropic import call_anthropic_api
|
|
4
4
|
from .cerebras import call_cerebras_api
|
|
5
5
|
from .chutes import call_chutes_api
|
|
6
|
+
from .deepseek import call_deepseek_api
|
|
6
7
|
from .fireworks import call_fireworks_api
|
|
7
8
|
from .gemini import call_gemini_api
|
|
8
9
|
from .groq import call_groq_api
|
|
@@ -20,6 +21,7 @@ __all__ = [
|
|
|
20
21
|
"call_anthropic_api",
|
|
21
22
|
"call_cerebras_api",
|
|
22
23
|
"call_chutes_api",
|
|
24
|
+
"call_deepseek_api",
|
|
23
25
|
"call_fireworks_api",
|
|
24
26
|
"call_gemini_api",
|
|
25
27
|
"call_groq_api",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""DeepSeek API provider for gac."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from gac.errors import AIError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def call_deepseek_api(model: str, messages: list[dict], temperature: float, max_tokens: int) -> str:
|
|
11
|
+
"""Call DeepSeek API directly."""
|
|
12
|
+
api_key = os.getenv("DEEPSEEK_API_KEY")
|
|
13
|
+
if not api_key:
|
|
14
|
+
raise AIError.authentication_error("DEEPSEEK_API_KEY not found in environment variables")
|
|
15
|
+
|
|
16
|
+
url = "https://api.deepseek.com/v1/chat/completions"
|
|
17
|
+
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
|
18
|
+
|
|
19
|
+
data = {"model": model, "messages": messages, "temperature": temperature, "max_tokens": max_tokens}
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
response = httpx.post(url, headers=headers, json=data, timeout=120)
|
|
23
|
+
response.raise_for_status()
|
|
24
|
+
response_data = response.json()
|
|
25
|
+
content = response_data["choices"][0]["message"]["content"]
|
|
26
|
+
if content is None:
|
|
27
|
+
raise AIError.model_error("DeepSeek API returned null content")
|
|
28
|
+
if content == "":
|
|
29
|
+
raise AIError.model_error("DeepSeek API returned empty content")
|
|
30
|
+
return content
|
|
31
|
+
except httpx.HTTPStatusError as e:
|
|
32
|
+
if e.response.status_code == 429:
|
|
33
|
+
raise AIError.rate_limit_error(f"DeepSeek API rate limit exceeded: {e.response.text}") from e
|
|
34
|
+
raise AIError.model_error(f"DeepSeek API error: {e.response.status_code} - {e.response.text}") from e
|
|
35
|
+
except httpx.TimeoutException as e:
|
|
36
|
+
raise AIError.timeout_error(f"DeepSeek API request timed out: {str(e)}") from e
|
|
37
|
+
except Exception as e:
|
|
38
|
+
raise AIError.model_error(f"Error calling DeepSeek API: {str(e)}") from e
|
|
@@ -16,32 +16,37 @@ def call_gemini_api(model: str, messages: list[dict[str, Any]], temperature: flo
|
|
|
16
16
|
|
|
17
17
|
url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent"
|
|
18
18
|
|
|
19
|
-
# Build
|
|
20
|
-
contents = []
|
|
19
|
+
# Build Gemini request payload, converting roles to supported values.
|
|
20
|
+
contents: list[dict[str, Any]] = []
|
|
21
|
+
system_instruction_parts: list[dict[str, str]] = []
|
|
21
22
|
|
|
22
|
-
# Add system instruction as first content with role "system" (2025 format)
|
|
23
23
|
for msg in messages:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
role = msg.get("role")
|
|
25
|
+
content_value = msg.get("content")
|
|
26
|
+
content = "" if content_value is None else str(content_value)
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
)
|
|
28
|
+
if role == "system":
|
|
29
|
+
if content.strip():
|
|
30
|
+
system_instruction_parts.append({"text": content})
|
|
31
|
+
continue
|
|
32
|
+
|
|
33
|
+
if role == "assistant":
|
|
34
|
+
gemini_role = "model"
|
|
35
|
+
elif role == "user":
|
|
36
|
+
gemini_role = "user"
|
|
37
|
+
else:
|
|
38
|
+
raise AIError.model_error(f"Unsupported message role for Gemini API: {role}")
|
|
39
|
+
|
|
40
|
+
contents.append({"role": gemini_role, "parts": [{"text": content}]})
|
|
39
41
|
|
|
40
42
|
payload: dict[str, Any] = {
|
|
41
43
|
"contents": contents,
|
|
42
44
|
"generationConfig": {"temperature": temperature, "maxOutputTokens": max_tokens},
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
if system_instruction_parts:
|
|
48
|
+
payload["systemInstruction"] = {"role": "system", "parts": system_instruction_parts}
|
|
49
|
+
|
|
45
50
|
headers = {"x-goog-api-key": api_key, "Content-Type": "application/json"}
|
|
46
51
|
|
|
47
52
|
try:
|
|
@@ -50,18 +55,26 @@ def call_gemini_api(model: str, messages: list[dict[str, Any]], temperature: flo
|
|
|
50
55
|
response_data = response.json()
|
|
51
56
|
|
|
52
57
|
# Check for candidates and proper response structure
|
|
53
|
-
|
|
58
|
+
candidates = response_data.get("candidates")
|
|
59
|
+
if not candidates:
|
|
54
60
|
raise AIError.model_error("Gemini API response missing candidates")
|
|
55
61
|
|
|
56
|
-
candidate =
|
|
62
|
+
candidate = candidates[0]
|
|
57
63
|
if "content" not in candidate or "parts" not in candidate["content"] or not candidate["content"]["parts"]:
|
|
58
64
|
raise AIError.model_error("Gemini API response has invalid content structure")
|
|
59
65
|
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
parts = candidate["content"]["parts"]
|
|
67
|
+
content_text: str | None = None
|
|
68
|
+
for part in parts:
|
|
69
|
+
if isinstance(part, dict):
|
|
70
|
+
part_text = part.get("text")
|
|
71
|
+
if isinstance(part_text, str) and part_text:
|
|
72
|
+
content_text = part_text
|
|
73
|
+
break
|
|
74
|
+
if content_text is None:
|
|
62
75
|
raise AIError.model_error("Gemini API response missing text content")
|
|
63
76
|
|
|
64
|
-
return
|
|
77
|
+
return content_text
|
|
65
78
|
except AIError:
|
|
66
79
|
raise
|
|
67
80
|
except httpx.HTTPStatusError as e:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|