llms-py 3.0.10__py3-none-any.whl → 3.0.12__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.
llms/extensions/app/db.py CHANGED
@@ -528,9 +528,9 @@ class AppDB:
528
528
  with self.db.create_writer_connection() as conn:
529
529
  conn.execute(
530
530
  "UPDATE thread SET completedAt = :completedAt, error = :error WHERE completedAt IS NULL",
531
- {"completedAt": datetime.now(), "error": "Server Shutdown"},
531
+ {"completedAt": datetime.now().isoformat(" "), "error": "Server Shutdown"},
532
532
  )
533
533
  conn.execute(
534
534
  "UPDATE request SET completedAt = :completedAt, error = :error WHERE completedAt IS NULL",
535
- {"completedAt": datetime.now(), "error": "Server Shutdown"},
535
+ {"completedAt": datetime.now().isoformat(" "), "error": "Server Shutdown"},
536
536
  )
@@ -77,9 +77,17 @@ def install_anthropic(ctx):
77
77
 
78
78
  anthropic_message = {"role": message.get("role"), "content": []}
79
79
 
80
+ # Handle interleaved thinking (must always be a list if present)
81
+ if "thinking" in message and message["thinking"]:
82
+ anthropic_message["content"].append({"type": "thinking", "thinking": message["thinking"]})
83
+
80
84
  content = message.get("content", "")
81
85
  if isinstance(content, str):
82
- anthropic_message["content"] = content
86
+ if anthropic_message["content"]:
87
+ # If we have thinking, we must use blocks for text
88
+ anthropic_message["content"].append({"type": "text", "text": content})
89
+ else:
90
+ anthropic_message["content"] = content
83
91
  elif isinstance(content, list):
84
92
  for item in content:
85
93
  if item.get("type") == "text":
@@ -63,6 +63,50 @@ def install_google(ctx):
63
63
  to[k] = v
64
64
  return to
65
65
 
66
+ def sanitize_parameters(params):
67
+ """Sanitize tool parameters for Google provider."""
68
+
69
+ if not isinstance(params, dict):
70
+ return params
71
+
72
+ # Create a copy to avoid modifying original tool definition
73
+ p = params.copy()
74
+
75
+ # Remove forbidden fields
76
+ for forbidden in ["$schema", "additionalProperties"]:
77
+ if forbidden in p:
78
+ del p[forbidden]
79
+
80
+ # Recursively sanitize known nesting fields
81
+ # 1. Properties (dict of schemas)
82
+ if "properties" in p:
83
+ for k, v in p["properties"].items():
84
+ p["properties"][k] = sanitize_parameters(v)
85
+
86
+ # 2. Items (schema or list of schemas)
87
+ if "items" in p:
88
+ if isinstance(p["items"], list):
89
+ p["items"] = [sanitize_parameters(i) for i in p["items"]]
90
+ else:
91
+ p["items"] = sanitize_parameters(p["items"])
92
+
93
+ # 3. Combinators (list of schemas)
94
+ for combinator in ["allOf", "anyOf", "oneOf"]:
95
+ if combinator in p:
96
+ p[combinator] = [sanitize_parameters(i) for i in p[combinator]]
97
+
98
+ # 4. Not (schema)
99
+ if "not" in p:
100
+ p["not"] = sanitize_parameters(p["not"])
101
+
102
+ # 5. Definitions (dict of schemas)
103
+ for def_key in ["definitions", "$defs"]:
104
+ if def_key in p:
105
+ for k, v in p[def_key].items():
106
+ p[def_key][k] = sanitize_parameters(v)
107
+
108
+ return p
109
+
66
110
  class GoogleProvider(OpenAiCompatible):
67
111
  sdk = "@ai-sdk/google"
68
112
 
@@ -112,11 +156,12 @@ def install_google(ctx):
112
156
  for tool in chat["tools"]:
113
157
  if tool["type"] == "function":
114
158
  f = tool["function"]
159
+
115
160
  function_declarations.append(
116
161
  {
117
162
  "name": f["name"],
118
163
  "description": f.get("description"),
119
- "parameters": f.get("parameters"),
164
+ "parameters": sanitize_parameters(f.get("parameters")),
120
165
  }
121
166
  )
122
167
  elif tool["type"] == "file_search":
@@ -183,13 +228,19 @@ def install_google(ctx):
183
228
  if name:
184
229
  # content is the string response
185
230
  # Some implementations pass the content directly.
186
- # Google docs say: response: { "name": "...", "content": { ... } }
187
- # Actually "response" field in functionResponse is a Struct/Map.
231
+ # Google docs say: response: { "key": "value" }
232
+ try:
233
+ response_data = json.loads(message["content"])
234
+ if not isinstance(response_data, dict):
235
+ response_data = {"content": message["content"]}
236
+ except Exception:
237
+ response_data = {"content": message["content"]}
238
+
188
239
  parts.append(
189
240
  {
190
241
  "functionResponse": {
191
242
  "name": name,
192
- "response": {"name": name, "content": message["content"]},
243
+ "response": response_data,
193
244
  }
194
245
  }
195
246
  )
llms/main.py CHANGED
@@ -18,6 +18,7 @@ import mimetypes
18
18
  import os
19
19
  import re
20
20
  import secrets
21
+ import shlex
21
22
  import shutil
22
23
  import site
23
24
  import subprocess
@@ -25,10 +26,11 @@ import sys
25
26
  import time
26
27
  import traceback
27
28
  from datetime import datetime
29
+ from enum import IntEnum
28
30
  from importlib import resources # Py≥3.9 (pip install importlib_resources for 3.7/3.8)
29
31
  from io import BytesIO
30
32
  from pathlib import Path
31
- from typing import Optional, get_type_hints
33
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Union, get_type_hints
32
34
  from urllib.parse import parse_qs, urlencode, urljoin
33
35
 
34
36
  import aiohttp
@@ -41,7 +43,7 @@ try:
41
43
  except ImportError:
42
44
  HAS_PIL = False
43
45
 
44
- VERSION = "3.0.10"
46
+ VERSION = "3.0.12"
45
47
  _ROOT = None
46
48
  DEBUG = os.getenv("DEBUG") == "1"
47
49
  MOCK = os.getenv("MOCK") == "1"
@@ -59,6 +61,12 @@ g_oauth_states = {} # CSRF protection: {state: {created, redirect_uri}}
59
61
  g_app = None # ExtensionsContext Singleton
60
62
 
61
63
 
64
+ class ExitCode(IntEnum):
65
+ SUCCESS = 0
66
+ FAILED = 1
67
+ UNHANDLED = 9
68
+
69
+
62
70
  def _log(message):
63
71
  if g_verbose:
64
72
  print(f"{g_logprefix}{message}", flush=True)
@@ -1620,10 +1628,15 @@ async def g_chat_completion(chat, context=None):
1620
1628
  tool_history = []
1621
1629
  final_response = None
1622
1630
 
1623
- for _ in range(max_iterations):
1631
+ for request_count in range(max_iterations):
1624
1632
  if should_cancel_thread(context):
1625
1633
  return
1626
1634
 
1635
+ if DEBUG:
1636
+ messages = current_chat.get("messages", [])
1637
+ last_message = messages[-1] if messages else None
1638
+ _dbg(f"Provider {provider_name}, request {request_count}:\n{json.dumps(last_message, indent=2)}")
1639
+
1627
1640
  response = await provider.chat(current_chat, context=context)
1628
1641
 
1629
1642
  if should_cancel_thread(context):
@@ -1853,12 +1866,18 @@ def config_str(key):
1853
1866
  return key in g_config and g_config[key] or None
1854
1867
 
1855
1868
 
1856
- def load_config(config, providers, verbose=None):
1869
+ def load_config(config, providers, verbose=None, debug=None, disable_extensions: List[str] = None):
1857
1870
  global g_config, g_providers, g_verbose
1858
1871
  g_config = config
1859
1872
  g_providers = providers
1860
- if verbose:
1873
+ if verbose is not None:
1861
1874
  g_verbose = verbose
1875
+ if debug is not None:
1876
+ global DEBUG
1877
+ DEBUG = debug
1878
+ if disable_extensions:
1879
+ global DISABLE_EXTENSIONS
1880
+ DISABLE_EXTENSIONS = disable_extensions
1862
1881
 
1863
1882
 
1864
1883
  def init_llms(config, providers):
@@ -2539,7 +2558,7 @@ class AppExtensions:
2539
2558
  APIs extensions can use to extend the app
2540
2559
  """
2541
2560
 
2542
- def __init__(self, cli_args, extra_args):
2561
+ def __init__(self, cli_args: argparse.Namespace, extra_args: Dict[str, Any]):
2543
2562
  self.cli_args = cli_args
2544
2563
  self.extra_args = extra_args
2545
2564
  self.config = None
@@ -2615,12 +2634,12 @@ class AppExtensions:
2615
2634
  "ctx.mjs": "/ui/ctx.mjs",
2616
2635
  }
2617
2636
 
2618
- def set_config(self, config):
2637
+ def set_config(self, config: Dict[str, Any]):
2619
2638
  self.config = config
2620
2639
  self.auth_enabled = self.config.get("auth", {}).get("enabled", False)
2621
2640
 
2622
2641
  # Authentication middleware helper
2623
- def check_auth(self, request):
2642
+ def check_auth(self, request: web.Request) -> Tuple[bool, Optional[Dict[str, Any]]]:
2624
2643
  """Check if request is authenticated. Returns (is_authenticated, user_data)"""
2625
2644
  if not self.auth_enabled:
2626
2645
  return True, None
@@ -2639,7 +2658,7 @@ class AppExtensions:
2639
2658
 
2640
2659
  return False, None
2641
2660
 
2642
- def get_session(self, request):
2661
+ def get_session(self, request: web.Request) -> Optional[Dict[str, Any]]:
2643
2662
  session_token = get_session_token(request)
2644
2663
 
2645
2664
  if not session_token or session_token not in g_sessions:
@@ -2648,30 +2667,39 @@ class AppExtensions:
2648
2667
  session_data = g_sessions[session_token]
2649
2668
  return session_data
2650
2669
 
2651
- def get_username(self, request):
2670
+ def get_username(self, request: web.Request) -> Optional[str]:
2652
2671
  session = self.get_session(request)
2653
2672
  if session:
2654
2673
  return session.get("userName")
2655
2674
  return None
2656
2675
 
2657
- def get_user_path(self, username=None):
2676
+ def get_user_path(self, username: Optional[str] = None) -> str:
2658
2677
  if username:
2659
2678
  return home_llms_path(os.path.join("user", username))
2660
2679
  return home_llms_path(os.path.join("user", "default"))
2661
2680
 
2662
- def chat_request(self, template=None, text=None, model=None, system_prompt=None):
2681
+ def get_providers(self) -> Dict[str, Any]:
2682
+ return g_handlers
2683
+
2684
+ def chat_request(
2685
+ self,
2686
+ template: Optional[str] = None,
2687
+ text: Optional[str] = None,
2688
+ model: Optional[str] = None,
2689
+ system_prompt: Optional[str] = None,
2690
+ ) -> Dict[str, Any]:
2663
2691
  return g_chat_request(template=template, text=text, model=model, system_prompt=system_prompt)
2664
2692
 
2665
- async def chat_completion(self, chat, context=None):
2693
+ async def chat_completion(self, chat: Dict[str, Any], context: Optional[Dict[str, Any]] = None) -> Any:
2666
2694
  response = await g_chat_completion(chat, context)
2667
2695
  return response
2668
2696
 
2669
- def on_cache_saved_filters(self, context):
2697
+ def on_cache_saved_filters(self, context: Dict[str, Any]):
2670
2698
  # _log(f"on_cache_saved_filters {len(self.cache_saved_filters)}: {context['url']}")
2671
2699
  for filter_func in self.cache_saved_filters:
2672
2700
  filter_func(context)
2673
2701
 
2674
- async def on_chat_error(self, e, context):
2702
+ async def on_chat_error(self, e: Exception, context: Dict[str, Any]):
2675
2703
  # Apply chat error filters
2676
2704
  if "stackTrace" not in context:
2677
2705
  context["stackTrace"] = traceback.format_exc()
@@ -2681,25 +2709,27 @@ class AppExtensions:
2681
2709
  except Exception as e:
2682
2710
  _err("chat error filter failed", e)
2683
2711
 
2684
- async def on_chat_tool(self, chat, context):
2712
+ async def on_chat_tool(self, chat: Dict[str, Any], context: Dict[str, Any]):
2685
2713
  m_len = len(chat.get("messages", []))
2686
2714
  t_len = len(self.chat_tool_filters)
2687
2715
  _dbg(
2688
- f"on_tool_call for thread {context.get('threadId', None)} with {m_len} {pluralize('message', m_len)}, invoking {t_len} {pluralize('filter', t_len)}:"
2716
+ f"on_tool_call for thread {context.get('threadId')} with {m_len} {pluralize('message', m_len)}, invoking {t_len} {pluralize('filter', t_len)}:"
2689
2717
  )
2690
2718
  for filter_func in self.chat_tool_filters:
2691
2719
  await filter_func(chat, context)
2692
2720
 
2693
- def exit(self, exit_code=0):
2721
+ def shutdown(self):
2694
2722
  if len(self.shutdown_handlers) > 0:
2695
2723
  _dbg(f"running {len(self.shutdown_handlers)} shutdown handlers...")
2696
2724
  for handler in self.shutdown_handlers:
2697
2725
  handler()
2698
2726
 
2727
+ def exit(self, exit_code: int = 0):
2728
+ self.shutdown()
2699
2729
  _dbg(f"exit({exit_code})")
2700
2730
  sys.exit(exit_code)
2701
2731
 
2702
- def create_chat_with_tools(self, chat, use_tools="all"):
2732
+ def create_chat_with_tools(self, chat: Dict[str, Any], use_tools: str = "all") -> Dict[str, Any]:
2703
2733
  # Inject global tools if present
2704
2734
  current_chat = chat.copy()
2705
2735
  tools = current_chat.get("tools")
@@ -2728,7 +2758,7 @@ def handler_name(handler):
2728
2758
 
2729
2759
 
2730
2760
  class ExtensionContext:
2731
- def __init__(self, app, path):
2761
+ def __init__(self, app: AppExtensions, path: str):
2732
2762
  self.app = app
2733
2763
  self.cli_args = app.cli_args
2734
2764
  self.extra_args = app.extra_args
@@ -2746,101 +2776,107 @@ class ExtensionContext:
2746
2776
  self.request_args = app.request_args
2747
2777
  self.disabled = False
2748
2778
 
2749
- def chat_to_prompt(self, chat):
2779
+ def chat_to_prompt(self, chat: Dict[str, Any]) -> str:
2750
2780
  return chat_to_prompt(chat)
2751
2781
 
2752
- def chat_to_system_prompt(self, chat):
2782
+ def chat_to_system_prompt(self, chat: Dict[str, Any]) -> str:
2753
2783
  return chat_to_system_prompt(chat)
2754
2784
 
2755
- def chat_response_to_message(self, response):
2785
+ def chat_response_to_message(self, response: Dict[str, Any]) -> Dict[str, Any]:
2756
2786
  return chat_response_to_message(response)
2757
2787
 
2758
- def last_user_prompt(self, chat):
2788
+ def last_user_prompt(self, chat: Dict[str, Any]) -> str:
2759
2789
  return last_user_prompt(chat)
2760
2790
 
2761
- def to_file_info(self, chat, info=None, response=None):
2791
+ def to_file_info(
2792
+ self, chat: Dict[str, Any], info: Optional[Dict[str, Any]] = None, response: Optional[Dict[str, Any]] = None
2793
+ ) -> Dict[str, Any]:
2762
2794
  return to_file_info(chat, info=info, response=response)
2763
2795
 
2764
- def save_image_to_cache(self, base64_data, filename, image_info, ignore_info=False):
2796
+ def save_image_to_cache(
2797
+ self, base64_data: Union[str, bytes], filename: str, image_info: Dict[str, Any], ignore_info: bool = False
2798
+ ) -> Tuple[str, Optional[Dict[str, Any]]]:
2765
2799
  return save_image_to_cache(base64_data, filename, image_info, ignore_info=ignore_info)
2766
2800
 
2767
- def save_bytes_to_cache(self, bytes_data, filename, file_info):
2801
+ def save_bytes_to_cache(
2802
+ self, bytes_data: Union[str, bytes], filename: str, file_info: Optional[Dict[str, Any]]
2803
+ ) -> Tuple[str, Optional[Dict[str, Any]]]:
2768
2804
  return save_bytes_to_cache(bytes_data, filename, file_info)
2769
2805
 
2770
- def text_from_file(self, path):
2806
+ def text_from_file(self, path: str) -> str:
2771
2807
  return text_from_file(path)
2772
2808
 
2773
- def json_from_file(self, path):
2809
+ def json_from_file(self, path: str) -> Any:
2774
2810
  return json_from_file(path)
2775
2811
 
2776
- def download_file(self, url):
2812
+ def download_file(self, url: str) -> Tuple[bytes, Dict[str, Any]]:
2777
2813
  return download_file(url)
2778
2814
 
2779
- def session_download_file(self, session, url):
2815
+ def session_download_file(self, session: aiohttp.ClientSession, url: str) -> Tuple[bytes, Dict[str, Any]]:
2780
2816
  return session_download_file(session, url)
2781
2817
 
2782
- def read_binary_file(self, url):
2818
+ def read_binary_file(self, url: str) -> Tuple[bytes, Dict[str, Any]]:
2783
2819
  return read_binary_file(url)
2784
2820
 
2785
- def log(self, message):
2821
+ def log(self, message: Any):
2786
2822
  if self.verbose:
2787
2823
  print(f"[{self.name}] {message}", flush=True)
2788
2824
  return message
2789
2825
 
2790
- def log_json(self, obj):
2826
+ def log_json(self, obj: Any):
2791
2827
  if self.verbose:
2792
2828
  print(f"[{self.name}] {json.dumps(obj, indent=2)}", flush=True)
2793
2829
  return obj
2794
2830
 
2795
- def dbg(self, message):
2831
+ def dbg(self, message: Any):
2796
2832
  if self.debug:
2797
2833
  print(f"DEBUG [{self.name}]: {message}", flush=True)
2798
2834
 
2799
- def err(self, message, e):
2835
+ def err(self, message: str, e: Exception):
2800
2836
  print(f"ERROR [{self.name}]: {message}", e)
2801
2837
  if self.verbose:
2802
2838
  print(traceback.format_exc(), flush=True)
2803
2839
 
2804
- def error_message(self, e):
2840
+ def error_message(self, e: Exception) -> str:
2805
2841
  return to_error_message(e)
2806
2842
 
2807
- def error_response(self, e, stacktrace=False):
2843
+ def error_response(self, e: Exception, stacktrace: bool = False) -> Dict[str, Any]:
2808
2844
  return to_error_response(e, stacktrace=stacktrace)
2809
2845
 
2810
- def add_provider(self, provider):
2846
+ def add_provider(self, provider: Any):
2811
2847
  self.log(f"Registered provider: {provider.__name__}")
2812
2848
  self.app.all_providers.append(provider)
2813
2849
 
2814
- def register_ui_extension(self, index):
2850
+ def register_ui_extension(self, index: str):
2815
2851
  path = os.path.join(self.ext_prefix, index)
2816
2852
  self.log(f"Registered UI extension: {path}")
2817
2853
  self.app.ui_extensions.append({"id": self.name, "path": path})
2818
2854
 
2819
- def register_chat_request_filter(self, handler):
2855
+ def register_chat_request_filter(self, handler: Callable):
2820
2856
  self.log(f"Registered chat request filter: {handler_name(handler)}")
2821
2857
  self.app.chat_request_filters.append(handler)
2822
2858
 
2823
- def register_chat_tool_filter(self, handler):
2859
+ def register_chat_tool_filter(self, handler: Callable):
2824
2860
  self.log(f"Registered chat tool filter: {handler_name(handler)}")
2825
2861
  self.app.chat_tool_filters.append(handler)
2826
2862
 
2827
- def register_chat_response_filter(self, handler):
2863
+ def register_chat_response_filter(self, handler: Callable):
2828
2864
  self.log(f"Registered chat response filter: {handler_name(handler)}")
2829
2865
  self.app.chat_response_filters.append(handler)
2830
2866
 
2831
- def register_chat_error_filter(self, handler):
2867
+ def register_chat_error_filter(self, handler: Callable):
2832
2868
  self.log(f"Registered chat error filter: {handler_name(handler)}")
2833
2869
  self.app.chat_error_filters.append(handler)
2834
2870
 
2835
- def register_cache_saved_filter(self, handler):
2871
+ def register_cache_saved_filter(self, handler: Callable):
2836
2872
  self.log(f"Registered cache saved filter: {handler_name(handler)}")
2837
2873
  self.app.cache_saved_filters.append(handler)
2838
2874
 
2839
- def register_shutdown_handler(self, handler):
2875
+ def register_shutdown_handler(self, handler: Callable):
2840
2876
  self.log(f"Registered shutdown handler: {handler_name(handler)}")
2841
2877
  self.app.shutdown_handlers.append(handler)
2842
2878
 
2843
- def add_static_files(self, ext_dir):
2879
+ def add_static_files(self, ext_dir: str):
2844
2880
  self.log(f"Registered static files: {ext_dir}")
2845
2881
 
2846
2882
  async def serve_static(request):
@@ -2852,57 +2888,63 @@ class ExtensionContext:
2852
2888
 
2853
2889
  self.app.server_add_get.append((os.path.join(self.ext_prefix, "{path:.*}"), serve_static, {}))
2854
2890
 
2855
- def web_path(self, method, path):
2891
+ def web_path(self, method: str, path: str) -> str:
2856
2892
  full_path = os.path.join(self.ext_prefix, path) if path else self.ext_prefix
2857
2893
  self.dbg(f"Registered {method:<6} {full_path}")
2858
2894
  return full_path
2859
2895
 
2860
- def add_get(self, path, handler, **kwargs):
2896
+ def add_get(self, path: str, handler: Callable, **kwargs: Any):
2861
2897
  self.app.server_add_get.append((self.web_path("GET", path), handler, kwargs))
2862
2898
 
2863
- def add_post(self, path, handler, **kwargs):
2899
+ def add_post(self, path: str, handler: Callable, **kwargs: Any):
2864
2900
  self.app.server_add_post.append((self.web_path("POST", path), handler, kwargs))
2865
2901
 
2866
- def add_put(self, path, handler, **kwargs):
2902
+ def add_put(self, path: str, handler: Callable, **kwargs: Any):
2867
2903
  self.app.server_add_put.append((self.web_path("PUT", path), handler, kwargs))
2868
2904
 
2869
- def add_delete(self, path, handler, **kwargs):
2905
+ def add_delete(self, path: str, handler: Callable, **kwargs: Any):
2870
2906
  self.app.server_add_delete.append((self.web_path("DELETE", path), handler, kwargs))
2871
2907
 
2872
- def add_patch(self, path, handler, **kwargs):
2908
+ def add_patch(self, path: str, handler: Callable, **kwargs: Any):
2873
2909
  self.app.server_add_patch.append((self.web_path("PATCH", path), handler, kwargs))
2874
2910
 
2875
- def add_importmaps(self, dict):
2911
+ def add_importmaps(self, dict: Dict[str, str]):
2876
2912
  self.app.import_maps.update(dict)
2877
2913
 
2878
- def add_index_header(self, html):
2914
+ def add_index_header(self, html: str):
2879
2915
  self.app.index_headers.append(html)
2880
2916
 
2881
- def add_index_footer(self, html):
2917
+ def add_index_footer(self, html: str):
2882
2918
  self.app.index_footers.append(html)
2883
2919
 
2884
- def get_config(self):
2920
+ def get_config(self) -> Optional[Dict[str, Any]]:
2885
2921
  return g_config
2886
2922
 
2887
- def get_cache_path(self, path=""):
2923
+ def get_cache_path(self, path: str = "") -> str:
2888
2924
  return get_cache_path(path)
2889
2925
 
2890
- def get_file_mime_type(self, filename):
2926
+ def get_file_mime_type(self, filename: str) -> str:
2891
2927
  return get_file_mime_type(filename)
2892
2928
 
2893
- def chat_request(self, template=None, text=None, model=None, system_prompt=None):
2929
+ def chat_request(
2930
+ self,
2931
+ template: Optional[str] = None,
2932
+ text: Optional[str] = None,
2933
+ model: Optional[str] = None,
2934
+ system_prompt: Optional[str] = None,
2935
+ ) -> Dict[str, Any]:
2894
2936
  return self.app.chat_request(template=template, text=text, model=model, system_prompt=system_prompt)
2895
2937
 
2896
- def chat_completion(self, chat, context=None):
2897
- return self.app.chat_completion(chat, context=context)
2938
+ async def chat_completion(self, chat: Dict[str, Any], context: Optional[Dict[str, Any]] = None) -> Any:
2939
+ return await self.app.chat_completion(chat, context=context)
2898
2940
 
2899
- def get_providers(self):
2941
+ def get_providers(self) -> Dict[str, Any]:
2900
2942
  return g_handlers
2901
2943
 
2902
- def get_provider(self, name):
2944
+ def get_provider(self, name: str) -> Optional[Any]:
2903
2945
  return g_handlers.get(name)
2904
2946
 
2905
- def sanitize_tool_def(self, tool_def):
2947
+ def sanitize_tool_def(self, tool_def: Dict[str, Any]) -> Dict[str, Any]:
2906
2948
  """
2907
2949
  Merge $defs parameter into tool_def property to reduce client/server complexity
2908
2950
  """
@@ -2942,7 +2984,7 @@ class ExtensionContext:
2942
2984
  parameters = func_def.get("parameters", {})
2943
2985
  defs = parameters.get("$defs", {})
2944
2986
  properties = parameters.get("properties", {})
2945
- for prop_name, prop_def in properties.items():
2987
+ for _, prop_def in properties.items():
2946
2988
  if "$ref" in prop_def:
2947
2989
  ref = prop_def["$ref"]
2948
2990
  if ref.startswith("#/$defs/"):
@@ -2954,7 +2996,7 @@ class ExtensionContext:
2954
2996
  del parameters["$defs"]
2955
2997
  return tool_def
2956
2998
 
2957
- def register_tool(self, func, tool_def=None, group=None):
2999
+ def register_tool(self, func: Callable, tool_def: Optional[Dict[str, Any]] = None, group: Optional[str] = None):
2958
3000
  if tool_def is None:
2959
3001
  tool_def = function_to_tool_definition(func)
2960
3002
 
@@ -2976,54 +3018,61 @@ class ExtensionContext:
2976
3018
  self.app.tool_groups[group] = []
2977
3019
  self.app.tool_groups[group].append(name)
2978
3020
 
2979
- def get_tool_definition(self, name):
3021
+ def get_tool_definition(self, name: str) -> Optional[Dict[str, Any]]:
2980
3022
  for tool_def in self.app.tool_definitions:
2981
3023
  if tool_def["function"]["name"] == name:
2982
3024
  return tool_def
2983
3025
  return None
2984
3026
 
2985
- def group_resources(self, resources: list):
3027
+ def group_resources(self, resources: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
2986
3028
  return group_resources(resources)
2987
3029
 
2988
- def check_auth(self, request):
3030
+ def check_auth(self, request: web.Request) -> Tuple[bool, Optional[Dict[str, Any]]]:
2989
3031
  return self.app.check_auth(request)
2990
3032
 
2991
- def get_session(self, request):
3033
+ def get_session(self, request: web.Request) -> Optional[Dict[str, Any]]:
2992
3034
  return self.app.get_session(request)
2993
3035
 
2994
- def get_username(self, request):
3036
+ def get_username(self, request: web.Request) -> Optional[str]:
2995
3037
  return self.app.get_username(request)
2996
3038
 
2997
- def get_user_path(self, username=None):
3039
+ def get_user_path(self, username: Optional[str] = None) -> str:
2998
3040
  return self.app.get_user_path(username)
2999
3041
 
3000
- def context_to_username(self, context):
3042
+ def context_to_username(self, context: Optional[Dict[str, Any]]) -> Optional[str]:
3001
3043
  if context and "request" in context:
3002
3044
  return self.get_username(context["request"])
3003
3045
  return None
3004
3046
 
3005
- def should_cancel_thread(self, context):
3047
+ def should_cancel_thread(self, context: Dict[str, Any]) -> bool:
3006
3048
  return should_cancel_thread(context)
3007
3049
 
3008
- def cache_message_inline_data(self, message):
3050
+ def cache_message_inline_data(self, message: Dict[str, Any]):
3009
3051
  return cache_message_inline_data(message)
3010
3052
 
3011
- async def exec_tool(self, name, args):
3053
+ async def exec_tool(self, name: str, args: Dict[str, Any]) -> Tuple[Optional[str], List[Dict[str, Any]]]:
3012
3054
  return await g_exec_tool(name, args)
3013
3055
 
3014
- def tool_result(self, result, function_name: Optional[str] = None, function_args: Optional[dict] = None):
3056
+ def tool_result(
3057
+ self, result: Any, function_name: Optional[str] = None, function_args: Optional[Dict[str, Any]] = None
3058
+ ) -> Dict[str, Any]:
3015
3059
  return g_tool_result(result, function_name, function_args)
3016
3060
 
3017
- def tool_result_part(self, result: dict, function_name: Optional[str] = None, function_args: Optional[dict] = None):
3061
+ def tool_result_part(
3062
+ self,
3063
+ result: Dict[str, Any],
3064
+ function_name: Optional[str] = None,
3065
+ function_args: Optional[Dict[str, Any]] = None,
3066
+ ) -> Dict[str, Any]:
3018
3067
  return tool_result_part(result, function_name, function_args)
3019
3068
 
3020
- def to_content(self, result):
3069
+ def to_content(self, result: Any) -> str:
3021
3070
  return to_content(result)
3022
3071
 
3023
- def create_chat_with_tools(self, chat, use_tools="all"):
3072
+ def create_chat_with_tools(self, chat: Dict[str, Any], use_tools: str = "all") -> Dict[str, Any]:
3024
3073
  return self.app.create_chat_with_tools(chat, use_tools)
3025
3074
 
3026
- def chat_to_aspect_ratio(self, chat):
3075
+ def chat_to_aspect_ratio(self, chat: Dict[str, Any]) -> str:
3027
3076
  return chat_to_aspect_ratio(chat)
3028
3077
 
3029
3078
 
@@ -3080,7 +3129,21 @@ def get_extensions_dirs():
3080
3129
  return ret
3081
3130
 
3082
3131
 
3132
+ def verify_root_path():
3133
+ global _ROOT
3134
+ _ROOT = os.getenv("LLMS_ROOT", resolve_root())
3135
+ if not _ROOT:
3136
+ print("Resource root not found")
3137
+ exit(1)
3138
+
3139
+
3083
3140
  def init_extensions(parser):
3141
+ """
3142
+ Programmatic entry point for the CLI.
3143
+ Example: cli("ls minimax")
3144
+ """
3145
+ verify_root_path()
3146
+
3084
3147
  """
3085
3148
  Initializes extensions by loading their __init__.py files and calling the __parser__ function if it exists.
3086
3149
  """
@@ -3235,14 +3298,7 @@ def run_extension_cli():
3235
3298
  return False
3236
3299
 
3237
3300
 
3238
- def main():
3239
- global _ROOT, g_verbose, g_default_model, g_logprefix, g_providers, g_config, g_config_path, g_app
3240
-
3241
- _ROOT = os.getenv("LLMS_ROOT", resolve_root())
3242
- if not _ROOT:
3243
- print("Resource root not found")
3244
- exit(1)
3245
-
3301
+ def create_arg_parser():
3246
3302
  parser = argparse.ArgumentParser(description=f"llms v{VERSION}")
3247
3303
  parser.add_argument("--config", default=None, help="Path to config file", metavar="FILE")
3248
3304
  parser.add_argument("--providers", default=None, help="Path to models.dev providers file", metavar="FILE")
@@ -3311,11 +3367,13 @@ def main():
3311
3367
  help="Update an extension (use 'all' to update all extensions)",
3312
3368
  metavar="EXTENSION",
3313
3369
  )
3370
+ return parser
3314
3371
 
3315
- # Load parser extensions, go through all extensions and load their parser arguments
3316
- init_extensions(parser)
3317
3372
 
3318
- cli_args, extra_args = parser.parse_known_args()
3373
+ def cli_exec(cli_args, extra_args):
3374
+ global _ROOT, g_verbose, g_default_model, g_logprefix, g_providers, g_config, g_config_path, g_app
3375
+
3376
+ verify_root_path()
3319
3377
 
3320
3378
  g_app = AppExtensions(cli_args, extra_args)
3321
3379
 
@@ -3351,12 +3409,12 @@ def main():
3351
3409
  else:
3352
3410
  asyncio.run(save_text_url(github_url("providers-extra.json"), home_providers_extra_path))
3353
3411
  print(f"Created default extra providers config at {home_providers_extra_path}")
3354
- exit(0)
3412
+ return ExitCode.SUCCESS
3355
3413
 
3356
3414
  if cli_args.providers:
3357
3415
  if not os.path.exists(cli_args.providers):
3358
3416
  print(f"providers.json not found at {cli_args.providers}")
3359
- exit(1)
3417
+ return ExitCode.FAILED
3360
3418
  g_providers = json.loads(text_from_file(cli_args.providers))
3361
3419
 
3362
3420
  if cli_args.config:
@@ -3385,7 +3443,7 @@ def main():
3385
3443
  if cli_args.update_providers:
3386
3444
  asyncio.run(update_providers(home_providers_path))
3387
3445
  print(f"Updated {home_providers_path}")
3388
- exit(0)
3446
+ return ExitCode.SUCCESS
3389
3447
 
3390
3448
  # if home_providers_path is older than 1 day, update providers list
3391
3449
  if (
@@ -3418,7 +3476,7 @@ def main():
3418
3476
  print(" llms --add <github-user>/<repo>")
3419
3477
 
3420
3478
  asyncio.run(list_extensions())
3421
- exit(0)
3479
+ return ExitCode.SUCCESS
3422
3480
 
3423
3481
  async def install_extension(name):
3424
3482
  # Determine git URL and target directory name
@@ -3481,7 +3539,7 @@ def main():
3481
3539
  os.rmdir(target_path)
3482
3540
 
3483
3541
  asyncio.run(install_extension(cli_args.add))
3484
- exit(0)
3542
+ return ExitCode.SUCCESS
3485
3543
 
3486
3544
  if cli_args.remove is not None:
3487
3545
  if cli_args.remove == "ls":
@@ -3490,11 +3548,11 @@ def main():
3490
3548
  extensions = os.listdir(extensions_path)
3491
3549
  if len(extensions) == 0:
3492
3550
  print("No extensions installed.")
3493
- exit(0)
3551
+ return ExitCode.SUCCESS
3494
3552
  print("Installed extensions:")
3495
3553
  for extension in extensions:
3496
3554
  print(f" {extension}")
3497
- exit(0)
3555
+ return ExitCode.SUCCESS
3498
3556
  # Remove an extension
3499
3557
  extension_name = cli_args.remove
3500
3558
  extensions_path = get_extensions_path()
@@ -3502,7 +3560,7 @@ def main():
3502
3560
 
3503
3561
  if not os.path.exists(target_path):
3504
3562
  print(f"Extension {extension_name} not found at {target_path}")
3505
- exit(1)
3563
+ return ExitCode.FAILED
3506
3564
 
3507
3565
  print(f"Removing extension: {extension_name}...")
3508
3566
  try:
@@ -3510,9 +3568,9 @@ def main():
3510
3568
  print(f"Extension {extension_name} removed successfully.")
3511
3569
  except Exception as e:
3512
3570
  print(f"Failed to remove extension: {e}")
3513
- exit(1)
3571
+ return ExitCode.FAILED
3514
3572
 
3515
- exit(0)
3573
+ return ExitCode.SUCCESS
3516
3574
 
3517
3575
  if cli_args.update:
3518
3576
  if cli_args.update == "ls":
@@ -3521,7 +3579,7 @@ def main():
3521
3579
  extensions = os.listdir(extensions_path)
3522
3580
  if len(extensions) == 0:
3523
3581
  print("No extensions installed.")
3524
- exit(0)
3582
+ return ExitCode.SUCCESS
3525
3583
  print("Installed extensions:")
3526
3584
  for extension in extensions:
3527
3585
  print(f" {extension}")
@@ -3529,7 +3587,7 @@ def main():
3529
3587
  print("\nUsage:")
3530
3588
  print(" llms --update <extension>")
3531
3589
  print(" llms --update all")
3532
- exit(0)
3590
+ return ExitCode.SUCCESS
3533
3591
 
3534
3592
  async def update_extensions(extension_name):
3535
3593
  extensions_path = get_extensions_path()
@@ -3546,7 +3604,7 @@ def main():
3546
3604
  _log(result.stdout.decode("utf-8"))
3547
3605
 
3548
3606
  asyncio.run(update_extensions(cli_args.update))
3549
- exit(0)
3607
+ return ExitCode.SUCCESS
3550
3608
 
3551
3609
  g_app.extensions = install_extensions()
3552
3610
 
@@ -3604,7 +3662,7 @@ def main():
3604
3662
  print(f"\n{model_count} models available from {provider_count} providers")
3605
3663
 
3606
3664
  print_status()
3607
- g_app.exit(0)
3665
+ return ExitCode.SUCCESS
3608
3666
 
3609
3667
  if cli_args.check is not None:
3610
3668
  # Check validity of models for a provider
@@ -3613,7 +3671,7 @@ def main():
3613
3671
  provider_name = cli_args.check
3614
3672
  model_names = extra_args if len(extra_args) > 0 else None
3615
3673
  loop.run_until_complete(check_models(provider_name, model_names))
3616
- g_app.exit(0)
3674
+ return ExitCode.SUCCESS
3617
3675
 
3618
3676
  if cli_args.serve is not None:
3619
3677
  # Disable inactive providers and save to config before starting server
@@ -3658,7 +3716,7 @@ def main():
3658
3716
  print("ERROR: Authentication is enabled but GitHub OAuth is not properly configured.")
3659
3717
  print("Please set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET environment variables,")
3660
3718
  print("or disable authentication by setting 'auth.enabled' to false in llms.json")
3661
- exit(1)
3719
+ return ExitCode.FAILED
3662
3720
 
3663
3721
  _log("Authentication enabled - GitHub OAuth configured")
3664
3722
 
@@ -4252,7 +4310,7 @@ def main():
4252
4310
 
4253
4311
  print(f"Starting server on port {port}...")
4254
4312
  web.run_app(app, host="0.0.0.0", port=port, print=_log)
4255
- g_app.exit(0)
4313
+ return ExitCode.SUCCESS
4256
4314
 
4257
4315
  if cli_args.enable is not None:
4258
4316
  if cli_args.enable.endswith(","):
@@ -4271,7 +4329,7 @@ def main():
4271
4329
  if provider not in g_config["providers"]:
4272
4330
  print(f"Provider '{provider}' not found")
4273
4331
  print(f"Available providers: {', '.join(g_config['providers'].keys())}")
4274
- exit(1)
4332
+ return ExitCode.FAILED
4275
4333
  if provider in g_config["providers"]:
4276
4334
  provider_config, msg = enable_provider(provider)
4277
4335
  print(f"\nEnabled provider {provider}:")
@@ -4282,7 +4340,7 @@ def main():
4282
4340
  print_status()
4283
4341
  if len(msgs) > 0:
4284
4342
  print("\n" + "\n".join(msgs))
4285
- g_app.exit(0)
4343
+ return ExitCode.SUCCESS
4286
4344
 
4287
4345
  if cli_args.disable is not None:
4288
4346
  if cli_args.disable.endswith(","):
@@ -4300,24 +4358,24 @@ def main():
4300
4358
  if provider not in g_config["providers"]:
4301
4359
  print(f"Provider {provider} not found")
4302
4360
  print(f"Available providers: {', '.join(g_config['providers'].keys())}")
4303
- exit(1)
4361
+ return ExitCode.FAILED
4304
4362
  disable_provider(provider)
4305
4363
  print(f"\nDisabled provider {provider}")
4306
4364
 
4307
4365
  print_status()
4308
- g_app.exit(0)
4366
+ return ExitCode.SUCCESS
4309
4367
 
4310
4368
  if cli_args.default is not None:
4311
4369
  default_model = cli_args.default
4312
4370
  provider_model = get_provider_model(default_model)
4313
4371
  if provider_model is None:
4314
4372
  print(f"Model {default_model} not found")
4315
- exit(1)
4373
+ return ExitCode.FAILED
4316
4374
  default_text = g_config["defaults"]["text"]
4317
4375
  default_text["model"] = default_model
4318
4376
  save_config(g_config)
4319
4377
  print(f"\nDefault model set to: {default_model}")
4320
- g_app.exit(0)
4378
+ return ExitCode.SUCCESS
4321
4379
 
4322
4380
  if (
4323
4381
  cli_args.chat is not None
@@ -4339,13 +4397,13 @@ def main():
4339
4397
  template = f"out:{cli_args.out}"
4340
4398
  if template not in g_config["defaults"]:
4341
4399
  print(f"Template for output modality '{cli_args.out}' not found")
4342
- exit(1)
4400
+ return ExitCode.FAILED
4343
4401
  chat = g_config["defaults"][template]
4344
4402
  if cli_args.chat is not None:
4345
4403
  chat_path = os.path.join(os.path.dirname(__file__), cli_args.chat)
4346
4404
  if not os.path.exists(chat_path):
4347
4405
  print(f"Chat request template not found: {chat_path}")
4348
- exit(1)
4406
+ return ExitCode.FAILED
4349
4407
  _log(f"Using chat: {chat_path}")
4350
4408
 
4351
4409
  with open(chat_path) as f:
@@ -4386,19 +4444,48 @@ def main():
4386
4444
  raw=cli_args.raw,
4387
4445
  )
4388
4446
  )
4389
- g_app.exit(0)
4447
+ return ExitCode.SUCCESS
4390
4448
  except Exception as e:
4391
4449
  print(f"{cli_args.logprefix}Error: {e}")
4392
4450
  if cli_args.verbose:
4393
4451
  traceback.print_exc()
4394
- g_app.exit(1)
4452
+ return ExitCode.FAILED
4395
4453
 
4396
4454
  handled = run_extension_cli()
4455
+ return ExitCode.SUCCESS if handled else ExitCode.UNHANDLED
4456
+
4457
+
4458
+ def get_app():
4459
+ return g_app
4460
+
4461
+
4462
+ def cli(command_line: str):
4463
+ parser = create_arg_parser()
4397
4464
 
4398
- if not handled:
4465
+ # Load parser extensions, go through all extensions and load their parser arguments
4466
+ if load_extensions:
4467
+ init_extensions(parser)
4468
+
4469
+ args = shlex.split(command_line)
4470
+ cli_args, extra_args = parser.parse_known_args(args)
4471
+ return cli_exec(cli_args, extra_args)
4472
+
4473
+
4474
+ def main():
4475
+ parser = create_arg_parser()
4476
+
4477
+ # Load parser extensions, go through all extensions and load their parser arguments
4478
+ init_extensions(parser)
4479
+
4480
+ cli_args, extra_args = parser.parse_known_args()
4481
+ exit_code = cli_exec(cli_args, extra_args)
4482
+
4483
+ if exit_code == ExitCode.UNHANDLED:
4399
4484
  # show usage from ArgumentParser
4400
4485
  parser.print_help()
4401
- g_app.exit(0)
4486
+ g_app.exit(0) if g_app else exit(0)
4487
+
4488
+ g_app.exit(exit_code) if g_app else exit(exit_code)
4402
4489
 
4403
4490
 
4404
4491
  if __name__ == "__main__":
llms/ui/ai.mjs CHANGED
@@ -6,7 +6,7 @@ const headers = { 'Accept': 'application/json' }
6
6
  const prefsKey = 'llms.prefs'
7
7
 
8
8
  export const o = {
9
- version: '3.0.10',
9
+ version: '3.0.12',
10
10
  base,
11
11
  prefsKey,
12
12
  welcome: 'Welcome to llms.py',
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llms-py
3
- Version: 3.0.10
3
+ Version: 3.0.12
4
4
  Summary: A lightweight CLI tool and OpenAI-compatible server for querying multiple Large Language Model (LLM) providers
5
5
  Home-page: https://github.com/ServiceStack/llms
6
6
  Author: ServiceStack
@@ -44,6 +44,6 @@ Lightweight CLI, API and ChatGPT-like alternative to Open WebUI for accessing mu
44
44
 
45
45
  [llmspy.org](https://llmspy.org)
46
46
 
47
- [![](https://github.com/ServiceStack/llmspy.org/blob/main/public/img/llmspy-home.webp?raw=true)](https://llmspy.org)
47
+ [![](https://github.com/ServiceStack/llmspy.org/blob/main/public/img/llmspy-home-v3.webp?raw=true)](https://llmspy.org)
48
48
 
49
49
  GitHub: [llmspy.org](https://github.com/ServiceStack/llmspy.org)
@@ -3,13 +3,13 @@ llms/__main__.py,sha256=hrBulHIt3lmPm1BCyAEVtB6DQ0Hvc3gnIddhHCmJasg,151
3
3
  llms/db.py,sha256=oozp5I5lECVO8oZEFwcZl3ES5mARqWeR1BkoqG5kSqM,11687
4
4
  llms/index.html,sha256=nGk1Djtn9p7l6LuKp4Kg0JIB9fCzxtTWXFfmDb4ggpc,1658
5
5
  llms/llms.json,sha256=NEr9kJRkUGZ2YZHbWC-haGPlVVL2Qtnx4kKZENGH1wk,11494
6
- llms/main.py,sha256=ZSRfpbTGMJjU_H65Iz4I6aDBmEQjFnfUwSGRr4SJfQg,169748
6
+ llms/main.py,sha256=HgQ_nS0OSqu8BRq_k4a1vpPhGzNiIgvIg-fxs7O6G44,174163
7
7
  llms/providers-extra.json,sha256=_6DmGBiQY9LM6_Y0zOiObYn7ba4g3akSNQfmHcYlENc,11101
8
8
  llms/providers.json,sha256=yjhDurlwo70xqfV0HNLiZaCpw3WvtIgkjoLahQIKX2w,282530
9
9
  llms/extensions/analytics/ui/index.mjs,sha256=m1XwaqYCLwK267JAUCAltkN_nOXep0GxfpvGNS5i4_w,69547
10
10
  llms/extensions/app/README.md,sha256=TKoblZpHlheLCh_dfXOxqTc5OvxlgMBa-vKo8Hqb2gg,1370
11
11
  llms/extensions/app/__init__.py,sha256=aU8Bfliw--Xj1bsKL3PSoX6MY1ZNgweNyMWS1V_YG4s,20855
12
- llms/extensions/app/db.py,sha256=CqpHReXXjrLXXNxINo-wsnBJenKZVVHKlWlhtXFWj08,21503
12
+ llms/extensions/app/db.py,sha256=DU8YZ25yFsBI-O6msxh2GgzbwaqKqXkAHJLwQKcmFPI,21533
13
13
  llms/extensions/app/ui/Recents.mjs,sha256=2ypAKUp9_Oqcive1nUWZ8I2PQTBomBg_Pkjygi4oPgs,9261
14
14
  llms/extensions/app/ui/index.mjs,sha256=sB9176LLNuKFsZ28yL-tROA6J4xePNtvxtSrzFcinRo,13271
15
15
  llms/extensions/app/ui/threadStore.mjs,sha256=CLlD-1HBO1yhZBHLfL0ZdA4quT4R07qr1JW4a8igVNc,12287
@@ -128,10 +128,10 @@ llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf,sha256=8B8-h9nGphwMC
128
128
  llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff,sha256=4U_tArGrp86fWv1YRLXQMhsiNR_rxyDg3ouHI1J2Cfc,16028
129
129
  llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2,sha256=cdUX1ngneHz6vfGGkUzDNY7aU543kxlB8rL9SiH2jAs,13568
130
130
  llms/extensions/providers/__init__.py,sha256=C5zOBQEOB2L96rAZdjV42fPVk_dZxSh2Dv30Kb1w3lE,534
131
- llms/extensions/providers/anthropic.py,sha256=LAOdpa_cP1zBUvSQ-ct3kVjRga-nPJ1U_KBb99N4IEk,10429
131
+ llms/extensions/providers/anthropic.py,sha256=V9mechnhyoX-5Z5AkwyQ-UzLax6cqG7j7GLvGTZF9no,10941
132
132
  llms/extensions/providers/cerebras.py,sha256=HaeFW0GwbD6V6Zrrwqyv78kQb0VXg9oHmykvJfIOOYE,1417
133
133
  llms/extensions/providers/chutes.py,sha256=5ZrfbqoOhgzKLQy_qULcp4jlvW5WXPR0jP9kN2Jzb9g,6229
134
- llms/extensions/providers/google.py,sha256=9P90bEefRA18tpjfEuAz1T5YpwzdOngrTFhw-LI3eXg,24434
134
+ llms/extensions/providers/google.py,sha256=oCCTE2KAw-WWE2v14XpKzgAMdFIWbjTBoa6GWuqT4dw,26215
135
135
  llms/extensions/providers/nvidia.py,sha256=C6cwqn3EufYDfRIgbc8MDkQNyD6w3c7hbjfYaHJSDik,4279
136
136
  llms/extensions/providers/openai.py,sha256=hkE-LVsw6M92_qEbpayuPo17Z1OWKHe7lm2wduLMng8,6138
137
137
  llms/extensions/providers/openrouter.py,sha256=5SfCJKo1aGKoDGez6HXYQe9elMMo9sSEDFqqdxamAgA,3330
@@ -143,7 +143,7 @@ llms/extensions/system_prompts/ui/prompts.json,sha256=t5DD3bird-87wFa4OlW-bC2wdo
143
143
  llms/extensions/tools/__init__.py,sha256=u76604Cn_sRFQRqeA_pkVEty27V688Mt9Z7Kh63yDr8,4825
144
144
  llms/extensions/tools/ui/index.mjs,sha256=IbGB2FQJ5VL4a8arwoR9C79vUCNrz8VIyQnHZ4vxU9o,34486
145
145
  llms/ui/App.mjs,sha256=CoUzO9mV__-jV19NKHYIbwHsjWMnO11jyNSbnJhe1gQ,7486
146
- llms/ui/ai.mjs,sha256=F4MaotQJDO60uHJBC6tA4dlQE1aPOR28tvLU670sLIs,6541
146
+ llms/ui/ai.mjs,sha256=S-LmypGGmTrAteH9By37md6gRJw2ILbjXf36Glho5EY,6541
147
147
  llms/ui/app.css,sha256=vfXErYVdVlE3pL8oZ-2G_OC-_reJzmaL0p91EVv48uo,186490
148
148
  llms/ui/ctx.mjs,sha256=X4scgXEQ9bMUfQl36sM4A3o2Ufad3LRwItxfmSu1xwc,12838
149
149
  llms/ui/fav.svg,sha256=_R6MFeXl6wBFT0lqcUxYQIDWgm246YH_3hSTW0oO8qw,734
@@ -169,9 +169,9 @@ llms/ui/modules/model-selector.mjs,sha256=6U4rAZ7vmQELFRQGWk4YEtq02v3lyHdMq6yUOp
169
169
  llms/ui/modules/chat/ChatBody.mjs,sha256=5yWjo6tWmcKidDpRvKFeHqx3lXO3DB-3rTyXY72gB4U,49122
170
170
  llms/ui/modules/chat/SettingsDialog.mjs,sha256=HMBJTwrapKrRIAstIIqp0QlJL5O-ho4hzgvfagPfsX8,19930
171
171
  llms/ui/modules/chat/index.mjs,sha256=lfSbERMaM3bLsKhdJJPWwL4-FGr8U_ftlvqW5vC3T1s,39762
172
- llms_py-3.0.10.dist-info/licenses/LICENSE,sha256=bus9cuAOWeYqBk2OuhSABVV1P4z7hgrEFISpyda_H5w,1532
173
- llms_py-3.0.10.dist-info/METADATA,sha256=IWZDosCH-2jQpFVfSZ4YEUHqOkph9QdILhpQspYVfCc,2192
174
- llms_py-3.0.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
175
- llms_py-3.0.10.dist-info/entry_points.txt,sha256=WswyE7PfnkZMIxboC-MS6flBD6wm-CYU7JSUnMhqMfM,40
176
- llms_py-3.0.10.dist-info/top_level.txt,sha256=gC7hk9BKSeog8gyg-EM_g2gxm1mKHwFRfK-10BxOsa4,5
177
- llms_py-3.0.10.dist-info/RECORD,,
172
+ llms_py-3.0.12.dist-info/licenses/LICENSE,sha256=bus9cuAOWeYqBk2OuhSABVV1P4z7hgrEFISpyda_H5w,1532
173
+ llms_py-3.0.12.dist-info/METADATA,sha256=KVDU66mm6L_mF4uwzhWhSbPHqqydeOKSuuKHf0IRJ_w,2195
174
+ llms_py-3.0.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
175
+ llms_py-3.0.12.dist-info/entry_points.txt,sha256=WswyE7PfnkZMIxboC-MS6flBD6wm-CYU7JSUnMhqMfM,40
176
+ llms_py-3.0.12.dist-info/top_level.txt,sha256=gC7hk9BKSeog8gyg-EM_g2gxm1mKHwFRfK-10BxOsa4,5
177
+ llms_py-3.0.12.dist-info/RECORD,,