gac 1.5.2__tar.gz → 1.7.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.
- {gac-1.5.2 → gac-1.7.0}/PKG-INFO +7 -5
- {gac-1.5.2 → gac-1.7.0}/README.md +1 -1
- {gac-1.5.2 → gac-1.7.0}/pyproject.toml +10 -3
- {gac-1.5.2 → gac-1.7.0}/src/gac/__version__.py +1 -1
- {gac-1.5.2 → gac-1.7.0}/src/gac/ai.py +6 -4
- {gac-1.5.2 → gac-1.7.0}/src/gac/ai_utils.py +1 -0
- {gac-1.5.2 → gac-1.7.0}/src/gac/cli.py +1 -1
- {gac-1.5.2 → gac-1.7.0}/src/gac/git.py +50 -0
- {gac-1.5.2 → gac-1.7.0}/src/gac/init_cli.py +2 -3
- {gac-1.5.2 → gac-1.7.0}/src/gac/main.py +10 -2
- {gac-1.5.2 → gac-1.7.0}/src/gac/providers/__init__.py +2 -0
- {gac-1.5.2 → gac-1.7.0}/src/gac/providers/anthropic.py +5 -1
- {gac-1.5.2 → gac-1.7.0}/src/gac/providers/cerebras.py +5 -1
- {gac-1.5.2 → gac-1.7.0}/src/gac/providers/gemini.py +5 -1
- {gac-1.5.2 → gac-1.7.0}/src/gac/providers/groq.py +5 -1
- {gac-1.5.2 → gac-1.7.0}/src/gac/providers/lmstudio.py +4 -0
- {gac-1.5.2 → gac-1.7.0}/src/gac/providers/ollama.py +4 -0
- {gac-1.5.2 → gac-1.7.0}/src/gac/providers/openai.py +5 -1
- {gac-1.5.2 → gac-1.7.0}/src/gac/providers/openrouter.py +3 -1
- {gac-1.5.2 → gac-1.7.0}/src/gac/providers/streamlake.py +5 -1
- gac-1.7.0/src/gac/providers/synthetic.py +42 -0
- {gac-1.5.2 → gac-1.7.0}/src/gac/providers/zai.py +5 -1
- {gac-1.5.2 → gac-1.7.0}/.gitignore +0 -0
- {gac-1.5.2 → gac-1.7.0}/LICENSE +0 -0
- {gac-1.5.2 → gac-1.7.0}/src/gac/__init__.py +0 -0
- {gac-1.5.2 → gac-1.7.0}/src/gac/config.py +0 -0
- {gac-1.5.2 → gac-1.7.0}/src/gac/config_cli.py +0 -0
- {gac-1.5.2 → gac-1.7.0}/src/gac/constants.py +0 -0
- {gac-1.5.2 → gac-1.7.0}/src/gac/diff_cli.py +0 -0
- {gac-1.5.2 → gac-1.7.0}/src/gac/errors.py +0 -0
- {gac-1.5.2 → gac-1.7.0}/src/gac/preprocess.py +0 -0
- {gac-1.5.2 → gac-1.7.0}/src/gac/prompt.py +0 -0
- {gac-1.5.2 → gac-1.7.0}/src/gac/security.py +0 -0
- {gac-1.5.2 → gac-1.7.0}/src/gac/utils.py +0 -0
{gac-1.5.2 → gac-1.7.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gac
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: AI-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
|
|
@@ -17,23 +17,25 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
21
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
22
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
22
23
|
Requires-Python: >=3.10
|
|
23
24
|
Requires-Dist: anthropic>=0.68.0
|
|
24
25
|
Requires-Dist: click>=8.3.0
|
|
25
26
|
Requires-Dist: halo
|
|
27
|
+
Requires-Dist: httpcore>=1.0.9
|
|
26
28
|
Requires-Dist: httpx>=0.28.0
|
|
27
|
-
Requires-Dist: pydantic>=2.
|
|
29
|
+
Requires-Dist: pydantic>=2.12.0
|
|
28
30
|
Requires-Dist: python-dotenv>=1.1.1
|
|
29
31
|
Requires-Dist: questionary
|
|
30
32
|
Requires-Dist: rich>=14.1.0
|
|
31
33
|
Requires-Dist: sumy
|
|
32
|
-
Requires-Dist: tiktoken>=0.
|
|
34
|
+
Requires-Dist: tiktoken>=0.12.0
|
|
33
35
|
Provides-Extra: dev
|
|
34
36
|
Requires-Dist: build; extra == 'dev'
|
|
35
|
-
Requires-Dist: bump-my-version; extra == 'dev'
|
|
36
37
|
Requires-Dist: codecov; extra == 'dev'
|
|
38
|
+
Requires-Dist: pre-commit; extra == 'dev'
|
|
37
39
|
Requires-Dist: pytest; extra == 'dev'
|
|
38
40
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
39
41
|
Requires-Dist: ruff; extra == 'dev'
|
|
@@ -45,7 +47,7 @@ Description-Content-Type: text/markdown
|
|
|
45
47
|
# Git Auto Commit (gac)
|
|
46
48
|
|
|
47
49
|
[](https://pypi.org/project/gac/)
|
|
48
|
-
[](https://www.python.org/downloads/)
|
|
50
|
+
[](https://www.python.org/downloads/)
|
|
49
51
|
[](https://github.com/cellwebb/gac/actions)
|
|
50
52
|
[](https://app.codecov.io/gh/cellwebb/gac)
|
|
51
53
|
[](https://github.com/psf/black)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Git Auto Commit (gac)
|
|
4
4
|
|
|
5
5
|
[](https://pypi.org/project/gac/)
|
|
6
|
-
[](https://www.python.org/downloads/)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
7
|
[](https://github.com/cellwebb/gac/actions)
|
|
8
8
|
[](https://app.codecov.io/gh/cellwebb/gac)
|
|
9
9
|
[](https://github.com/psf/black)
|
|
@@ -20,21 +20,23 @@ classifiers = [
|
|
|
20
20
|
"Programming Language :: Python :: 3.11",
|
|
21
21
|
"Programming Language :: Python :: 3.12",
|
|
22
22
|
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Programming Language :: Python :: 3.14",
|
|
23
24
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
24
25
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
25
26
|
]
|
|
26
27
|
dependencies = [
|
|
27
28
|
# HTTP client for AI provider APIs
|
|
28
29
|
"httpx>=0.28.0",
|
|
30
|
+
"httpcore>=1.0.9", # Required for Python 3.14 compatibility
|
|
29
31
|
|
|
30
32
|
# Anthropic SDK (token counting)
|
|
31
33
|
"anthropic>=0.68.0",
|
|
32
34
|
|
|
33
35
|
# Token counting (OpenAI models)
|
|
34
|
-
"tiktoken>=0.
|
|
36
|
+
"tiktoken>=0.12.0",
|
|
35
37
|
|
|
36
38
|
# Core functionality
|
|
37
|
-
"pydantic>=2.
|
|
39
|
+
"pydantic>=2.12.0",
|
|
38
40
|
"python-dotenv>=1.1.1",
|
|
39
41
|
|
|
40
42
|
# CLI and formatting
|
|
@@ -53,7 +55,9 @@ gac = "gac.cli:cli"
|
|
|
53
55
|
[project.optional-dependencies]
|
|
54
56
|
dev = [
|
|
55
57
|
# Version management
|
|
56
|
-
|
|
58
|
+
# Note: bump-my-version has Python 3.14 compatibility issues
|
|
59
|
+
# Use manual version bumping for now
|
|
60
|
+
# "bump-my-version",
|
|
57
61
|
|
|
58
62
|
# Testing
|
|
59
63
|
"pytest",
|
|
@@ -62,6 +66,7 @@ dev = [
|
|
|
62
66
|
|
|
63
67
|
# Linting and formatting
|
|
64
68
|
"ruff",
|
|
69
|
+
"pre-commit",
|
|
65
70
|
|
|
66
71
|
# Release tools
|
|
67
72
|
"build",
|
|
@@ -150,6 +155,7 @@ dependencies = [
|
|
|
150
155
|
|
|
151
156
|
# Linting and formatting
|
|
152
157
|
"ruff",
|
|
158
|
+
"pre-commit",
|
|
153
159
|
]
|
|
154
160
|
|
|
155
161
|
[tool.hatch.envs.default.scripts]
|
|
@@ -208,4 +214,5 @@ dev = [
|
|
|
208
214
|
"pytest>=8.4.2",
|
|
209
215
|
"pytest-asyncio>=1.2.0",
|
|
210
216
|
"pytest-cov>=7.0.0",
|
|
217
|
+
"pre-commit",
|
|
211
218
|
]
|
|
@@ -19,6 +19,7 @@ from gac.providers import (
|
|
|
19
19
|
call_openai_api,
|
|
20
20
|
call_openrouter_api,
|
|
21
21
|
call_streamlake_api,
|
|
22
|
+
call_synthetic_api,
|
|
22
23
|
call_zai_api,
|
|
23
24
|
call_zai_coding_api,
|
|
24
25
|
)
|
|
@@ -67,16 +68,17 @@ def generate_commit_message(
|
|
|
67
68
|
# Provider functions mapping
|
|
68
69
|
provider_funcs = {
|
|
69
70
|
"anthropic": call_anthropic_api,
|
|
70
|
-
"openai": call_openai_api,
|
|
71
|
-
"groq": call_groq_api,
|
|
72
71
|
"cerebras": call_cerebras_api,
|
|
72
|
+
"gemini": call_gemini_api,
|
|
73
|
+
"groq": call_groq_api,
|
|
74
|
+
"lmstudio": call_lmstudio_api,
|
|
73
75
|
"ollama": call_ollama_api,
|
|
76
|
+
"openai": call_openai_api,
|
|
74
77
|
"openrouter": call_openrouter_api,
|
|
75
78
|
"streamlake": call_streamlake_api,
|
|
79
|
+
"synthetic": call_synthetic_api,
|
|
76
80
|
"zai": call_zai_api,
|
|
77
81
|
"zai-coding": call_zai_coding_api,
|
|
78
|
-
"gemini": call_gemini_api,
|
|
79
|
-
"lmstudio": call_lmstudio_api,
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
# Generate the commit message using centralized retry logic
|
|
@@ -53,7 +53,7 @@ logger = logging.getLogger(__name__)
|
|
|
53
53
|
help=f"Set log level (default: {config['log_level']})",
|
|
54
54
|
)
|
|
55
55
|
# Advanced options
|
|
56
|
-
@click.option("--no-verify", is_flag=True, help="Skip pre-commit hooks when committing")
|
|
56
|
+
@click.option("--no-verify", is_flag=True, help="Skip pre-commit and lefthook hooks when committing")
|
|
57
57
|
@click.option("--skip-secret-scan", is_flag=True, help="Skip security scan for secrets in staged changes")
|
|
58
58
|
# Other options
|
|
59
59
|
@click.option("--version", is_flag=True, help="Show the version of the Git Auto Commit (gac) tool")
|
|
@@ -153,6 +153,56 @@ def run_pre_commit_hooks() -> bool:
|
|
|
153
153
|
return True
|
|
154
154
|
|
|
155
155
|
|
|
156
|
+
def run_lefthook_hooks() -> bool:
|
|
157
|
+
"""Run Lefthook hooks if they exist.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
True if Lefthook hooks passed or don't exist, False if they failed.
|
|
161
|
+
"""
|
|
162
|
+
# Check for common Lefthook configuration files
|
|
163
|
+
lefthook_configs = [".lefthook.yml", "lefthook.yml", ".lefthook.yaml", "lefthook.yaml"]
|
|
164
|
+
config_exists = any(os.path.exists(config) for config in lefthook_configs)
|
|
165
|
+
|
|
166
|
+
if not config_exists:
|
|
167
|
+
logger.debug("No Lefthook configuration found, skipping Lefthook hooks")
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
# Check if lefthook is installed and configured
|
|
171
|
+
try:
|
|
172
|
+
# First check if lefthook is installed
|
|
173
|
+
result = run_subprocess(["lefthook", "--version"], silent=True, raise_on_error=False)
|
|
174
|
+
if not result:
|
|
175
|
+
logger.debug("Lefthook not installed, skipping hooks")
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
# Run lefthook hooks on staged files
|
|
179
|
+
logger.info("Running Lefthook hooks...")
|
|
180
|
+
# Run lefthook and capture both stdout and stderr
|
|
181
|
+
result = subprocess.run(["lefthook", "run", "pre-commit"], capture_output=True, text=True, check=False)
|
|
182
|
+
|
|
183
|
+
if result.returncode == 0:
|
|
184
|
+
# All hooks passed
|
|
185
|
+
return True
|
|
186
|
+
else:
|
|
187
|
+
# Lefthook hooks failed - show the output
|
|
188
|
+
output = result.stdout if result.stdout else ""
|
|
189
|
+
error = result.stderr if result.stderr else ""
|
|
190
|
+
|
|
191
|
+
# Combine outputs (lefthook usually outputs to stdout)
|
|
192
|
+
full_output = output + ("\n" + error if error else "")
|
|
193
|
+
|
|
194
|
+
if full_output.strip():
|
|
195
|
+
# Show which hooks failed and why
|
|
196
|
+
logger.error(f"Lefthook hooks failed:\n{full_output}")
|
|
197
|
+
else:
|
|
198
|
+
logger.error(f"Lefthook hooks failed with exit code {result.returncode}")
|
|
199
|
+
return False
|
|
200
|
+
except Exception as e:
|
|
201
|
+
logger.debug(f"Error running Lefthook: {e}")
|
|
202
|
+
# If lefthook isn't available, don't block the commit
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
|
|
156
206
|
def push_changes() -> bool:
|
|
157
207
|
"""Push committed changes to the remote repository."""
|
|
158
208
|
remote_exists = run_git_command(["remote"])
|
|
@@ -41,6 +41,7 @@ def init() -> None:
|
|
|
41
41
|
("OpenAI", "gpt-4.1-mini"),
|
|
42
42
|
("OpenRouter", "openrouter/auto"),
|
|
43
43
|
("Streamlake", ""),
|
|
44
|
+
("Synthetic", "hf:zai-org/GLM-4.6"),
|
|
44
45
|
("Z.AI", "glm-4.5-air"),
|
|
45
46
|
("Z.AI Coding", "glm-4.6"),
|
|
46
47
|
]
|
|
@@ -49,7 +50,7 @@ def init() -> None:
|
|
|
49
50
|
if not provider:
|
|
50
51
|
click.echo("Provider selection cancelled. Exiting.")
|
|
51
52
|
return
|
|
52
|
-
provider_key = provider.lower().replace(".", "").replace(" ", "-")
|
|
53
|
+
provider_key = provider.lower().replace(".", "").replace(" ", "-").replace("syntheticnew", "synthetic")
|
|
53
54
|
|
|
54
55
|
is_ollama = provider_key == "ollama"
|
|
55
56
|
is_lmstudio = provider_key == "lm-studio"
|
|
@@ -104,8 +105,6 @@ def init() -> None:
|
|
|
104
105
|
if api_key:
|
|
105
106
|
if is_zai:
|
|
106
107
|
api_key_name = "ZAI_API_KEY"
|
|
107
|
-
elif is_lmstudio:
|
|
108
|
-
api_key_name = "LMSTUDIO_API_KEY"
|
|
109
108
|
else:
|
|
110
109
|
api_key_name = f"{provider_key.upper()}_API_KEY"
|
|
111
110
|
set_key(str(GAC_ENV_PATH), api_key_name, api_key)
|
|
@@ -19,6 +19,7 @@ from gac.git import (
|
|
|
19
19
|
get_staged_files,
|
|
20
20
|
push_changes,
|
|
21
21
|
run_git_command,
|
|
22
|
+
run_lefthook_hooks,
|
|
22
23
|
run_pre_commit_hooks,
|
|
23
24
|
)
|
|
24
25
|
from gac.preprocess import preprocess_diff
|
|
@@ -80,11 +81,18 @@ def main(
|
|
|
80
81
|
)
|
|
81
82
|
sys.exit(0)
|
|
82
83
|
|
|
83
|
-
# Run pre-commit hooks before doing expensive operations
|
|
84
|
+
# Run pre-commit and lefthook hooks before doing expensive operations
|
|
84
85
|
if not no_verify and not dry_run:
|
|
86
|
+
# Run lefthook hooks
|
|
87
|
+
if not run_lefthook_hooks():
|
|
88
|
+
console.print("[red]Lefthook hooks failed. Please fix the issues and try again.[/red]")
|
|
89
|
+
console.print("[yellow]You can use --no-verify to skip pre-commit and lefthook hooks.[/yellow]")
|
|
90
|
+
sys.exit(1)
|
|
91
|
+
|
|
92
|
+
# Run pre-commit hooks
|
|
85
93
|
if not run_pre_commit_hooks():
|
|
86
94
|
console.print("[red]Pre-commit hooks failed. Please fix the issues and try again.[/red]")
|
|
87
|
-
console.print("[yellow]You can use --no-verify to skip pre-commit hooks.[/yellow]")
|
|
95
|
+
console.print("[yellow]You can use --no-verify to skip pre-commit and lefthook hooks.[/yellow]")
|
|
88
96
|
sys.exit(1)
|
|
89
97
|
|
|
90
98
|
status = run_git_command(["status"])
|
|
@@ -9,6 +9,7 @@ from .ollama import call_ollama_api
|
|
|
9
9
|
from .openai import call_openai_api
|
|
10
10
|
from .openrouter import call_openrouter_api
|
|
11
11
|
from .streamlake import call_streamlake_api
|
|
12
|
+
from .synthetic import call_synthetic_api
|
|
12
13
|
from .zai import call_zai_api, call_zai_coding_api
|
|
13
14
|
|
|
14
15
|
__all__ = [
|
|
@@ -21,6 +22,7 @@ __all__ = [
|
|
|
21
22
|
"call_openai_api",
|
|
22
23
|
"call_openrouter_api",
|
|
23
24
|
"call_streamlake_api",
|
|
25
|
+
"call_synthetic_api",
|
|
24
26
|
"call_zai_api",
|
|
25
27
|
"call_zai_coding_api",
|
|
26
28
|
]
|
|
@@ -11,7 +11,7 @@ def call_anthropic_api(model: str, messages: list[dict], temperature: float, max
|
|
|
11
11
|
"""Call Anthropic API directly."""
|
|
12
12
|
api_key = os.getenv("ANTHROPIC_API_KEY")
|
|
13
13
|
if not api_key:
|
|
14
|
-
raise AIError.
|
|
14
|
+
raise AIError.authentication_error("ANTHROPIC_API_KEY not found in environment variables")
|
|
15
15
|
|
|
16
16
|
url = "https://api.anthropic.com/v1/messages"
|
|
17
17
|
headers = {"x-api-key": api_key, "anthropic-version": "2023-06-01", "content-type": "application/json"}
|
|
@@ -42,6 +42,10 @@ def call_anthropic_api(model: str, messages: list[dict], temperature: float, max
|
|
|
42
42
|
raise AIError.model_error("Anthropic API returned empty content")
|
|
43
43
|
return content
|
|
44
44
|
except httpx.HTTPStatusError as e:
|
|
45
|
+
if e.response.status_code == 429:
|
|
46
|
+
raise AIError.rate_limit_error(f"Anthropic API rate limit exceeded: {e.response.text}") from e
|
|
45
47
|
raise AIError.model_error(f"Anthropic API error: {e.response.status_code} - {e.response.text}") from e
|
|
48
|
+
except httpx.TimeoutException as e:
|
|
49
|
+
raise AIError.timeout_error(f"Anthropic API request timed out: {str(e)}") from e
|
|
46
50
|
except Exception as e:
|
|
47
51
|
raise AIError.model_error(f"Error calling Anthropic API: {str(e)}") from e
|
|
@@ -11,7 +11,7 @@ def call_cerebras_api(model: str, messages: list[dict], temperature: float, max_
|
|
|
11
11
|
"""Call Cerebras API directly."""
|
|
12
12
|
api_key = os.getenv("CEREBRAS_API_KEY")
|
|
13
13
|
if not api_key:
|
|
14
|
-
raise AIError.
|
|
14
|
+
raise AIError.authentication_error("CEREBRAS_API_KEY not found in environment variables")
|
|
15
15
|
|
|
16
16
|
url = "https://api.cerebras.ai/v1/chat/completions"
|
|
17
17
|
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
|
@@ -29,6 +29,10 @@ def call_cerebras_api(model: str, messages: list[dict], temperature: float, max_
|
|
|
29
29
|
raise AIError.model_error("Cerebras API returned empty content")
|
|
30
30
|
return content
|
|
31
31
|
except httpx.HTTPStatusError as e:
|
|
32
|
+
if e.response.status_code == 429:
|
|
33
|
+
raise AIError.rate_limit_error(f"Cerebras API rate limit exceeded: {e.response.text}") from e
|
|
32
34
|
raise AIError.model_error(f"Cerebras API error: {e.response.status_code} - {e.response.text}") from e
|
|
35
|
+
except httpx.TimeoutException as e:
|
|
36
|
+
raise AIError.timeout_error(f"Cerebras API request timed out: {str(e)}") from e
|
|
33
37
|
except Exception as e:
|
|
34
38
|
raise AIError.model_error(f"Error calling Cerebras API: {str(e)}") from e
|
|
@@ -12,7 +12,7 @@ def call_gemini_api(model: str, messages: list[dict[str, Any]], temperature: flo
|
|
|
12
12
|
"""Call Gemini API directly."""
|
|
13
13
|
api_key = os.getenv("GEMINI_API_KEY")
|
|
14
14
|
if not api_key:
|
|
15
|
-
raise AIError.
|
|
15
|
+
raise AIError.authentication_error("GEMINI_API_KEY not found in environment variables")
|
|
16
16
|
|
|
17
17
|
url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent"
|
|
18
18
|
|
|
@@ -65,6 +65,10 @@ def call_gemini_api(model: str, messages: list[dict[str, Any]], temperature: flo
|
|
|
65
65
|
except AIError:
|
|
66
66
|
raise
|
|
67
67
|
except httpx.HTTPStatusError as e:
|
|
68
|
+
if e.response.status_code == 429:
|
|
69
|
+
raise AIError.rate_limit_error(f"Gemini API rate limit exceeded: {e.response.text}") from e
|
|
68
70
|
raise AIError.model_error(f"Gemini API error: {e.response.status_code} - {e.response.text}") from e
|
|
71
|
+
except httpx.TimeoutException as e:
|
|
72
|
+
raise AIError.timeout_error(f"Gemini API request timed out: {str(e)}") from e
|
|
69
73
|
except Exception as e:
|
|
70
74
|
raise AIError.model_error(f"Error calling Gemini API: {str(e)}") from e
|
|
@@ -14,7 +14,7 @@ def call_groq_api(model: str, messages: list[dict], temperature: float, max_toke
|
|
|
14
14
|
"""Call Groq API directly."""
|
|
15
15
|
api_key = os.getenv("GROQ_API_KEY")
|
|
16
16
|
if not api_key:
|
|
17
|
-
raise AIError.
|
|
17
|
+
raise AIError.authentication_error("GROQ_API_KEY not found in environment variables")
|
|
18
18
|
|
|
19
19
|
url = "https://api.groq.com/openai/v1/chat/completions"
|
|
20
20
|
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
|
@@ -54,6 +54,10 @@ def call_groq_api(model: str, messages: list[dict], temperature: float, max_toke
|
|
|
54
54
|
logger.error(f"Unexpected response format from Groq API: {response_data}")
|
|
55
55
|
raise AIError.model_error(f"Unexpected response format from Groq API: {response_data}")
|
|
56
56
|
except httpx.HTTPStatusError as e:
|
|
57
|
+
if e.response.status_code == 429:
|
|
58
|
+
raise AIError.rate_limit_error(f"Groq API rate limit exceeded: {e.response.text}") from e
|
|
57
59
|
raise AIError.model_error(f"Groq API error: {e.response.status_code} - {e.response.text}") from e
|
|
60
|
+
except httpx.TimeoutException as e:
|
|
61
|
+
raise AIError.timeout_error(f"Groq API request timed out: {str(e)}") from e
|
|
58
62
|
except Exception as e:
|
|
59
63
|
raise AIError.model_error(f"Error calling Groq API: {str(e)}") from e
|
|
@@ -50,6 +50,10 @@ def call_lmstudio_api(model: str, messages: list[dict[str, Any]], temperature: f
|
|
|
50
50
|
except httpx.ConnectError as e:
|
|
51
51
|
raise AIError.connection_error(f"LM Studio connection failed: {str(e)}") from e
|
|
52
52
|
except httpx.HTTPStatusError as e:
|
|
53
|
+
if e.response.status_code == 429:
|
|
54
|
+
raise AIError.rate_limit_error(f"LM Studio API rate limit exceeded: {e.response.text}") from e
|
|
53
55
|
raise AIError.model_error(f"LM Studio API error: {e.response.status_code} - {e.response.text}") from e
|
|
56
|
+
except httpx.TimeoutException as e:
|
|
57
|
+
raise AIError.timeout_error(f"LM Studio API request timed out: {str(e)}") from e
|
|
54
58
|
except Exception as e:
|
|
55
59
|
raise AIError.model_error(f"Error calling LM Studio API: {str(e)}") from e
|
|
@@ -41,6 +41,10 @@ def call_ollama_api(model: str, messages: list[dict], temperature: float, max_to
|
|
|
41
41
|
except httpx.ConnectError as e:
|
|
42
42
|
raise AIError.connection_error(f"Ollama connection failed. Make sure Ollama is running: {str(e)}") from e
|
|
43
43
|
except httpx.HTTPStatusError as e:
|
|
44
|
+
if e.response.status_code == 429:
|
|
45
|
+
raise AIError.rate_limit_error(f"Ollama API rate limit exceeded: {e.response.text}") from e
|
|
44
46
|
raise AIError.model_error(f"Ollama API error: {e.response.status_code} - {e.response.text}") from e
|
|
47
|
+
except httpx.TimeoutException as e:
|
|
48
|
+
raise AIError.timeout_error(f"Ollama API request timed out: {str(e)}") from e
|
|
45
49
|
except Exception as e:
|
|
46
50
|
raise AIError.model_error(f"Error calling Ollama API: {str(e)}") from e
|
|
@@ -11,7 +11,7 @@ def call_openai_api(model: str, messages: list[dict], temperature: float, max_to
|
|
|
11
11
|
"""Call OpenAI API directly."""
|
|
12
12
|
api_key = os.getenv("OPENAI_API_KEY")
|
|
13
13
|
if not api_key:
|
|
14
|
-
raise AIError.
|
|
14
|
+
raise AIError.authentication_error("OPENAI_API_KEY not found in environment variables")
|
|
15
15
|
|
|
16
16
|
url = "https://api.openai.com/v1/chat/completions"
|
|
17
17
|
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
|
@@ -29,6 +29,10 @@ def call_openai_api(model: str, messages: list[dict], temperature: float, max_to
|
|
|
29
29
|
raise AIError.model_error("OpenAI API returned empty content")
|
|
30
30
|
return content
|
|
31
31
|
except httpx.HTTPStatusError as e:
|
|
32
|
+
if e.response.status_code == 429:
|
|
33
|
+
raise AIError.rate_limit_error(f"OpenAI API rate limit exceeded: {e.response.text}") from e
|
|
32
34
|
raise AIError.model_error(f"OpenAI API error: {e.response.status_code} - {e.response.text}") from e
|
|
35
|
+
except httpx.TimeoutException as e:
|
|
36
|
+
raise AIError.timeout_error(f"OpenAI API request timed out: {str(e)}") from e
|
|
33
37
|
except Exception as e:
|
|
34
38
|
raise AIError.model_error(f"Error calling OpenAI API: {str(e)}") from e
|
|
@@ -11,7 +11,7 @@ def call_openrouter_api(model: str, messages: list[dict], temperature: float, ma
|
|
|
11
11
|
"""Call OpenRouter API directly."""
|
|
12
12
|
api_key = os.getenv("OPENROUTER_API_KEY")
|
|
13
13
|
if not api_key:
|
|
14
|
-
raise AIError.
|
|
14
|
+
raise AIError.authentication_error("OPENROUTER_API_KEY environment variable not set")
|
|
15
15
|
|
|
16
16
|
url = "https://openrouter.ai/api/v1/chat/completions"
|
|
17
17
|
headers = {
|
|
@@ -52,5 +52,7 @@ def call_openrouter_api(model: str, messages: list[dict], temperature: float, ma
|
|
|
52
52
|
raise AIError.model_error(f"OpenRouter API error: {status_code} - {error_text}") from e
|
|
53
53
|
except httpx.ConnectError as e:
|
|
54
54
|
raise AIError.connection_error(f"OpenRouter API connection error: {str(e)}") from e
|
|
55
|
+
except httpx.TimeoutException as e:
|
|
56
|
+
raise AIError.timeout_error(f"OpenRouter API request timed out: {str(e)}") from e
|
|
55
57
|
except Exception as e:
|
|
56
58
|
raise AIError.model_error(f"Error calling OpenRouter API: {str(e)}") from e
|
|
@@ -11,7 +11,7 @@ def call_streamlake_api(model: str, messages: list[dict], temperature: float, ma
|
|
|
11
11
|
"""Call StreamLake (Vanchin) chat completions API."""
|
|
12
12
|
api_key = os.getenv("STREAMLAKE_API_KEY") or os.getenv("VC_API_KEY")
|
|
13
13
|
if not api_key:
|
|
14
|
-
raise AIError.
|
|
14
|
+
raise AIError.authentication_error(
|
|
15
15
|
"STREAMLAKE_API_KEY not found in environment variables (VC_API_KEY alias also not set)"
|
|
16
16
|
)
|
|
17
17
|
|
|
@@ -42,6 +42,10 @@ def call_streamlake_api(model: str, messages: list[dict], temperature: float, ma
|
|
|
42
42
|
|
|
43
43
|
return content
|
|
44
44
|
except httpx.HTTPStatusError as e:
|
|
45
|
+
if e.response.status_code == 429:
|
|
46
|
+
raise AIError.rate_limit_error(f"StreamLake API rate limit exceeded: {e.response.text}") from e
|
|
45
47
|
raise AIError.model_error(f"StreamLake API error: {e.response.status_code} - {e.response.text}") from e
|
|
48
|
+
except httpx.TimeoutException as e:
|
|
49
|
+
raise AIError.timeout_error(f"StreamLake API request timed out: {str(e)}") from e
|
|
46
50
|
except Exception as e: # noqa: BLE001 - convert to AIError
|
|
47
51
|
raise AIError.model_error(f"Error calling StreamLake API: {str(e)}") from e
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Synthetic.new API provider for gac."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from gac.errors import AIError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def call_synthetic_api(model: str, messages: list[dict], temperature: float, max_tokens: int) -> str:
|
|
11
|
+
"""Call Synthetic API directly."""
|
|
12
|
+
# Handle model names without hf: prefix
|
|
13
|
+
if not model.startswith("hf:"):
|
|
14
|
+
model = f"hf:{model}"
|
|
15
|
+
|
|
16
|
+
api_key = os.getenv("SYNTHETIC_API_KEY") or os.getenv("SYN_API_KEY")
|
|
17
|
+
if not api_key:
|
|
18
|
+
raise AIError.authentication_error("SYNTHETIC_API_KEY or SYN_API_KEY not found in environment variables")
|
|
19
|
+
|
|
20
|
+
url = "https://api.synthetic.new/openai/v1/chat/completions"
|
|
21
|
+
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
|
22
|
+
|
|
23
|
+
data = {"model": model, "messages": messages, "temperature": temperature, "max_completion_tokens": max_tokens}
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
response = httpx.post(url, headers=headers, json=data, timeout=120)
|
|
27
|
+
response.raise_for_status()
|
|
28
|
+
response_data = response.json()
|
|
29
|
+
content = response_data["choices"][0]["message"]["content"]
|
|
30
|
+
if content is None:
|
|
31
|
+
raise AIError.model_error("Synthetic.new API returned null content")
|
|
32
|
+
if content == "":
|
|
33
|
+
raise AIError.model_error("Synthetic.new API returned empty content")
|
|
34
|
+
return content
|
|
35
|
+
except httpx.HTTPStatusError as e:
|
|
36
|
+
if e.response.status_code == 429:
|
|
37
|
+
raise AIError.rate_limit_error(f"Synthetic.new API rate limit exceeded: {e.response.text}") from e
|
|
38
|
+
raise AIError.model_error(f"Synthetic.new API error: {e.response.status_code} - {e.response.text}") from e
|
|
39
|
+
except httpx.TimeoutException as e:
|
|
40
|
+
raise AIError.timeout_error(f"Synthetic.new API request timed out: {str(e)}") from e
|
|
41
|
+
except Exception as e:
|
|
42
|
+
raise AIError.model_error(f"Error calling Synthetic.new API: {str(e)}") from e
|
|
@@ -13,7 +13,7 @@ def _call_zai_api_impl(
|
|
|
13
13
|
"""Internal implementation for Z.AI API calls."""
|
|
14
14
|
api_key = os.getenv("ZAI_API_KEY")
|
|
15
15
|
if not api_key:
|
|
16
|
-
raise AIError.
|
|
16
|
+
raise AIError.authentication_error("ZAI_API_KEY not found in environment variables")
|
|
17
17
|
|
|
18
18
|
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
|
19
19
|
data = {"model": model, "messages": messages, "temperature": temperature, "max_tokens": max_tokens}
|
|
@@ -38,7 +38,11 @@ def _call_zai_api_impl(
|
|
|
38
38
|
else:
|
|
39
39
|
raise AIError.model_error(f"{api_name} API unexpected response structure: {response_data}")
|
|
40
40
|
except httpx.HTTPStatusError as e:
|
|
41
|
+
if e.response.status_code == 429:
|
|
42
|
+
raise AIError.rate_limit_error(f"{api_name} API rate limit exceeded: {e.response.text}") from e
|
|
41
43
|
raise AIError.model_error(f"{api_name} API error: {e.response.status_code} - {e.response.text}") from e
|
|
44
|
+
except httpx.TimeoutException as e:
|
|
45
|
+
raise AIError.timeout_error(f"{api_name} API request timed out: {str(e)}") from e
|
|
42
46
|
except Exception as e:
|
|
43
47
|
raise AIError.model_error(f"Error calling {api_name} API: {str(e)}") from e
|
|
44
48
|
|
|
File without changes
|
{gac-1.5.2 → gac-1.7.0}/LICENSE
RENAMED
|
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
|