ngpt 1.6.0__py3-none-any.whl → 1.7.1__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.
ngpt/cli.py CHANGED
@@ -68,6 +68,8 @@ def show_config_help():
68
68
  print(" ngpt --config --config-index 1")
69
69
  print(" 7. Remove a configuration at a specific index:")
70
70
  print(" ngpt --config --remove --config-index 1")
71
+ print(" 8. List available models for the current configuration:")
72
+ print(" ngpt --list-models")
71
73
 
72
74
  def check_config(config):
73
75
  """Check config for common issues and provide guidance."""
@@ -273,6 +275,7 @@ def main():
273
275
  config_group.add_argument('--remove', action='store_true', help='Remove the configuration at the specified index (requires --config and --config-index)')
274
276
  config_group.add_argument('--show-config', action='store_true', help='Show the current configuration(s) and exit')
275
277
  config_group.add_argument('--all', action='store_true', help='Show details for all configurations (requires --show-config)')
278
+ config_group.add_argument('--list-models', action='store_true', help='List all available models for the current configuration and exit')
276
279
 
277
280
  # Global options
278
281
  global_group = parser.add_argument_group('Global Options')
@@ -409,18 +412,35 @@ def main():
409
412
  return
410
413
 
411
414
  # For interactive mode, we'll allow continuing without a specific prompt
412
- if not args.prompt and not (args.shell or args.code or args.text or args.interactive):
415
+ if not args.prompt and not (args.shell or args.code or args.text or args.interactive or args.show_config or args.list_models):
413
416
  parser.print_help()
414
417
  return
415
418
 
416
419
  # Check configuration (using the potentially overridden active_config)
417
- if not check_config(active_config):
420
+ if not args.show_config and not args.list_models and not check_config(active_config):
418
421
  return
419
422
 
420
423
  # Initialize client using the potentially overridden active_config
421
424
  client = NGPTClient(**active_config)
422
425
 
423
426
  try:
427
+ # Handle listing models
428
+ if args.list_models:
429
+ print("Retrieving available models...")
430
+ models = client.list_models()
431
+ if models:
432
+ print(f"\nAvailable models for {active_config.get('provider', 'API')}:")
433
+ print("-" * 50)
434
+ for model in models:
435
+ if "id" in model:
436
+ owned_by = f" ({model.get('owned_by', 'Unknown')})" if "owned_by" in model else ""
437
+ current = " [active]" if model["id"] == active_config["model"] else ""
438
+ print(f"- {model['id']}{owned_by}{current}")
439
+ print("\nUse --model MODEL_NAME to select a specific model")
440
+ else:
441
+ print("No models available or could not retrieve models.")
442
+ return
443
+
424
444
  # Handle modes
425
445
  if args.interactive:
426
446
  # Interactive chat mode
ngpt/client.py CHANGED
@@ -18,12 +18,65 @@ class NGPTClient:
18
18
  self.base_url = base_url if base_url.endswith('/') else base_url + '/'
19
19
  self.model = model
20
20
 
21
+ # Detect provider based on base_url domain first
22
+ detected_provider = self._detect_provider_from_url(self.base_url)
23
+ self.provider = detected_provider if detected_provider else provider
24
+
21
25
  # Default headers
22
26
  self.headers = {
23
27
  "Content-Type": "application/json",
24
28
  "Authorization": f"Bearer {self.api_key}"
25
29
  }
26
30
 
31
+ def _detect_provider_from_url(self, url: str) -> str:
32
+ """
33
+ Detect the API provider based on the base URL domain.
34
+
35
+ Args:
36
+ url: The base URL to analyze
37
+
38
+ Returns:
39
+ Detected provider name or empty string if not detected
40
+ """
41
+ import re
42
+ from urllib.parse import urlparse
43
+
44
+ # Extract domain from URL
45
+ try:
46
+ parsed_url = urlparse(url)
47
+ domain = parsed_url.netloc.lower()
48
+
49
+ # Check for known API provider domains
50
+ if 'openai.com' in domain:
51
+ return 'OpenAI'
52
+ elif 'anthropic.com' in domain:
53
+ return 'Anthropic'
54
+ elif 'generativelanguage.googleapis.com' in domain:
55
+ return 'Gemini'
56
+ elif 'groq.com' in domain:
57
+ return 'Groq'
58
+ elif 'cohere.com' in domain or 'cohere.ai' in domain:
59
+ return 'Cohere'
60
+ elif 'mistral.ai' in domain:
61
+ return 'Mistral'
62
+ elif 'claude.ai' in domain:
63
+ return 'Claude'
64
+ elif re.match(r'(127\.0\.0\.1|localhost)', domain):
65
+ # Local APIs, check path components for clues
66
+ path = parsed_url.path.lower()
67
+ if '/ollama' in path:
68
+ return 'Ollama'
69
+ elif '/llama' in path:
70
+ return 'Llama'
71
+ # Default to the explicitly provided provider for local APIs
72
+ return ''
73
+
74
+ # Return empty string if no match found, will fall back to provided provider
75
+ return ''
76
+ except Exception:
77
+ # In case of any parsing error, fall back to provided provider
78
+ return ''
79
+
27
80
  def chat(
28
81
  self,
29
82
  prompt: str,
@@ -79,11 +132,26 @@ class NGPTClient:
79
132
  endpoint = "chat/completions"
80
133
  url = f"{self.base_url}{endpoint}"
81
134
 
135
+ # Set headers based on provider
136
+ headers = self.headers.copy()
137
+ provider = self.provider.lower() if hasattr(self, 'provider') else ""
138
+
139
+ # Provider-specific customizations
140
+ if 'gemini' in provider:
141
+ # Gemini uses X-Goog-Api-Key instead of Bearer token
142
+ headers = {
143
+ "Content-Type": "application/json",
144
+ "X-Goog-Api-Key": self.api_key
145
+ }
146
+
147
+ # Gemini may require a different endpoint or payload structure
148
+ # This is a simplistic approach - may need further customization
149
+
82
150
  try:
83
151
  if not stream:
84
152
  # Regular request
85
153
  try:
86
- response = requests.post(url, headers=self.headers, json=payload)
154
+ response = requests.post(url, headers=headers, json=payload)
87
155
  response.raise_for_status() # Raise exception for HTTP errors
88
156
  result = response.json()
89
157
 
@@ -97,7 +165,7 @@ class NGPTClient:
97
165
  else:
98
166
  # Streaming request
99
167
  collected_content = ""
100
- with requests.post(url, headers=self.headers, json=payload, stream=True) as response:
168
+ with requests.post(url, headers=headers, json=payload, stream=True) as response:
101
169
  response.raise_for_status() # Raise exception for HTTP errors
102
170
 
103
171
  try:
@@ -259,4 +327,89 @@ Code:"""
259
327
  )
260
328
  except Exception as e:
261
329
  print(f"Error generating code: {e}")
262
- return ""
330
+ return ""
331
+
332
+ def list_models(self) -> list:
333
+ """
334
+ Retrieve the list of available models from the API.
335
+
336
+ Returns:
337
+ List of available model objects or empty list if failed
338
+ """
339
+ if not self.api_key:
340
+ print("Error: API key is not set. Please configure your API key in the config file or provide it with --api-key.")
341
+ return []
342
+
343
+ provider = getattr(self, 'provider', '').lower()
344
+
345
+ # Handle provider-specific API differences
346
+ if 'gemini' in provider:
347
+ # Gemini API specific handling
348
+ # Base URL typically ends with /v1beta or similar - we want to add /models
349
+ base_url = self.base_url.rstrip('/')
350
+ if base_url.endswith('/v1beta'):
351
+ url = f"{base_url}/models"
352
+ else:
353
+ url = f"{base_url}/models"
354
+
355
+ # Gemini uses X-Goog-Api-Key instead of Bearer token
356
+ headers = {
357
+ "Content-Type": "application/json",
358
+ "X-Goog-Api-Key": self.api_key
359
+ }
360
+
361
+ try:
362
+ response = requests.get(url, headers=headers)
363
+ response.raise_for_status()
364
+ result = response.json()
365
+
366
+ # Gemini API returns models in a different format
367
+ if "models" in result:
368
+ # Transform to match our expected format
369
+ return [{"id": model.get("name", "").split("/")[-1],
370
+ "owned_by": "Google"}
371
+ for model in result["models"]]
372
+ else:
373
+ print("Error: Unexpected response format when retrieving Gemini models.")
374
+ return []
375
+
376
+ except requests.exceptions.HTTPError as e:
377
+ print(f"HTTP Error with Gemini API: {e}")
378
+ return []
379
+
380
+ except Exception as e:
381
+ print(f"Error retrieving Gemini models: {e}")
382
+ return []
383
+ else:
384
+ # Standard OpenAI-compatible endpoint
385
+ url = f"{self.base_url}models"
386
+
387
+ try:
388
+ response = requests.get(url, headers=self.headers)
389
+ response.raise_for_status() # Raise exception for HTTP errors
390
+ result = response.json()
391
+
392
+ if "data" in result:
393
+ return result["data"]
394
+ else:
395
+ print("Error: Unexpected response format when retrieving models.")
396
+ return []
397
+
398
+ except requests.exceptions.HTTPError as e:
399
+ if e.response.status_code == 401:
400
+ print("Error: Authentication failed. Please check your API key.")
401
+ elif e.response.status_code == 404:
402
+ print(f"Error: Models endpoint not found at {url}")
403
+ elif e.response.status_code == 429:
404
+ print("Error: Rate limit exceeded. Please try again later.")
405
+ else:
406
+ print(f"HTTP Error: {e}")
407
+ return []
408
+
409
+ except requests.exceptions.ConnectionError:
410
+ print(f"Error: Could not connect to {self.base_url}. Please check your internet connection and base URL.")
411
+ return []
412
+
413
+ except Exception as e:
414
+ print(f"Error: An unexpected error occurred while retrieving models: {e}")
415
+ return []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngpt
3
- Version: 1.6.0
3
+ Version: 1.7.1
4
4
  Summary: A lightweight Python CLI and library for interacting with OpenAI-compatible APIs, supporting both official and self-hosted LLM endpoints.
5
5
  Project-URL: Homepage, https://github.com/nazdridoy/ngpt
6
6
  Project-URL: Repository, https://github.com/nazdridoy/ngpt
@@ -121,6 +121,9 @@ ngpt --show-config
121
121
  # Show all configurations
122
122
  ngpt --show-config --all
123
123
 
124
+ # List available models for the current configuration
125
+ ngpt --list-models
126
+
124
127
  # With custom options
125
128
  ngpt --api-key your-key --base-url http://your-endpoint --model your-model "Hello"
126
129
 
@@ -211,6 +214,7 @@ You can configure the client using the following options:
211
214
  | `--api-key` | API key for the service |
212
215
  | `--base-url` | Base URL for the API |
213
216
  | `--model` | Model to use |
217
+ | `--list-models` | List all available models for the current configuration |
214
218
  | `--web-search` | Enable web search capability |
215
219
  | `-n, --no-stream` | Return the whole response without streaming |
216
220
  | `--config` | Path to a custom configuration file or, when used without a value, enters interactive configuration mode |
@@ -0,0 +1,9 @@
1
+ ngpt/__init__.py,sha256=ehInP9w0MZlS1vZ1g6Cm4YE1ftmgF72CnEddQ3Le9n4,368
2
+ ngpt/cli.py,sha256=AyIraZFq7icPot0moqPVJer72iqbtJxKBhy6VH-dwAA,28746
3
+ ngpt/client.py,sha256=qwAmBHSJvadbsvK5YPbY0By1M4bBvxd7j8piP5UdTgE,16553
4
+ ngpt/config.py,sha256=BF0G3QeiPma8l7EQyc37bR7LWZog7FHJQNe7uj9cr4w,6896
5
+ ngpt-1.7.1.dist-info/METADATA,sha256=ffoVoKaC58f-SKImuqxLfGOv9Umlpmou82aCFWpsfIM,10568
6
+ ngpt-1.7.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
+ ngpt-1.7.1.dist-info/entry_points.txt,sha256=1cnAMujyy34DlOahrJg19lePSnb08bLbkUs_kVerqdk,39
8
+ ngpt-1.7.1.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
9
+ ngpt-1.7.1.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- ngpt/__init__.py,sha256=ehInP9w0MZlS1vZ1g6Cm4YE1ftmgF72CnEddQ3Le9n4,368
2
- ngpt/cli.py,sha256=brSdM8uFBbVktbpK6CDAFEzmKzBKb47OSK1G9ludDYg,27564
3
- ngpt/client.py,sha256=O0dPYeQCJlpWZWBBsroo-5UxeyBVwqC6o3Pm8lRnDiY,10329
4
- ngpt/config.py,sha256=BF0G3QeiPma8l7EQyc37bR7LWZog7FHJQNe7uj9cr4w,6896
5
- ngpt-1.6.0.dist-info/METADATA,sha256=hqt6-L2cmyQ_GgoYEOf-_MVUlrm6n1JoxzgAqmVgFkc,10416
6
- ngpt-1.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
- ngpt-1.6.0.dist-info/entry_points.txt,sha256=1cnAMujyy34DlOahrJg19lePSnb08bLbkUs_kVerqdk,39
8
- ngpt-1.6.0.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
9
- ngpt-1.6.0.dist-info/RECORD,,
File without changes