janito 2.15.0__py3-none-any.whl → 2.17.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. janito/agent/setup_agent.py +1 -1
  2. janito/cli/chat_mode/session.py +42 -1
  3. janito/cli/cli_commands/list_drivers.py +118 -93
  4. janito/cli/cli_commands/list_providers_region.py +85 -0
  5. janito/cli/cli_commands/set_api_key.py +16 -5
  6. janito/cli/core/getters.py +7 -3
  7. janito/cli/main_cli.py +38 -3
  8. janito/cli/prompt_setup.py +3 -0
  9. janito/cli/single_shot_mode/handler.py +43 -1
  10. janito/drivers/azure_openai/driver.py +14 -5
  11. janito/drivers/cerebras/__init__.py +1 -0
  12. janito/drivers/openai/driver.py +16 -5
  13. janito/drivers/zai/driver.py +19 -22
  14. janito/formatting_token.py +9 -5
  15. janito/llm/auth_utils.py +21 -0
  16. janito/providers/__init__.py +1 -0
  17. janito/providers/alibaba/provider.py +11 -9
  18. janito/providers/anthropic/provider.py +4 -5
  19. janito/providers/azure_openai/provider.py +4 -5
  20. janito/providers/cerebras/__init__.py +1 -0
  21. janito/providers/cerebras/model_info.py +76 -0
  22. janito/providers/cerebras/provider.py +145 -0
  23. janito/providers/deepseek/provider.py +4 -5
  24. janito/providers/google/provider.py +4 -5
  25. janito/providers/moonshotai/provider.py +46 -37
  26. janito/providers/openai/provider.py +45 -39
  27. janito/providers/zai/provider.py +3 -9
  28. janito/regions/__init__.py +16 -0
  29. janito/regions/cli.py +124 -0
  30. janito/regions/geo_utils.py +240 -0
  31. janito/regions/provider_regions.py +158 -0
  32. {janito-2.15.0.dist-info → janito-2.17.0.dist-info}/METADATA +1 -1
  33. {janito-2.15.0.dist-info → janito-2.17.0.dist-info}/RECORD +37 -27
  34. {janito-2.15.0.dist-info → janito-2.17.0.dist-info}/WHEEL +0 -0
  35. {janito-2.15.0.dist-info → janito-2.17.0.dist-info}/entry_points.txt +0 -0
  36. {janito-2.15.0.dist-info → janito-2.17.0.dist-info}/licenses/LICENSE +0 -0
  37. {janito-2.15.0.dist-info → janito-2.17.0.dist-info}/top_level.txt +0 -0
@@ -22,46 +22,52 @@ class OpenAIProvider(LLMProvider):
22
22
  def __init__(
23
23
  self, auth_manager: LLMAuthManager = None, config: LLMDriverConfig = None
24
24
  ):
25
+ self._tools_adapter = get_local_tools_adapter()
26
+ self._driver = None
27
+
25
28
  if not self.available:
26
- # Even when the OpenAI driver is unavailable we still need a tools adapter
27
- # so that any generic logic that expects `execute_tool()` to work does not
28
- # crash with an AttributeError when it tries to access `self._tools_adapter`.
29
- self._tools_adapter = get_local_tools_adapter()
30
- self._driver = None
31
- else:
32
- self.auth_manager = auth_manager or LLMAuthManager()
33
- self._api_key = self.auth_manager.get_credentials(type(self).NAME)
34
- if not self._api_key:
35
- print(f"[ERROR] No API key found for provider '{self.name}'. Please set the API key using:")
36
- print(f" janito --set-api-key YOUR_API_KEY -p {self.name}")
37
- print(f"Or set the OPENAI_API_KEY environment variable.")
38
- return
39
-
40
- self._tools_adapter = get_local_tools_adapter()
41
- self._driver_config = config or LLMDriverConfig(model=None)
42
- if not self._driver_config.model:
43
- self._driver_config.model = self.DEFAULT_MODEL
44
- if not self._driver_config.api_key:
45
- self._driver_config.api_key = self._api_key
46
- # Set only the correct token parameter for the model
47
- model_name = self._driver_config.model
48
- model_spec = self.MODEL_SPECS.get(model_name)
49
- # Remove both to avoid stale values
50
- if hasattr(self._driver_config, "max_tokens"):
51
- self._driver_config.max_tokens = None
52
- if hasattr(self._driver_config, "max_completion_tokens"):
53
- self._driver_config.max_completion_tokens = None
54
- if model_spec:
55
- if getattr(model_spec, "thinking_supported", False):
56
- max_cot = getattr(model_spec, "max_cot", None)
57
- if max_cot and max_cot != "N/A":
58
- self._driver_config.max_completion_tokens = int(max_cot)
59
- else:
60
- max_response = getattr(model_spec, "max_response", None)
61
- if max_response and max_response != "N/A":
62
- self._driver_config.max_tokens = int(max_response)
63
- self.fill_missing_device_info(self._driver_config)
64
- self._driver = None # to be provided by factory/agent
29
+ return
30
+
31
+ self._initialize_config(auth_manager, config)
32
+ self._setup_model_config()
33
+
34
+ def _initialize_config(self, auth_manager, config):
35
+ """Initialize configuration and API key."""
36
+ self.auth_manager = auth_manager or LLMAuthManager()
37
+ self._api_key = self.auth_manager.get_credentials(type(self).NAME)
38
+ if not self._api_key:
39
+ from janito.llm.auth_utils import handle_missing_api_key
40
+
41
+ handle_missing_api_key(self.name, "OPENAI_API_KEY")
42
+
43
+ self._driver_config = config or LLMDriverConfig(model=None)
44
+ if not self._driver_config.model:
45
+ self._driver_config.model = self.DEFAULT_MODEL
46
+ if not self._driver_config.api_key:
47
+ self._driver_config.api_key = self._api_key
48
+
49
+ def _setup_model_config(self):
50
+ """Configure token limits based on model specifications."""
51
+ model_name = self._driver_config.model
52
+ model_spec = self.MODEL_SPECS.get(model_name)
53
+
54
+ # Reset token parameters
55
+ if hasattr(self._driver_config, "max_tokens"):
56
+ self._driver_config.max_tokens = None
57
+ if hasattr(self._driver_config, "max_completion_tokens"):
58
+ self._driver_config.max_completion_tokens = None
59
+
60
+ if model_spec:
61
+ if getattr(model_spec, "thinking_supported", False):
62
+ max_cot = getattr(model_spec, "max_cot", None)
63
+ if max_cot and max_cot != "N/A":
64
+ self._driver_config.max_completion_tokens = int(max_cot)
65
+ else:
66
+ max_response = getattr(model_spec, "max_response", None)
67
+ if max_response and max_response != "N/A":
68
+ self._driver_config.max_tokens = int(max_response)
69
+
70
+ self.fill_missing_device_info(self._driver_config)
65
71
 
66
72
  @property
67
73
  def driver(self) -> OpenAIModelDriver:
@@ -33,20 +33,14 @@ class ZAIProvider(LLMProvider):
33
33
  # crash with an AttributeError when it tries to access `self._tools_adapter`.
34
34
  self._tools_adapter = get_local_tools_adapter()
35
35
  self._driver = None
36
- # Initialize _driver_config to avoid AttributeError
37
- self._driver_config = LLMDriverConfig(model=None)
38
36
 
39
37
  def _setup_available(self, auth_manager, config):
40
38
  self.auth_manager = auth_manager or LLMAuthManager()
41
39
  self._api_key = self.auth_manager.get_credentials(type(self).NAME)
42
40
  if not self._api_key:
43
- print(
44
- f"[ERROR] No API key found for provider '{self.name}'. Please set the API key using:"
45
- )
46
- print(f" janito --set-api-key YOUR_API_KEY -p {self.name}")
47
- print(f"Or set the ZAI_API_KEY environment variable.")
48
- self._tools_adapter = get_local_tools_adapter()
49
- return
41
+ from janito.llm.auth_utils import handle_missing_api_key
42
+
43
+ handle_missing_api_key(self.name, "ZAI_API_KEY")
50
44
 
51
45
  self._tools_adapter = get_local_tools_adapter()
52
46
  self._driver_config = config or LLMDriverConfig(model=None)
@@ -0,0 +1,16 @@
1
+ """
2
+ Region definitions and geolocation utilities for LLM providers.
3
+
4
+ This module provides static region definitions for various LLM providers
5
+ and utilities to determine optimal API endpoints based on user location.
6
+ """
7
+
8
+ from .provider_regions import PROVIDER_REGIONS, get_optimal_endpoint
9
+ from .geo_utils import get_user_location, get_closest_region
10
+
11
+ __all__ = [
12
+ "PROVIDER_REGIONS",
13
+ "get_optimal_endpoint",
14
+ "get_user_location",
15
+ "get_closest_region",
16
+ ]
janito/regions/cli.py ADDED
@@ -0,0 +1,124 @@
1
+ """
2
+ CLI commands for region management and geolocation utilities.
3
+ """
4
+
5
+ import argparse
6
+ import json
7
+ from typing import Optional
8
+
9
+ from .geo_utils import get_region_info
10
+ from .provider_regions import (
11
+ get_provider_regions,
12
+ get_optimal_endpoint,
13
+ get_all_providers,
14
+ )
15
+
16
+
17
+ def handle_region_info(args=None):
18
+ """Display current region information."""
19
+ info = get_region_info()
20
+ print(json.dumps(info, indent=2))
21
+
22
+
23
+ def handle_provider_regions(args):
24
+ """Display regions for a specific provider."""
25
+ provider = args.provider.lower()
26
+ regions = get_provider_regions(provider)
27
+
28
+ if not regions:
29
+ print(f"Provider '{provider}' not found")
30
+ return
31
+
32
+ result = {
33
+ "provider": provider,
34
+ "regions": [
35
+ {
36
+ "code": r.region_code,
37
+ "name": r.name,
38
+ "endpoint": r.endpoint,
39
+ "location": r.location,
40
+ "priority": r.priority,
41
+ }
42
+ for r in regions
43
+ ],
44
+ }
45
+ print(json.dumps(result, indent=2))
46
+
47
+
48
+ def handle_optimal_endpoint(args):
49
+ """Get optimal endpoint for a provider based on user location."""
50
+ from .geo_utils import get_user_location, get_closest_region
51
+
52
+ country_code, _ = get_user_location()
53
+ major_region = get_closest_region(country_code)
54
+
55
+ provider = args.provider.lower()
56
+ endpoint = get_optimal_endpoint(provider, major_region)
57
+
58
+ if not endpoint:
59
+ print(f"Provider '{provider}' not found")
60
+ return
61
+
62
+ result = {
63
+ "provider": provider,
64
+ "user_region": major_region,
65
+ "country_code": country_code,
66
+ "optimal_endpoint": endpoint,
67
+ }
68
+ print(json.dumps(result, indent=2))
69
+
70
+
71
+ def handle_list_providers(args=None):
72
+ """List all supported providers."""
73
+ providers = get_all_providers()
74
+ result = {"providers": providers, "count": len(providers)}
75
+ print(json.dumps(result, indent=2))
76
+
77
+
78
+ def setup_region_parser(subparsers):
79
+ """Setup region-related CLI commands."""
80
+ region_parser = subparsers.add_parser(
81
+ "region", help="Region and geolocation utilities"
82
+ )
83
+ region_subparsers = region_parser.add_subparsers(
84
+ dest="region_command", help="Region commands"
85
+ )
86
+
87
+ # region info
88
+ info_parser = region_subparsers.add_parser(
89
+ "info", help="Show current region information"
90
+ )
91
+ info_parser.set_defaults(func=handle_region_info)
92
+
93
+ # region providers
94
+ providers_parser = region_subparsers.add_parser(
95
+ "providers", help="List all supported providers"
96
+ )
97
+ providers_parser.set_defaults(func=handle_list_providers)
98
+
99
+ # region list
100
+ list_parser = region_subparsers.add_parser(
101
+ "list", help="List regions for a provider"
102
+ )
103
+ list_parser.add_argument("provider", help="Provider name (e.g., openai, anthropic)")
104
+ list_parser.set_defaults(func=handle_provider_regions)
105
+
106
+ # region endpoint
107
+ endpoint_parser = region_subparsers.add_parser(
108
+ "endpoint", help="Get optimal endpoint for provider"
109
+ )
110
+ endpoint_parser.add_argument(
111
+ "provider", help="Provider name (e.g., openai, anthropic)"
112
+ )
113
+ endpoint_parser.set_defaults(func=handle_optimal_endpoint)
114
+
115
+
116
+ if __name__ == "__main__":
117
+ parser = argparse.ArgumentParser(description="Region utilities")
118
+ setup_region_parser(parser.add_subparsers(dest="command"))
119
+
120
+ args = parser.parse_args()
121
+ if hasattr(args, "func"):
122
+ args.func(args)
123
+ else:
124
+ parser.print_help()
@@ -0,0 +1,240 @@
1
+ """
2
+ Geolocation utilities for determining user location and optimal regions.
3
+
4
+ This module provides utilities to detect user location and determine
5
+ optimal API regions based on geographic proximity.
6
+ """
7
+
8
+ import os
9
+ import json
10
+ import subprocess
11
+ import sys
12
+ from typing import Optional, Tuple
13
+ from pathlib import Path
14
+
15
+
16
+ def _get_geoip_location() -> Optional[str]:
17
+ """Try to get location using geoip2 if available."""
18
+ try:
19
+ import geoip2.database
20
+ import geoip2.errors
21
+ import urllib.request
22
+
23
+ # Common GeoIP database locations
24
+ db_paths = [
25
+ "/usr/share/GeoIP/GeoLite2-City.mmdb",
26
+ "/var/lib/GeoIP/GeoLite2-City.mmdb",
27
+ str(Path.home() / ".local/share/GeoIP/GeoLite2-City.mmdb"),
28
+ ]
29
+
30
+ for db_path in db_paths:
31
+ if Path(db_path).exists():
32
+ reader = geoip2.database.Reader(db_path)
33
+ try:
34
+ with urllib.request.urlopen(
35
+ "https://api.ipify.org", timeout=2
36
+ ) as response:
37
+ ip = response.read().decode().strip()
38
+
39
+ response = reader.city(ip)
40
+ country_code = response.country.iso_code
41
+ reader.close()
42
+
43
+ if country_code:
44
+ return country_code.upper()
45
+ except Exception:
46
+ reader.close()
47
+ continue
48
+ except (ImportError, Exception):
49
+ pass
50
+ return None
51
+
52
+
53
+ def _get_ipinfo_location() -> Optional[str]:
54
+ """Try to get location using ipinfo.io via curl."""
55
+ try:
56
+ import subprocess
57
+
58
+ result = subprocess.run(
59
+ ["curl", "-s", "https://ipinfo.io/country"],
60
+ capture_output=True,
61
+ text=True,
62
+ timeout=3,
63
+ )
64
+ if result.returncode == 0 and result.stdout.strip():
65
+ return result.stdout.strip().upper()
66
+ except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
67
+ pass
68
+ return None
69
+
70
+
71
+ def _get_locale_location() -> Optional[str]:
72
+ """Try to get location from system locale."""
73
+ try:
74
+ import locale
75
+
76
+ loc = locale.getdefaultlocale()[0]
77
+ if loc:
78
+ parts = loc.split("_")
79
+ if len(parts) > 1:
80
+ return parts[-1].upper()
81
+ except Exception:
82
+ pass
83
+ return None
84
+
85
+
86
+ def get_user_location() -> Tuple[str, str]:
87
+ """
88
+ Detect user's location using geoip lookup.
89
+
90
+ Returns:
91
+ Tuple of (country_code, region_name)
92
+
93
+ Note:
94
+ This is a best-effort detection. Falls back to environment variables
95
+ or defaults to 'US' if detection fails.
96
+ """
97
+ # Try environment variable first
98
+ user_region = os.getenv("JANITO_REGION")
99
+ if user_region:
100
+ return user_region.upper(), f"Environment: {user_region}"
101
+
102
+ # Try different detection methods
103
+ country_code = (
104
+ _get_geoip_location()
105
+ or _get_ipinfo_location()
106
+ or _get_locale_location()
107
+ or "US"
108
+ )
109
+
110
+ source = (
111
+ "GeoIP"
112
+ if _get_geoip_location()
113
+ else (
114
+ "ipinfo.io"
115
+ if _get_ipinfo_location()
116
+ else ("Locale" if _get_locale_location() else "Default")
117
+ )
118
+ )
119
+
120
+ return country_code, f"{source}: {country_code}"
121
+
122
+
123
+ def get_closest_region(country_code: str) -> str:
124
+ """
125
+ Map country code to closest major region.
126
+
127
+ Args:
128
+ country_code: ISO country code (e.g., 'US', 'DE', 'CN')
129
+
130
+ Returns:
131
+ Major region identifier (US, EU, CN, CH, ASIA)
132
+ """
133
+ country_code = country_code.upper()
134
+
135
+ # US and Americas
136
+ if country_code in ["US", "CA", "MX", "BR", "AR", "CL", "CO", "PE"]:
137
+ return "US"
138
+
139
+ # European Union and Europe
140
+ if country_code in [
141
+ "AT",
142
+ "BE",
143
+ "BG",
144
+ "HR",
145
+ "CY",
146
+ "CZ",
147
+ "DK",
148
+ "EE",
149
+ "FI",
150
+ "FR",
151
+ "DE",
152
+ "GR",
153
+ "HU",
154
+ "IE",
155
+ "IT",
156
+ "LV",
157
+ "LT",
158
+ "LU",
159
+ "MT",
160
+ "NL",
161
+ "PL",
162
+ "PT",
163
+ "RO",
164
+ "SK",
165
+ "SI",
166
+ "ES",
167
+ "SE",
168
+ "GB",
169
+ "NO",
170
+ "CH",
171
+ "IS",
172
+ "LI",
173
+ ]:
174
+ return "EU"
175
+
176
+ # China and nearby
177
+ if country_code in ["CN", "HK", "MO", "TW"]:
178
+ return "CN"
179
+
180
+ # Switzerland
181
+ if country_code == "CH":
182
+ return "CH"
183
+
184
+ # Asia Pacific
185
+ if country_code in [
186
+ "JP",
187
+ "KR",
188
+ "SG",
189
+ "MY",
190
+ "TH",
191
+ "VN",
192
+ "PH",
193
+ "ID",
194
+ "IN",
195
+ "AU",
196
+ "NZ",
197
+ "BD",
198
+ "LK",
199
+ "MM",
200
+ "KH",
201
+ "LA",
202
+ "BN",
203
+ "MV",
204
+ ]:
205
+ return "ASIA"
206
+
207
+ # Middle East
208
+ if country_code in ["AE", "SA", "QA", "KW", "OM", "BH", "IL", "TR"]:
209
+ return "ASIA"
210
+
211
+ # Africa
212
+ if country_code in ["ZA", "NG", "KE", "EG", "MA", "TN", "GH", "UG"]:
213
+ return "EU" # Route through EU for better connectivity
214
+
215
+ # Default to US for unknown regions
216
+ return "US"
217
+
218
+
219
+ def get_region_info() -> dict:
220
+ """
221
+ Get comprehensive region information for the current user.
222
+
223
+ Returns:
224
+ Dictionary with location details
225
+ """
226
+ country_code, source = get_user_location()
227
+ major_region = get_closest_region(country_code)
228
+
229
+ return {
230
+ "country_code": country_code,
231
+ "major_region": major_region,
232
+ "source": source,
233
+ "timestamp": str(__import__("datetime").datetime.now()),
234
+ }
235
+
236
+
237
+ if __name__ == "__main__":
238
+ # Test the geolocation functionality
239
+ info = get_region_info()
240
+ print(json.dumps(info, indent=2))
@@ -0,0 +1,158 @@
1
+ """
2
+ Static region definitions for LLM providers.
3
+
4
+ This module contains region mappings for major LLM providers with their
5
+ respective API endpoints and data center locations.
6
+ """
7
+
8
+ from typing import Dict, List, Optional
9
+ from dataclasses import dataclass
10
+
11
+
12
+ @dataclass
13
+ class RegionEndpoint:
14
+ """Represents a provider's endpoint in a specific region."""
15
+
16
+ region_code: str
17
+ name: str
18
+ endpoint: str
19
+ location: str # City, Country format
20
+ priority: int = 1 # Lower = higher priority
21
+
22
+
23
+ # Region definitions for major LLM providers
24
+ PROVIDER_REGIONS: Dict[str, List[RegionEndpoint]] = {
25
+ "openai": [
26
+ RegionEndpoint(
27
+ "US-WEST", "US West", "https://api.openai.com/v1", "San Francisco, US", 1
28
+ ),
29
+ ],
30
+ "anthropic": [
31
+ RegionEndpoint(
32
+ "US-WEST", "US West", "https://api.anthropic.com", "San Francisco, US", 1
33
+ ),
34
+ ],
35
+ "google": [
36
+ RegionEndpoint(
37
+ "EU-WEST",
38
+ "EU West",
39
+ "https://generativelanguage.googleapis.com/v1beta",
40
+ "Delfzijl, NL",
41
+ 1,
42
+ ),
43
+ ],
44
+ "azure-openai": [
45
+ RegionEndpoint(
46
+ "US-EAST",
47
+ "East US",
48
+ "https://{resource}.openai.azure.com",
49
+ "Virginia, US",
50
+ 1,
51
+ ),
52
+ RegionEndpoint(
53
+ "US-WEST",
54
+ "West US",
55
+ "https://{resource}.openai.azure.com",
56
+ "California, US",
57
+ 2,
58
+ ),
59
+ RegionEndpoint(
60
+ "EU-WEST",
61
+ "West Europe",
62
+ "https://{resource}.openai.azure.com",
63
+ "Netherlands, NL",
64
+ 3,
65
+ ),
66
+ RegionEndpoint(
67
+ "EU-NORTH",
68
+ "North Europe",
69
+ "https://{resource}.openai.azure.com",
70
+ "Ireland, IE",
71
+ 4,
72
+ ),
73
+ ],
74
+ "alibaba": [
75
+ RegionEndpoint(
76
+ "SG",
77
+ "Singapore",
78
+ "https://dashscope-intl.aliyuncs.com/api/v1",
79
+ "Singapore, SG",
80
+ 1,
81
+ ),
82
+ RegionEndpoint(
83
+ "CN-EAST",
84
+ "China East",
85
+ "https://dashscope.aliyuncs.com/api/v1",
86
+ "Hangzhou, CN",
87
+ 2,
88
+ ),
89
+ ],
90
+ "moonshot": [
91
+ RegionEndpoint(
92
+ "US-WEST", "US West", "https://api.moonshot.ai/v1", "San Francisco, US", 1
93
+ ),
94
+ RegionEndpoint(
95
+ "CN-EAST", "China East", "https://api.moonshot.cn/v1", "Shanghai, CN", 2
96
+ ),
97
+ RegionEndpoint(
98
+ "CN-NORTH", "China North", "https://api.moonshot.cn/v1", "Beijing, CN", 3
99
+ ),
100
+ ],
101
+ "zai": [
102
+ RegionEndpoint(
103
+ "ASIA-PACIFIC",
104
+ "Asia Pacific",
105
+ "https://api.z.ai/api/paas/v4",
106
+ "Singapore, SG",
107
+ 1,
108
+ ),
109
+ ],
110
+ }
111
+
112
+ # Geographic region mappings
113
+ REGION_MAPPINGS = {
114
+ "US": ["US-EAST", "US-WEST", "US-CENTRAL", "US-NORTH", "US-SOUTH"],
115
+ "EU": ["EU-CENTRAL", "EU-WEST", "EU-NORTH", "EU-SOUTH", "EU-EAST"],
116
+ "CN": ["CN-EAST", "CN-NORTH", "CN-SOUTH", "CN-WEST", "CN-CENTRAL"],
117
+ "CH": ["CH-NORTH", "CH-SOUTH", "CH-EAST", "CH-WEST"],
118
+ "ASIA": ["ASIA-EAST", "ASIA-PACIFIC", "ASIA-SOUTH", "ASIA-CENTRAL"],
119
+ "GLOBAL": ["GLOBAL", "WORLDWIDE"],
120
+ }
121
+
122
+
123
+ def get_provider_regions(provider: str) -> List[RegionEndpoint]:
124
+ """Get all regions for a specific provider."""
125
+ return PROVIDER_REGIONS.get(provider.lower(), [])
126
+
127
+
128
+ def get_optimal_endpoint(provider: str, user_region: str = "US") -> Optional[str]:
129
+ """
130
+ Get the optimal endpoint for a provider based on user region.
131
+
132
+ Args:
133
+ provider: The provider name (e.g., 'openai', 'anthropic')
134
+ user_region: User's geographic region (US, EU, CN, CH, ASIA)
135
+
136
+ Returns:
137
+ The optimal endpoint URL or None if provider not found
138
+ """
139
+ regions = get_provider_regions(provider)
140
+ if not regions:
141
+ return None
142
+
143
+ # Map user region to provider regions
144
+ preferred_region_codes = REGION_MAPPINGS.get(user_region.upper(), [])
145
+
146
+ # Find the highest priority region that matches
147
+ for region_code in preferred_region_codes:
148
+ for region in regions:
149
+ if region.region_code == region_code:
150
+ return region.endpoint
151
+
152
+ # Fallback to first available region
153
+ return regions[0].endpoint if regions else None
154
+
155
+
156
+ def get_all_providers() -> List[str]:
157
+ """Get list of all supported providers."""
158
+ return list(PROVIDER_REGIONS.keys())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: janito
3
- Version: 2.15.0
3
+ Version: 2.17.0
4
4
  Summary: A new Python package called janito.
5
5
  Author-email: João Pinto <janito@ikignosis.org>
6
6
  Project-URL: Homepage, https://github.com/ikignosis/janito