gac 1.5.2__tar.gz → 1.6.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.

Potentially problematic release.


This version of gac might be problematic. Click here for more details.

Files changed (34) hide show
  1. {gac-1.5.2 → gac-1.6.0}/PKG-INFO +5 -4
  2. {gac-1.5.2 → gac-1.6.0}/README.md +1 -1
  3. {gac-1.5.2 → gac-1.6.0}/pyproject.toml +3 -2
  4. {gac-1.5.2 → gac-1.6.0}/src/gac/__version__.py +1 -1
  5. {gac-1.5.2 → gac-1.6.0}/src/gac/ai.py +6 -4
  6. {gac-1.5.2 → gac-1.6.0}/src/gac/ai_utils.py +1 -0
  7. {gac-1.5.2 → gac-1.6.0}/src/gac/init_cli.py +2 -3
  8. {gac-1.5.2 → gac-1.6.0}/src/gac/providers/__init__.py +2 -0
  9. {gac-1.5.2 → gac-1.6.0}/src/gac/providers/anthropic.py +5 -1
  10. {gac-1.5.2 → gac-1.6.0}/src/gac/providers/cerebras.py +5 -1
  11. {gac-1.5.2 → gac-1.6.0}/src/gac/providers/gemini.py +5 -1
  12. {gac-1.5.2 → gac-1.6.0}/src/gac/providers/groq.py +5 -1
  13. {gac-1.5.2 → gac-1.6.0}/src/gac/providers/lmstudio.py +4 -0
  14. {gac-1.5.2 → gac-1.6.0}/src/gac/providers/ollama.py +4 -0
  15. {gac-1.5.2 → gac-1.6.0}/src/gac/providers/openai.py +5 -1
  16. {gac-1.5.2 → gac-1.6.0}/src/gac/providers/openrouter.py +3 -1
  17. {gac-1.5.2 → gac-1.6.0}/src/gac/providers/streamlake.py +5 -1
  18. gac-1.6.0/src/gac/providers/synthetic.py +38 -0
  19. {gac-1.5.2 → gac-1.6.0}/src/gac/providers/zai.py +5 -1
  20. {gac-1.5.2 → gac-1.6.0}/.gitignore +0 -0
  21. {gac-1.5.2 → gac-1.6.0}/LICENSE +0 -0
  22. {gac-1.5.2 → gac-1.6.0}/src/gac/__init__.py +0 -0
  23. {gac-1.5.2 → gac-1.6.0}/src/gac/cli.py +0 -0
  24. {gac-1.5.2 → gac-1.6.0}/src/gac/config.py +0 -0
  25. {gac-1.5.2 → gac-1.6.0}/src/gac/config_cli.py +0 -0
  26. {gac-1.5.2 → gac-1.6.0}/src/gac/constants.py +0 -0
  27. {gac-1.5.2 → gac-1.6.0}/src/gac/diff_cli.py +0 -0
  28. {gac-1.5.2 → gac-1.6.0}/src/gac/errors.py +0 -0
  29. {gac-1.5.2 → gac-1.6.0}/src/gac/git.py +0 -0
  30. {gac-1.5.2 → gac-1.6.0}/src/gac/main.py +0 -0
  31. {gac-1.5.2 → gac-1.6.0}/src/gac/preprocess.py +0 -0
  32. {gac-1.5.2 → gac-1.6.0}/src/gac/prompt.py +0 -0
  33. {gac-1.5.2 → gac-1.6.0}/src/gac/security.py +0 -0
  34. {gac-1.5.2 → gac-1.6.0}/src/gac/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gac
3
- Version: 1.5.2
3
+ Version: 1.6.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,6 +17,7 @@ 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
@@ -24,12 +25,12 @@ Requires-Dist: anthropic>=0.68.0
24
25
  Requires-Dist: click>=8.3.0
25
26
  Requires-Dist: halo
26
27
  Requires-Dist: httpx>=0.28.0
27
- Requires-Dist: pydantic>=2.11.9
28
+ Requires-Dist: pydantic>=2.12.0
28
29
  Requires-Dist: python-dotenv>=1.1.1
29
30
  Requires-Dist: questionary
30
31
  Requires-Dist: rich>=14.1.0
31
32
  Requires-Dist: sumy
32
- Requires-Dist: tiktoken>=0.11.0
33
+ Requires-Dist: tiktoken>=0.12.0
33
34
  Provides-Extra: dev
34
35
  Requires-Dist: build; extra == 'dev'
35
36
  Requires-Dist: bump-my-version; extra == 'dev'
@@ -45,7 +46,7 @@ Description-Content-Type: text/markdown
45
46
  # Git Auto Commit (gac)
46
47
 
47
48
  [![PyPI version](https://img.shields.io/pypi/v/gac.svg)](https://pypi.org/project/gac/)
48
- [![Python](https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13-blue.svg)](https://www.python.org/downloads/)
49
+ [![Python](https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13%20|%203.14-blue.svg)](https://www.python.org/downloads/)
49
50
  [![Build Status](https://github.com/cellwebb/gac/actions/workflows/ci.yml/badge.svg)](https://github.com/cellwebb/gac/actions)
50
51
  [![codecov](https://codecov.io/gh/cellwebb/gac/branch/main/graph/badge.svg)](https://app.codecov.io/gh/cellwebb/gac)
51
52
  [![Code Style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
@@ -3,7 +3,7 @@
3
3
  # Git Auto Commit (gac)
4
4
 
5
5
  [![PyPI version](https://img.shields.io/pypi/v/gac.svg)](https://pypi.org/project/gac/)
6
- [![Python](https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13-blue.svg)](https://www.python.org/downloads/)
6
+ [![Python](https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13%20|%203.14-blue.svg)](https://www.python.org/downloads/)
7
7
  [![Build Status](https://github.com/cellwebb/gac/actions/workflows/ci.yml/badge.svg)](https://github.com/cellwebb/gac/actions)
8
8
  [![codecov](https://codecov.io/gh/cellwebb/gac/branch/main/graph/badge.svg)](https://app.codecov.io/gh/cellwebb/gac)
9
9
  [![Code Style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
@@ -20,6 +20,7 @@ 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
  ]
@@ -31,10 +32,10 @@ dependencies = [
31
32
  "anthropic>=0.68.0",
32
33
 
33
34
  # Token counting (OpenAI models)
34
- "tiktoken>=0.11.0",
35
+ "tiktoken>=0.12.0",
35
36
 
36
37
  # Core functionality
37
- "pydantic>=2.11.9",
38
+ "pydantic>=2.12.0",
38
39
  "python-dotenv>=1.1.1",
39
40
 
40
41
  # CLI and formatting
@@ -1,3 +1,3 @@
1
1
  """Version information for gac package."""
2
2
 
3
- __version__ = "1.5.2"
3
+ __version__ = "1.6.0"
@@ -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
@@ -103,6 +103,7 @@ def generate_with_retries(
103
103
  "openai",
104
104
  "openrouter",
105
105
  "streamlake",
106
+ "synthetic",
106
107
  "zai",
107
108
  "zai-coding",
108
109
  ]
@@ -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)
@@ -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.model_error("ANTHROPIC_API_KEY not found in environment variables")
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.model_error("CEREBRAS_API_KEY not found in environment variables")
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.model_error("GEMINI_API_KEY not found in environment variables")
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.model_error("GROQ_API_KEY not found in environment variables")
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.model_error("OPENAI_API_KEY not found in environment variables")
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.model_error("OPENROUTER_API_KEY environment variable not set")
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.model_error(
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,38 @@
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
+ api_key = os.getenv("SYNTHETIC_API_KEY") or os.getenv("SYN_API_KEY")
13
+ if not api_key:
14
+ raise AIError.authentication_error("SYNTHETIC_API_KEY or SYN_API_KEY not found in environment variables")
15
+
16
+ url = "https://api.synthetic.new/openai/v1/chat/completions"
17
+ headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
18
+
19
+ data = {"model": model, "messages": messages, "temperature": temperature, "max_completion_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("Synthetic.new API returned null content")
28
+ if content == "":
29
+ raise AIError.model_error("Synthetic.new 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"Synthetic.new API rate limit exceeded: {e.response.text}") from e
34
+ raise AIError.model_error(f"Synthetic.new API error: {e.response.status_code} - {e.response.text}") from e
35
+ except httpx.TimeoutException as e:
36
+ raise AIError.timeout_error(f"Synthetic.new API request timed out: {str(e)}") from e
37
+ except Exception as e:
38
+ 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.model_error("ZAI_API_KEY not found in environment variables")
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
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