janito 2.15.0__py3-none-any.whl → 2.16.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.
@@ -12,18 +12,20 @@ from janito.llm.driver_input import DriverInput
12
12
  from janito.driver_events import RequestFinished, RequestStatus, RateLimitRetry
13
13
  from janito.llm.message_parts import TextMessagePart, FunctionCallMessagePart
14
14
 
15
- try:
16
- import openai
17
- available = True
18
- unavailable_reason = None
19
- except ImportError:
20
- available = False
21
- unavailable_reason = "openai module not installed"
15
+ import openai
22
16
 
23
17
 
24
18
  class ZAIModelDriver(LLMDriver):
25
- available = available
26
- unavailable_reason = unavailable_reason
19
+ # Check if required dependencies are available
20
+ try:
21
+ from zai import ZaiClient
22
+
23
+ available = True
24
+ unavailable_reason = None
25
+ except ImportError as e:
26
+ available = False
27
+ unavailable_reason = f"Missing dependency: {str(e)}"
28
+
27
29
  def _get_message_from_result(self, result):
28
30
  """Extract the message object from the provider result (Z.AI-specific)."""
29
31
  if hasattr(result, "choices") and result.choices:
@@ -256,14 +258,11 @@ class ZAIModelDriver(LLMDriver):
256
258
  try:
257
259
  if not config.api_key:
258
260
  provider_name = getattr(self, "provider_name", "ZAI")
259
- print(
260
- f"[ERROR] No API key found for provider '{provider_name}'. Please set the API key using:"
261
- )
262
- print(f" janito --set-api-key YOUR_API_KEY -p {provider_name.lower()}")
263
- print(
264
- f"Or set the {provider_name.upper()}_API_KEY environment variable."
261
+ from janito.llm.auth_utils import handle_missing_api_key
262
+
263
+ handle_missing_api_key(
264
+ provider_name, f"{provider_name.upper()}_API_KEY"
265
265
  )
266
- raise ValueError(f"API key is required for provider '{provider_name}'")
267
266
 
268
267
  api_key_display = str(config.api_key)
269
268
  if api_key_display and len(api_key_display) > 8:
@@ -284,12 +283,10 @@ class ZAIModelDriver(LLMDriver):
284
283
  flush=True,
285
284
  )
286
285
 
287
- # Use OpenAI SDK for Z.AI API compatibility
288
- try:
289
- import openai
290
- except ImportError:
291
- raise ImportError("openai module is not available. Please install it with: pip install openai")
292
- client = openai.OpenAI(
286
+ # Use the official Z.ai SDK
287
+ from zai import ZaiClient
288
+
289
+ client = ZaiClient(
293
290
  api_key=config.api_key, base_url="https://api.z.ai/api/paas/v4/"
294
291
  )
295
292
  return client
@@ -29,7 +29,7 @@ def format_token_message_summary(
29
29
  msg_count, usage, width=96, use_rich=False, elapsed=None
30
30
  ):
31
31
  """
32
- Returns a string (rich or pt markup) summarizing message count, last token usage, and elapsed time.
32
+ Returns a string (rich or pt markup) summarizing message count, last token usage, elapsed time, and tokens per second.
33
33
  """
34
34
  left = f" Messages: {'[' if use_rich else '<'}msg_count{']' if use_rich else '>'}{msg_count}{'[/msg_count]' if use_rich else '</msg_count>'}"
35
35
  tokens_part = ""
@@ -42,10 +42,14 @@ def format_token_message_summary(
42
42
  f"Completion: {format_tokens(completion_tokens, 'tokens_out', use_rich)}, "
43
43
  f"Total: {format_tokens(total_tokens, 'tokens_total', use_rich)}"
44
44
  )
45
- elapsed_part = (
46
- f" | Elapsed: [cyan]{elapsed:.2f}s[/cyan]" if elapsed is not None else ""
47
- )
48
- return f"{left}{tokens_part}{elapsed_part}"
45
+ elapsed_part = ""
46
+ tps_part = ""
47
+ if elapsed is not None and elapsed > 0:
48
+ elapsed_part = f" | Elapsed: [cyan]{elapsed:.2f}s[/cyan]"
49
+ if usage and total_tokens:
50
+ tokens_per_second = total_tokens / elapsed
51
+ tps_part = f" | TPS: {int(tokens_per_second)}"
52
+ return f"{left}{tokens_part}{elapsed_part}{tps_part}"
49
53
 
50
54
 
51
55
  def print_token_message_summary(
@@ -0,0 +1,21 @@
1
+ """
2
+ Authentication utilities for LLM providers.
3
+ """
4
+
5
+ import sys
6
+
7
+
8
+ def handle_missing_api_key(provider_name: str, env_var_name: str) -> None:
9
+ """
10
+ Handle missing API key by printing error message and exiting.
11
+
12
+ Args:
13
+ provider_name: Name of the provider (e.g., 'alibaba', 'openai')
14
+ env_var_name: Environment variable name (e.g., 'ALIBABA_API_KEY')
15
+ """
16
+ print(
17
+ f"[ERROR] No API key found for provider '{provider_name}'. Please set the API key using:"
18
+ )
19
+ print(f" janito --set-api-key YOUR_API_KEY -p {provider_name}")
20
+ print(f"Or set the {env_var_name} environment variable.")
21
+ sys.exit(1)
@@ -17,7 +17,9 @@ class AlibabaProvider(LLMProvider):
17
17
  NAME = "alibaba"
18
18
  MAINTAINER = "João Pinto <janito@ikignosis.org>"
19
19
  MODEL_SPECS = MODEL_SPECS
20
- DEFAULT_MODEL = "qwen3-coder-plus" # Options: qwen-turbo, qwen-plus, qwen-max, qwen3-coder-plus
20
+ DEFAULT_MODEL = (
21
+ "qwen3-coder-plus" # Options: qwen-turbo, qwen-plus, qwen-max, qwen3-coder-plus
22
+ )
21
23
 
22
24
  def __init__(
23
25
  self, auth_manager: LLMAuthManager = None, config: LLMDriverConfig = None
@@ -25,27 +27,29 @@ class AlibabaProvider(LLMProvider):
25
27
  # Always set a tools adapter so that even if the driver is unavailable,
26
28
  # generic code paths that expect provider.execute_tool() continue to work.
27
29
  self._tools_adapter = get_local_tools_adapter()
28
-
30
+
29
31
  # Always initialize _driver_config to avoid AttributeError
30
32
  self._driver_config = config or LLMDriverConfig(model=None)
31
-
33
+
32
34
  if not self.available:
33
35
  self._driver = None
34
36
  else:
35
37
  self.auth_manager = auth_manager or LLMAuthManager()
36
38
  self._api_key = self.auth_manager.get_credentials(type(self).NAME)
37
39
  if not self._api_key:
38
- print(f"[ERROR] No API key found for provider '{self.name}'. Please set the API key using:")
39
- print(f" janito --set-api-key YOUR_API_KEY -p {self.name}")
40
- print(f"Or set the ALIBABA_API_KEY environment variable.")
41
-
40
+ from janito.llm.auth_utils import handle_missing_api_key
41
+
42
+ handle_missing_api_key(self.name, "ALIBABA_API_KEY")
43
+
42
44
  if not self._driver_config.model:
43
45
  self._driver_config.model = self.DEFAULT_MODEL
44
46
  if not self._driver_config.api_key:
45
47
  self._driver_config.api_key = self._api_key
46
48
  # Set Alibaba international endpoint as default base_url if not provided
47
49
  if not getattr(self._driver_config, "base_url", None):
48
- self._driver_config.base_url = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
50
+ self._driver_config.base_url = (
51
+ "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
52
+ )
49
53
  self.fill_missing_device_info(self._driver_config)
50
54
  self._driver = None # to be provided by factory/agent
51
55
 
@@ -99,4 +103,4 @@ class AlibabaProvider(LLMProvider):
99
103
  return self._tools_adapter.execute_by_name(tool_name, *args, **kwargs)
100
104
 
101
105
 
102
- LLMProviderRegistry.register(AlibabaProvider.NAME, AlibabaProvider)
106
+ LLMProviderRegistry.register(AlibabaProvider.NAME, AlibabaProvider)
@@ -22,11 +22,10 @@ class AnthropicProvider(LLMProvider):
22
22
  self.auth_manager = auth_manager or LLMAuthManager()
23
23
  self._api_key = self.auth_manager.get_credentials(type(self).NAME)
24
24
  if not self._api_key:
25
- print(f"[ERROR] No API key found for provider '{self.name}'. Please set the API key using:")
26
- print(f" janito --set-api-key YOUR_API_KEY -p {self.name}")
27
- print(f"Or set the ANTHROPIC_API_KEY environment variable.")
28
- return
29
-
25
+ from janito.llm.auth_utils import handle_missing_api_key
26
+
27
+ handle_missing_api_key(self.name, "ANTHROPIC_API_KEY")
28
+
30
29
  self._tools_adapter = get_local_tools_adapter()
31
30
  self._driver_config = config or LLMDriverConfig(model=None)
32
31
  if not getattr(self._driver_config, "model", None):
@@ -33,11 +33,10 @@ class AzureOpenAIProvider(LLMProvider):
33
33
  self._auth_manager = auth_manager or LLMAuthManager()
34
34
  self._api_key = self._auth_manager.get_credentials(type(self).NAME)
35
35
  if not self._api_key:
36
- print(f"[ERROR] No API key found for provider '{self.name}'. Please set the API key using:")
37
- print(f" janito --set-api-key YOUR_API_KEY -p {self.name}")
38
- print(f"Or set the AZURE_OPENAI_API_KEY environment variable.")
39
- return
40
-
36
+ from janito.llm.auth_utils import handle_missing_api_key
37
+
38
+ handle_missing_api_key(self.name, "AZURE_OPENAI_API_KEY")
39
+
41
40
  self._tools_adapter = get_local_tools_adapter()
42
41
  self._driver_config = config or LLMDriverConfig(model=None)
43
42
  if not self._driver_config.model:
@@ -31,11 +31,10 @@ class DeepSeekProvider(LLMProvider):
31
31
  self.auth_manager = auth_manager or LLMAuthManager()
32
32
  self._api_key = self.auth_manager.get_credentials(type(self).NAME)
33
33
  if not self._api_key:
34
- print(f"[ERROR] No API key found for provider '{self.name}'. Please set the API key using:")
35
- print(f" janito --set-api-key YOUR_API_KEY -p {self.name}")
36
- print(f"Or set the DEEPSEEK_API_KEY environment variable.")
37
- return
38
-
34
+ from janito.llm.auth_utils import handle_missing_api_key
35
+
36
+ handle_missing_api_key(self.name, "DEEPSEEK_API_KEY")
37
+
39
38
  self._tools_adapter = get_local_tools_adapter()
40
39
  self._driver_config = config or LLMDriverConfig(model=None)
41
40
  if not self._driver_config.model:
@@ -33,11 +33,10 @@ class GoogleProvider(LLMProvider):
33
33
  self.auth_manager = auth_manager or LLMAuthManager()
34
34
  self._api_key = self.auth_manager.get_credentials(type(self).name)
35
35
  if not self._api_key:
36
- print(f"[ERROR] No API key found for provider '{self.name}'. Please set the API key using:")
37
- print(f" janito --set-api-key YOUR_API_KEY -p {self.name}")
38
- print(f"Or set the GOOGLE_API_KEY environment variable.")
39
- return
40
-
36
+ from janito.llm.auth_utils import handle_missing_api_key
37
+
38
+ handle_missing_api_key(self.name, "GOOGLE_API_KEY")
39
+
41
40
  self._tools_adapter = get_local_tools_adapter()
42
41
  self._driver_config = config or LLMDriverConfig(model=None)
43
42
  # Only set default if model is not set by CLI/config
@@ -17,44 +17,53 @@ class MoonshotAIProvider(LLMProvider):
17
17
  def __init__(
18
18
  self, auth_manager: LLMAuthManager = None, config: LLMDriverConfig = None
19
19
  ):
20
+ self._tools_adapter = get_local_tools_adapter()
21
+ self._driver = None
22
+
20
23
  if not self.available:
21
- self._tools_adapter = get_local_tools_adapter()
22
- self._driver = None
23
- else:
24
- self.auth_manager = auth_manager or LLMAuthManager()
25
- self._api_key = self.auth_manager.get_credentials(type(self).name)
26
- if not self._api_key:
27
- print(f"[ERROR] No API key found for provider '{self.name}'. Please set the API key using:")
28
- print(f" janito --set-api-key YOUR_API_KEY -p {self.name}")
29
- print(f"Or set the MOONSHOTAI_API_KEY environment variable.")
30
- return
31
-
32
- self._tools_adapter = get_local_tools_adapter()
33
- self._driver_config = config or LLMDriverConfig(model=None)
34
- if not self._driver_config.model:
35
- self._driver_config.model = self.DEFAULT_MODEL
36
- if not self._driver_config.api_key:
37
- self._driver_config.api_key = self._api_key
38
- # Set only the correct token parameter for the model
39
- model_name = self._driver_config.model
40
- model_spec = self.MODEL_SPECS.get(model_name)
41
- if hasattr(self._driver_config, "max_tokens"):
42
- self._driver_config.max_tokens = None
43
- if hasattr(self._driver_config, "max_completion_tokens"):
44
- self._driver_config.max_completion_tokens = None
45
- if model_spec:
46
- if getattr(model_spec, "thinking_supported", False):
47
- max_cot = getattr(model_spec, "max_cot", None)
48
- if max_cot and max_cot != "N/A":
49
- self._driver_config.max_completion_tokens = int(max_cot)
50
- else:
51
- max_response = getattr(model_spec, "max_response", None)
52
- if max_response and max_response != "N/A":
53
- self._driver_config.max_tokens = int(max_response)
54
- self.fill_missing_device_info(self._driver_config)
55
- self._driver = None
56
- # Set MoonshotAI base_url
57
- self._driver_config.base_url = "https://api.moonshot.ai/v1"
24
+ return
25
+
26
+ self._initialize_config(auth_manager, config)
27
+ self._setup_model_config()
28
+ self._driver_config.base_url = "https://api.moonshot.ai/v1"
29
+
30
+ def _initialize_config(self, auth_manager, config):
31
+ """Initialize configuration and API key."""
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
+ from janito.llm.auth_utils import handle_missing_api_key
36
+
37
+ handle_missing_api_key(self.name, "MOONSHOTAI_API_KEY")
38
+
39
+ self._driver_config = config or LLMDriverConfig(model=None)
40
+ if not self._driver_config.model:
41
+ self._driver_config.model = self.DEFAULT_MODEL
42
+ if not self._driver_config.api_key:
43
+ self._driver_config.api_key = self._api_key
44
+
45
+ def _setup_model_config(self):
46
+ """Configure token limits based on model specifications."""
47
+ model_name = self._driver_config.model
48
+ model_spec = self.MODEL_SPECS.get(model_name)
49
+
50
+ # Reset token parameters
51
+ if hasattr(self._driver_config, "max_tokens"):
52
+ self._driver_config.max_tokens = None
53
+ if hasattr(self._driver_config, "max_completion_tokens"):
54
+ self._driver_config.max_completion_tokens = None
55
+
56
+ if model_spec:
57
+ if getattr(model_spec, "thinking_supported", False):
58
+ max_cot = getattr(model_spec, "max_cot", None)
59
+ if max_cot and max_cot != "N/A":
60
+ self._driver_config.max_completion_tokens = int(max_cot)
61
+ else:
62
+ max_response = getattr(model_spec, "max_response", None)
63
+ if max_response and max_response != "N/A":
64
+ self._driver_config.max_tokens = int(max_response)
65
+
66
+ self.fill_missing_device_info(self._driver_config)
58
67
 
59
68
  @property
60
69
  def driver(self) -> OpenAIModelDriver:
@@ -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()