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.
@@ -115,9 +115,15 @@ class ChatSession:
115
115
  def _select_profile_and_role(self, args, role):
116
116
  profile = getattr(args, "profile", None) if args is not None else None
117
117
  role_arg = getattr(args, "role", None) if args is not None else None
118
+ python_profile = getattr(args, "python", False) if args is not None else False
118
119
  profile_system_prompt = None
119
120
  no_tools_mode = False
120
- if profile is None and role_arg is None:
121
+
122
+ # Handle --python flag
123
+ if python_profile and profile is None and role_arg is None:
124
+ profile = "Developer with Python Tools"
125
+
126
+ if profile is None and role_arg is None and not python_profile:
121
127
  try:
122
128
  from janito.cli.chat_mode.session_profile_select import select_profile
123
129
 
@@ -277,6 +283,41 @@ class ChatSession:
277
283
  else None
278
284
  )
279
285
  start_time = time.time()
286
+
287
+ # Print rule line with model info before processing prompt
288
+ model_name = (
289
+ self.agent.get_model_name()
290
+ if hasattr(self.agent, "get_model_name")
291
+ else "Unknown"
292
+ )
293
+ provider_name = (
294
+ self.agent.get_provider_name()
295
+ if hasattr(self.agent, "get_provider_name")
296
+ else "Unknown"
297
+ )
298
+
299
+ # Get backend hostname if available
300
+ backend_hostname = "Unknown"
301
+ if hasattr(self.agent, "driver") and self.agent.driver:
302
+ if hasattr(self.agent.driver, "config") and hasattr(
303
+ self.agent.driver.config, "base_url"
304
+ ):
305
+ base_url = self.agent.driver.config.base_url
306
+ if base_url:
307
+ try:
308
+ from urllib.parse import urlparse
309
+
310
+ parsed = urlparse(base_url)
311
+ backend_hostname = parsed.netloc
312
+ except Exception:
313
+ backend_hostname = base_url
314
+
315
+ self.console.print(
316
+ Rule(
317
+ f"[bold blue]Model: {model_name} ({provider_name}) | Backend: {backend_hostname}[/bold blue]"
318
+ )
319
+ )
320
+
280
321
  self._prompt_handler.run_prompt(cmd_input)
281
322
  end_time = time.time()
282
323
  elapsed = end_time - start_time
@@ -13,125 +13,149 @@ from rich.text import Text
13
13
  console = Console()
14
14
 
15
15
 
16
+ def _detect_dependencies_from_content(content, class_name):
17
+ """Detect dependencies from module content."""
18
+ dependencies = []
19
+
20
+ if "import openai" in content or "from openai" in content:
21
+ dependencies.append("openai")
22
+ if "import zai" in content or "from zai" in content:
23
+ dependencies.append("zai")
24
+ if "import anthropic" in content or "from anthropic" in content:
25
+ dependencies.append("anthropic")
26
+ if "import google" in content or "from google" in content:
27
+ dependencies.append("google-generativeai")
28
+
29
+ # Remove openai from zai driver dependencies
30
+ if "ZAIModelDriver" in class_name and "openai" in dependencies:
31
+ dependencies.remove("openai")
32
+
33
+ return dependencies
34
+
35
+
36
+ def _check_dependency_status(dependencies):
37
+ """Check if dependencies are available."""
38
+ if not dependencies:
39
+ return ["No external dependencies"]
40
+
41
+ dep_status = []
42
+ for dep in dependencies:
43
+ try:
44
+ importlib.import_module(dep)
45
+ dep_status.append(f"✅ {dep}")
46
+ except ImportError:
47
+ dep_status.append(f"❌ {dep}")
48
+
49
+ return dep_status
50
+
51
+
52
+ def _get_single_driver_info(module_path, class_name):
53
+ """Get information for a single driver."""
54
+ try:
55
+ module = importlib.import_module(module_path)
56
+ driver_class = getattr(module, class_name)
57
+
58
+ available = getattr(driver_class, "available", True)
59
+ unavailable_reason = getattr(driver_class, "unavailable_reason", None)
60
+
61
+ # Read module file to detect imports
62
+ module_file = Path(module.__file__)
63
+ with open(module_file, "r", encoding="utf-8") as f:
64
+ content = f.read()
65
+
66
+ dependencies = _detect_dependencies_from_content(content, class_name)
67
+ dep_status = _check_dependency_status(dependencies)
68
+
69
+ return {
70
+ "name": class_name,
71
+ "available": available,
72
+ "reason": unavailable_reason,
73
+ "dependencies": dep_status,
74
+ }
75
+
76
+ except (ImportError, AttributeError) as e:
77
+ return {
78
+ "name": class_name,
79
+ "module": module_path,
80
+ "available": False,
81
+ "reason": str(e),
82
+ "dependencies": ["❌ Module not found"],
83
+ }
84
+
85
+
16
86
  def get_driver_info():
17
87
  """Get information about all available drivers."""
18
88
  drivers = []
19
-
89
+
20
90
  # Define known driver modules
21
91
  driver_modules = [
22
92
  ("janito.drivers.openai.driver", "OpenAIModelDriver"),
23
93
  ("janito.drivers.azure_openai.driver", "AzureOpenAIModelDriver"),
24
94
  ("janito.drivers.zai.driver", "ZAIModelDriver"),
25
95
  ]
26
-
96
+
27
97
  for module_path, class_name in driver_modules:
28
- try:
29
- # Import the module
30
- module = importlib.import_module(module_path)
31
- driver_class = getattr(module, class_name)
32
-
33
- # Get availability info
34
- available = getattr(driver_class, 'available', True)
35
- unavailable_reason = getattr(driver_class, 'unavailable_reason', None)
36
-
37
- # Get dependencies from module imports
38
- dependencies = []
39
- module_file = Path(module.__file__)
40
-
41
- # Read module file to detect imports
42
- with open(module_file, 'r', encoding='utf-8') as f:
43
- content = f.read()
44
-
45
- # Simple dependency detection
46
- if 'import openai' in content or 'from openai' in content:
47
- dependencies.append('openai')
48
- if 'import zai' in content or 'from zai' in content:
49
- dependencies.append('zai')
50
- if 'import anthropic' in content or 'from anthropic' in content:
51
- dependencies.append('anthropic')
52
- if 'import google' in content or 'from google' in content:
53
- dependencies.append('google-generativeai')
54
-
55
-
56
-
57
- # Remove duplicates while preserving order
58
- seen = set()
59
- dependencies = [dep for dep in dependencies if not (dep in seen or seen.add(dep))]
60
-
61
- # Check if dependencies are available
62
- dep_status = []
63
- for dep in dependencies:
64
- try:
65
- importlib.import_module(dep)
66
- dep_status.append(f"✅ {dep}")
67
- except ImportError:
68
- dep_status.append(f"❌ {dep}")
69
-
70
- if not dependencies:
71
- dep_status = ["No external dependencies"]
72
-
73
- drivers.append({
74
- 'name': class_name,
75
- 'available': available,
76
- 'reason': unavailable_reason,
77
- 'dependencies': dep_status
78
- })
79
-
80
- except (ImportError, AttributeError) as e:
81
- drivers.append({
82
- 'name': class_name,
83
- 'module': module_path,
84
- 'available': False,
85
- 'reason': str(e),
86
- 'dependencies': ["❌ Module not found"]
87
- })
88
-
98
+ driver_info = _get_single_driver_info(module_path, class_name)
99
+ drivers.append(driver_info)
100
+
89
101
  return drivers
90
102
 
91
103
 
92
- def handle_list_drivers(args=None):
93
- """List all available LLM drivers with their status and dependencies."""
94
- drivers = get_driver_info()
95
-
96
- if not drivers:
97
- console.print("[red]No drivers found[/red]")
98
- return
99
-
100
- # Create table
104
+ def _create_driver_table(drivers):
105
+ """Create and populate the drivers table."""
101
106
  table = Table(title="Available LLM Drivers")
102
107
  table.add_column("Driver", style="cyan", no_wrap=True)
103
108
  table.add_column("Status", style="bold")
104
109
  table.add_column("Dependencies", style="yellow")
105
-
110
+
106
111
  for driver in drivers:
107
- name = driver['name']
108
-
109
- if driver['available']:
112
+ name = driver["name"]
113
+
114
+ if driver["available"]:
110
115
  status = "[green]✅ Available[/green]"
111
- if driver['reason']:
116
+ if driver["reason"]:
112
117
  status = f"[yellow]⚠️ Available ({driver['reason']})[/yellow]"
113
118
  else:
114
119
  status = f"[red]❌ Unavailable[/red]"
115
- if driver['reason']:
120
+ if driver["reason"]:
116
121
  status = f"[red]❌ {driver['reason']}[/red]"
117
-
118
- deps = "\n".join(driver['dependencies'])
119
-
122
+
123
+ deps = "\n".join(driver["dependencies"])
120
124
  table.add_row(name, status, deps)
121
-
122
- console.print(table)
123
-
124
- # Installation help
125
- # Get unique missing dependencies
126
- missing_deps = set()
125
+
126
+ return table
127
+
128
+
129
+ def _get_missing_dependencies(drivers):
130
+ """Get list of missing dependencies."""
131
+ missing_deps = []
127
132
  for driver in drivers:
128
- for dep_status in driver['dependencies']:
129
- if dep_status.startswith(''):
130
- missing_deps.add(dep_status[2:].strip())
131
-
133
+ for dep_status in driver["dependencies"]:
134
+ if dep_status.startswith(""):
135
+ dep_name = dep_status.split()[1]
136
+ if dep_name not in missing_deps:
137
+ missing_deps.append(dep_name)
138
+ return missing_deps
139
+
140
+
141
+ def handle_list_drivers(args=None):
142
+ """List all available LLM drivers with their status and dependencies."""
143
+ drivers = get_driver_info()
144
+
145
+ if not drivers:
146
+ console.print("[red]No drivers found[/red]")
147
+ return
148
+
149
+ table = _create_driver_table(drivers)
150
+ console.print(table)
151
+
152
+ # Installation help - only show for missing dependencies
153
+ missing_deps = _get_missing_dependencies(drivers)
132
154
  if missing_deps:
133
- console.print(f"\n[dim]💡 Install missing deps: pip install {' '.join(sorted(missing_deps))}[/dim]")
155
+ console.print(
156
+ f"\n[dim]💡 Install missing deps: pip install {' '.join(missing_deps)}[/dim]"
157
+ )
134
158
 
135
159
 
136
160
  if __name__ == "__main__":
137
- handle_list_drivers()
161
+ handle_list_drivers()
@@ -0,0 +1,85 @@
1
+ """
2
+ CLI Command: List providers with their regional API information
3
+ """
4
+
5
+ import json
6
+ from typing import Dict, List
7
+ from rich.console import Console
8
+ from rich.table import Table
9
+ from rich.panel import Panel
10
+
11
+ from janito.regions.provider_regions import PROVIDER_REGIONS, get_all_providers
12
+ from janito.regions.geo_utils import get_region_info
13
+
14
+ console = Console()
15
+
16
+
17
+ def handle_list_providers_region(args=None):
18
+ """List all providers with their regional API information."""
19
+
20
+ # Get user location info
21
+ user_info = get_region_info()
22
+ user_region = user_info["major_region"]
23
+
24
+ # Get all providers
25
+ providers = get_all_providers()
26
+
27
+ # Create table
28
+ table = Table(title="LLM Providers by Region")
29
+ table.add_column("Provider", style="cyan", no_wrap=True)
30
+ table.add_column("Region", style="green")
31
+ table.add_column("Endpoint", style="bright_white")
32
+
33
+ for provider in sorted(providers):
34
+ regions = PROVIDER_REGIONS.get(provider, [])
35
+ if not regions:
36
+ table.add_row(provider, "N/A", "N/A", "N/A", "N/A")
37
+ continue
38
+
39
+ # Find the best region for the user
40
+ best_region = None
41
+ for region in regions:
42
+ if region.region_code.startswith(user_region):
43
+ best_region = region
44
+ break
45
+
46
+ # If no exact match, use first available
47
+ if not best_region:
48
+ best_region = regions[0]
49
+
50
+ # Extract 2-letter region code
51
+ if provider == "azure-openai":
52
+ region_code = "ALL"
53
+ else:
54
+ region_code = (
55
+ user_region
56
+ if any(r.region_code.startswith(user_region) for r in regions)
57
+ else (
58
+ "APAC"
59
+ if best_region.region_code == "ASIA-PACIFIC"
60
+ else best_region.region_code[:2]
61
+ )
62
+ )
63
+
64
+ table.add_row(
65
+ provider,
66
+ region_code,
67
+ best_region.endpoint,
68
+ )
69
+
70
+ console.print(table)
71
+
72
+ # Show user location info
73
+ console.print(
74
+ f"\n[dim]Your location: {user_info['country_code']} ({user_region})[/dim]"
75
+ )
76
+ console.print(f"[dim]Detection source: {user_info['source']}[/dim]")
77
+
78
+ # Show region mapping info
79
+ console.print(
80
+ "\n[dim]Regions: US (United States), EU (Europe), CN (China), CH (Switzerland), APAC (Asia Pacific)[/dim]"
81
+ )
82
+
83
+
84
+ if __name__ == "__main__":
85
+ handle_list_providers_region()
@@ -8,6 +8,8 @@ from janito.cli.cli_commands.list_tools import handle_list_tools
8
8
  from janito.cli.cli_commands.show_config import handle_show_config
9
9
  from janito.cli.cli_commands.list_config import handle_list_config
10
10
  from janito.cli.cli_commands.list_drivers import handle_list_drivers
11
+ from janito.regions.cli import handle_region_info
12
+ from janito.cli.cli_commands.list_providers_region import handle_list_providers_region
11
13
  from functools import partial
12
14
  from janito.provider_registry import ProviderRegistry
13
15
 
@@ -19,6 +21,8 @@ GETTER_KEYS = [
19
21
  "list_tools",
20
22
  "list_config",
21
23
  "list_drivers",
24
+ "region_info",
25
+ "list_providers_region",
22
26
  ]
23
27
 
24
28
 
@@ -45,9 +49,9 @@ def handle_getter(args, config_mgr=None):
45
49
  "show_config": partial(handle_show_config, args),
46
50
  "list_config": partial(handle_list_config, args),
47
51
  "list_drivers": partial(handle_list_drivers, args),
52
+ "region_info": partial(handle_region_info, args),
53
+ "list_providers_region": partial(handle_list_providers_region, args),
48
54
  }
49
55
  for arg in GETTER_KEYS:
50
56
  if getattr(args, arg, False) and arg in GETTER_DISPATCH:
51
- GETTER_DISPATCH[arg]()
52
- import sys
53
- sys.exit(0)
57
+ return GETTER_DISPATCH[arg]()
janito/cli/main_cli.py CHANGED
@@ -36,6 +36,13 @@ definition = [
36
36
  "default": None,
37
37
  },
38
38
  ),
39
+ (
40
+ ["--python"],
41
+ {
42
+ "action": "store_true",
43
+ "help": "Start with the Python developer profile (equivalent to --profile 'Developer with Python Tools')",
44
+ },
45
+ ),
39
46
  (
40
47
  ["--role"],
41
48
  {
@@ -119,7 +126,24 @@ definition = [
119
126
  ),
120
127
  (
121
128
  ["--list-drivers"],
122
- {"action": "store_true", "help": "List available LLM drivers and their dependencies"},
129
+ {
130
+ "action": "store_true",
131
+ "help": "List available LLM drivers and their dependencies",
132
+ },
133
+ ),
134
+ (
135
+ ["--region-info"],
136
+ {
137
+ "action": "store_true",
138
+ "help": "Show current region information and location",
139
+ },
140
+ ),
141
+ (
142
+ ["--list-providers-region"],
143
+ {
144
+ "action": "store_true",
145
+ "help": "List all providers with their regional API information",
146
+ },
123
147
  ),
124
148
  (
125
149
  ["-l", "--list-models"],
@@ -192,6 +216,7 @@ MODIFIER_KEYS = [
192
216
  "model",
193
217
  "role",
194
218
  "profile",
219
+ "python",
195
220
  "system",
196
221
  "temperature",
197
222
  "verbose",
@@ -211,6 +236,8 @@ GETTER_KEYS = [
211
236
  "list_tools",
212
237
  "list_config",
213
238
  "list_drivers",
239
+ "region_info",
240
+ "list_providers_region",
214
241
  ]
215
242
 
216
243
 
@@ -327,7 +354,14 @@ class JanitoCLI:
327
354
  if self._run_set_mode():
328
355
  return
329
356
  # Special handling: provider is not required for list_providers, list_tools, show_config, list_drivers
330
- if run_mode == RunMode.GET:
357
+ if run_mode == RunMode.GET and (
358
+ self.args.list_providers
359
+ or self.args.list_tools
360
+ or self.args.list_profiles
361
+ or self.args.show_config
362
+ or self.args.list_config
363
+ or self.args.list_drivers
364
+ ):
331
365
  self._maybe_print_verbose_provider_model()
332
366
  handle_getter(self.args)
333
367
  return
@@ -363,7 +397,8 @@ class JanitoCLI:
363
397
  agent_role,
364
398
  verbose_tools=self.args.verbose_tools,
365
399
  )
366
-
400
+ elif run_mode == RunMode.GET:
401
+ handle_getter(self.args)
367
402
 
368
403
  def _run_set_mode(self):
369
404
  if handle_api_key_set(self.args):
@@ -24,6 +24,11 @@ class PromptHandler:
24
24
  self.llm_driver_config = llm_driver_config
25
25
  self.role = role
26
26
  # Instantiate agent together with prompt handler using the shared helper
27
+ # Handle --python flag for single shot mode
28
+ profile = getattr(args, "profile", None)
29
+ if profile is None and getattr(args, "python", False):
30
+ profile = "Developer with Python Tools"
31
+
27
32
  self.agent, self.generic_handler = setup_agent_and_prompt_handler(
28
33
  args=args,
29
34
  provider_instance=provider_instance,
@@ -32,7 +37,7 @@ class PromptHandler:
32
37
  verbose_tools=getattr(args, "verbose_tools", False),
33
38
  verbose_agent=getattr(args, "verbose_agent", False),
34
39
  allowed_permissions=allowed_permissions,
35
- profile=getattr(args, "profile", None),
40
+ profile=profile,
36
41
  )
37
42
 
38
43
  def handle(self) -> None:
@@ -52,6 +57,43 @@ class PromptHandler:
52
57
 
53
58
  try:
54
59
  start_time = time.time()
60
+
61
+ # Print rule line with model info before processing prompt
62
+ model_name = (
63
+ self.agent.get_model_name()
64
+ if hasattr(self.agent, "get_model_name")
65
+ else "Unknown"
66
+ )
67
+ provider_name = (
68
+ self.agent.get_provider_name()
69
+ if hasattr(self.agent, "get_provider_name")
70
+ else "Unknown"
71
+ )
72
+
73
+ # Get backend hostname if available
74
+ backend_hostname = "Unknown"
75
+ if hasattr(self.agent, "driver") and self.agent.driver:
76
+ if hasattr(self.agent.driver, "config") and hasattr(
77
+ self.agent.driver.config, "base_url"
78
+ ):
79
+ base_url = self.agent.driver.config.base_url
80
+ if base_url:
81
+ try:
82
+ from urllib.parse import urlparse
83
+
84
+ parsed = urlparse(base_url)
85
+ backend_hostname = parsed.netloc
86
+ except Exception:
87
+ backend_hostname = base_url
88
+
89
+ from rich.rule import Rule
90
+
91
+ shared_console.print(
92
+ Rule(
93
+ f"[bold blue]Model: {model_name} ({provider_name}) | Backend: {backend_hostname}[/bold blue]"
94
+ )
95
+ )
96
+
55
97
  self.generic_handler.handle_prompt(
56
98
  sanitized,
57
99
  args=self.args,
@@ -6,6 +6,16 @@ from janito.llm.driver_config import LLMDriverConfig
6
6
 
7
7
 
8
8
  class AzureOpenAIModelDriver(OpenAIModelDriver):
9
+ # Check if required dependencies are available
10
+ try:
11
+ from openai import AzureOpenAI
12
+
13
+ available = True
14
+ unavailable_reason = None
15
+ except ImportError as e:
16
+ available = False
17
+ unavailable_reason = f"Missing dependency: {str(e)}"
18
+
9
19
  def start(self, *args, **kwargs):
10
20
  # Ensure azure_deployment_name is set before starting
11
21
  config = getattr(self, "config", None)
@@ -64,11 +74,10 @@ class AzureOpenAIModelDriver(OpenAIModelDriver):
64
74
  def _instantiate_openai_client(self, config):
65
75
  try:
66
76
  if not config.api_key:
67
- provider_name = getattr(self, 'provider_name', 'Azure OpenAI')
68
- print(f"[ERROR] No API key found for provider '{provider_name}'. Please set the API key using:")
69
- print(f" janito --set-api-key YOUR_API_KEY -p azure-openai")
70
- print(f"Or set the AZURE_OPENAI_API_KEY environment variable.")
71
- raise ValueError(f"API key is required for provider '{provider_name}'")
77
+ provider_name = getattr(self, "provider_name", "Azure OpenAI")
78
+ from janito.llm.auth_utils import handle_missing_api_key
79
+
80
+ handle_missing_api_key(provider_name, "AZURE_OPENAI_API_KEY")
72
81
 
73
82
  from openai import AzureOpenAI
74
83
 
@@ -16,6 +16,16 @@ import openai
16
16
 
17
17
 
18
18
  class OpenAIModelDriver(LLMDriver):
19
+ # Check if required dependencies are available
20
+ try:
21
+ import openai
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
+
19
29
  def _get_message_from_result(self, result):
20
30
  """Extract the message object from the provider result (OpenAI-specific)."""
21
31
  if hasattr(result, "choices") and result.choices:
@@ -248,11 +258,12 @@ class OpenAIModelDriver(LLMDriver):
248
258
  def _instantiate_openai_client(self, config):
249
259
  try:
250
260
  if not config.api_key:
251
- provider_name = getattr(self, 'provider_name', 'OpenAI-compatible')
252
- print(f"[ERROR] No API key found for provider '{provider_name}'. Please set the API key using:")
253
- print(f" janito --set-api-key YOUR_API_KEY -p {provider_name.lower()}")
254
- print(f"Or set the {provider_name.upper()}_API_KEY environment variable.")
255
- raise ValueError(f"API key is required for provider '{provider_name}'")
261
+ provider_name = getattr(self, "provider_name", "OpenAI-compatible")
262
+ from janito.llm.auth_utils import handle_missing_api_key
263
+
264
+ handle_missing_api_key(
265
+ provider_name, f"{provider_name.upper()}_API_KEY"
266
+ )
256
267
 
257
268
  api_key_display = str(config.api_key)
258
269
  if api_key_display and len(api_key_display) > 8: