llms-py 3.0.9__py3-none-any.whl → 3.0.11__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":
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.9"
46
+ VERSION = "3.0.11"
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)
@@ -1853,12 +1861,18 @@ def config_str(key):
1853
1861
  return key in g_config and g_config[key] or None
1854
1862
 
1855
1863
 
1856
- def load_config(config, providers, verbose=None):
1864
+ def load_config(config, providers, verbose=None, debug=None, disable_extensions: List[str] = None):
1857
1865
  global g_config, g_providers, g_verbose
1858
1866
  g_config = config
1859
1867
  g_providers = providers
1860
- if verbose:
1868
+ if verbose is not None:
1861
1869
  g_verbose = verbose
1870
+ if debug is not None:
1871
+ global DEBUG
1872
+ DEBUG = debug
1873
+ if disable_extensions:
1874
+ global DISABLE_EXTENSIONS
1875
+ DISABLE_EXTENSIONS = disable_extensions
1862
1876
 
1863
1877
 
1864
1878
  def init_llms(config, providers):
@@ -2539,7 +2553,7 @@ class AppExtensions:
2539
2553
  APIs extensions can use to extend the app
2540
2554
  """
2541
2555
 
2542
- def __init__(self, cli_args, extra_args):
2556
+ def __init__(self, cli_args: argparse.Namespace, extra_args: Dict[str, Any]):
2543
2557
  self.cli_args = cli_args
2544
2558
  self.extra_args = extra_args
2545
2559
  self.config = None
@@ -2615,12 +2629,12 @@ class AppExtensions:
2615
2629
  "ctx.mjs": "/ui/ctx.mjs",
2616
2630
  }
2617
2631
 
2618
- def set_config(self, config):
2632
+ def set_config(self, config: Dict[str, Any]):
2619
2633
  self.config = config
2620
2634
  self.auth_enabled = self.config.get("auth", {}).get("enabled", False)
2621
2635
 
2622
2636
  # Authentication middleware helper
2623
- def check_auth(self, request):
2637
+ def check_auth(self, request: web.Request) -> Tuple[bool, Optional[Dict[str, Any]]]:
2624
2638
  """Check if request is authenticated. Returns (is_authenticated, user_data)"""
2625
2639
  if not self.auth_enabled:
2626
2640
  return True, None
@@ -2639,7 +2653,7 @@ class AppExtensions:
2639
2653
 
2640
2654
  return False, None
2641
2655
 
2642
- def get_session(self, request):
2656
+ def get_session(self, request: web.Request) -> Optional[Dict[str, Any]]:
2643
2657
  session_token = get_session_token(request)
2644
2658
 
2645
2659
  if not session_token or session_token not in g_sessions:
@@ -2648,30 +2662,39 @@ class AppExtensions:
2648
2662
  session_data = g_sessions[session_token]
2649
2663
  return session_data
2650
2664
 
2651
- def get_username(self, request):
2665
+ def get_username(self, request: web.Request) -> Optional[str]:
2652
2666
  session = self.get_session(request)
2653
2667
  if session:
2654
2668
  return session.get("userName")
2655
2669
  return None
2656
2670
 
2657
- def get_user_path(self, username=None):
2671
+ def get_user_path(self, username: Optional[str] = None) -> str:
2658
2672
  if username:
2659
2673
  return home_llms_path(os.path.join("user", username))
2660
2674
  return home_llms_path(os.path.join("user", "default"))
2661
2675
 
2662
- def chat_request(self, template=None, text=None, model=None, system_prompt=None):
2676
+ def get_providers(self) -> Dict[str, Any]:
2677
+ return g_handlers
2678
+
2679
+ def chat_request(
2680
+ self,
2681
+ template: Optional[str] = None,
2682
+ text: Optional[str] = None,
2683
+ model: Optional[str] = None,
2684
+ system_prompt: Optional[str] = None,
2685
+ ) -> Dict[str, Any]:
2663
2686
  return g_chat_request(template=template, text=text, model=model, system_prompt=system_prompt)
2664
2687
 
2665
- async def chat_completion(self, chat, context=None):
2688
+ async def chat_completion(self, chat: Dict[str, Any], context: Optional[Dict[str, Any]] = None) -> Any:
2666
2689
  response = await g_chat_completion(chat, context)
2667
2690
  return response
2668
2691
 
2669
- def on_cache_saved_filters(self, context):
2692
+ def on_cache_saved_filters(self, context: Dict[str, Any]):
2670
2693
  # _log(f"on_cache_saved_filters {len(self.cache_saved_filters)}: {context['url']}")
2671
2694
  for filter_func in self.cache_saved_filters:
2672
2695
  filter_func(context)
2673
2696
 
2674
- async def on_chat_error(self, e, context):
2697
+ async def on_chat_error(self, e: Exception, context: Dict[str, Any]):
2675
2698
  # Apply chat error filters
2676
2699
  if "stackTrace" not in context:
2677
2700
  context["stackTrace"] = traceback.format_exc()
@@ -2681,25 +2704,27 @@ class AppExtensions:
2681
2704
  except Exception as e:
2682
2705
  _err("chat error filter failed", e)
2683
2706
 
2684
- async def on_chat_tool(self, chat, context):
2707
+ async def on_chat_tool(self, chat: Dict[str, Any], context: Dict[str, Any]):
2685
2708
  m_len = len(chat.get("messages", []))
2686
2709
  t_len = len(self.chat_tool_filters)
2687
2710
  _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)}:"
2711
+ f"on_tool_call for thread {context.get('threadId')} with {m_len} {pluralize('message', m_len)}, invoking {t_len} {pluralize('filter', t_len)}:"
2689
2712
  )
2690
2713
  for filter_func in self.chat_tool_filters:
2691
2714
  await filter_func(chat, context)
2692
2715
 
2693
- def exit(self, exit_code=0):
2716
+ def shutdown(self):
2694
2717
  if len(self.shutdown_handlers) > 0:
2695
2718
  _dbg(f"running {len(self.shutdown_handlers)} shutdown handlers...")
2696
2719
  for handler in self.shutdown_handlers:
2697
2720
  handler()
2698
2721
 
2722
+ def exit(self, exit_code: int = 0):
2723
+ self.shutdown()
2699
2724
  _dbg(f"exit({exit_code})")
2700
2725
  sys.exit(exit_code)
2701
2726
 
2702
- def create_chat_with_tools(self, chat, use_tools="all"):
2727
+ def create_chat_with_tools(self, chat: Dict[str, Any], use_tools: str = "all") -> Dict[str, Any]:
2703
2728
  # Inject global tools if present
2704
2729
  current_chat = chat.copy()
2705
2730
  tools = current_chat.get("tools")
@@ -2728,7 +2753,7 @@ def handler_name(handler):
2728
2753
 
2729
2754
 
2730
2755
  class ExtensionContext:
2731
- def __init__(self, app, path):
2756
+ def __init__(self, app: AppExtensions, path: str):
2732
2757
  self.app = app
2733
2758
  self.cli_args = app.cli_args
2734
2759
  self.extra_args = app.extra_args
@@ -2746,101 +2771,107 @@ class ExtensionContext:
2746
2771
  self.request_args = app.request_args
2747
2772
  self.disabled = False
2748
2773
 
2749
- def chat_to_prompt(self, chat):
2774
+ def chat_to_prompt(self, chat: Dict[str, Any]) -> str:
2750
2775
  return chat_to_prompt(chat)
2751
2776
 
2752
- def chat_to_system_prompt(self, chat):
2777
+ def chat_to_system_prompt(self, chat: Dict[str, Any]) -> str:
2753
2778
  return chat_to_system_prompt(chat)
2754
2779
 
2755
- def chat_response_to_message(self, response):
2780
+ def chat_response_to_message(self, response: Dict[str, Any]) -> Dict[str, Any]:
2756
2781
  return chat_response_to_message(response)
2757
2782
 
2758
- def last_user_prompt(self, chat):
2783
+ def last_user_prompt(self, chat: Dict[str, Any]) -> str:
2759
2784
  return last_user_prompt(chat)
2760
2785
 
2761
- def to_file_info(self, chat, info=None, response=None):
2786
+ def to_file_info(
2787
+ self, chat: Dict[str, Any], info: Optional[Dict[str, Any]] = None, response: Optional[Dict[str, Any]] = None
2788
+ ) -> Dict[str, Any]:
2762
2789
  return to_file_info(chat, info=info, response=response)
2763
2790
 
2764
- def save_image_to_cache(self, base64_data, filename, image_info, ignore_info=False):
2791
+ def save_image_to_cache(
2792
+ self, base64_data: Union[str, bytes], filename: str, image_info: Dict[str, Any], ignore_info: bool = False
2793
+ ) -> Tuple[str, Optional[Dict[str, Any]]]:
2765
2794
  return save_image_to_cache(base64_data, filename, image_info, ignore_info=ignore_info)
2766
2795
 
2767
- def save_bytes_to_cache(self, bytes_data, filename, file_info):
2796
+ def save_bytes_to_cache(
2797
+ self, bytes_data: Union[str, bytes], filename: str, file_info: Optional[Dict[str, Any]]
2798
+ ) -> Tuple[str, Optional[Dict[str, Any]]]:
2768
2799
  return save_bytes_to_cache(bytes_data, filename, file_info)
2769
2800
 
2770
- def text_from_file(self, path):
2801
+ def text_from_file(self, path: str) -> str:
2771
2802
  return text_from_file(path)
2772
2803
 
2773
- def json_from_file(self, path):
2804
+ def json_from_file(self, path: str) -> Any:
2774
2805
  return json_from_file(path)
2775
2806
 
2776
- def download_file(self, url):
2807
+ def download_file(self, url: str) -> Tuple[bytes, Dict[str, Any]]:
2777
2808
  return download_file(url)
2778
2809
 
2779
- def session_download_file(self, session, url):
2810
+ def session_download_file(self, session: aiohttp.ClientSession, url: str) -> Tuple[bytes, Dict[str, Any]]:
2780
2811
  return session_download_file(session, url)
2781
2812
 
2782
- def read_binary_file(self, url):
2813
+ def read_binary_file(self, url: str) -> Tuple[bytes, Dict[str, Any]]:
2783
2814
  return read_binary_file(url)
2784
2815
 
2785
- def log(self, message):
2816
+ def log(self, message: Any):
2786
2817
  if self.verbose:
2787
2818
  print(f"[{self.name}] {message}", flush=True)
2788
2819
  return message
2789
2820
 
2790
- def log_json(self, obj):
2821
+ def log_json(self, obj: Any):
2791
2822
  if self.verbose:
2792
2823
  print(f"[{self.name}] {json.dumps(obj, indent=2)}", flush=True)
2793
2824
  return obj
2794
2825
 
2795
- def dbg(self, message):
2826
+ def dbg(self, message: Any):
2796
2827
  if self.debug:
2797
2828
  print(f"DEBUG [{self.name}]: {message}", flush=True)
2798
2829
 
2799
- def err(self, message, e):
2830
+ def err(self, message: str, e: Exception):
2800
2831
  print(f"ERROR [{self.name}]: {message}", e)
2801
2832
  if self.verbose:
2802
2833
  print(traceback.format_exc(), flush=True)
2803
2834
 
2804
- def error_message(self, e):
2835
+ def error_message(self, e: Exception) -> str:
2805
2836
  return to_error_message(e)
2806
2837
 
2807
- def error_response(self, e, stacktrace=False):
2838
+ def error_response(self, e: Exception, stacktrace: bool = False) -> Dict[str, Any]:
2808
2839
  return to_error_response(e, stacktrace=stacktrace)
2809
2840
 
2810
- def add_provider(self, provider):
2841
+ def add_provider(self, provider: Any):
2811
2842
  self.log(f"Registered provider: {provider.__name__}")
2812
2843
  self.app.all_providers.append(provider)
2813
2844
 
2814
- def register_ui_extension(self, index):
2845
+ def register_ui_extension(self, index: str):
2815
2846
  path = os.path.join(self.ext_prefix, index)
2816
2847
  self.log(f"Registered UI extension: {path}")
2817
2848
  self.app.ui_extensions.append({"id": self.name, "path": path})
2818
2849
 
2819
- def register_chat_request_filter(self, handler):
2850
+ def register_chat_request_filter(self, handler: Callable):
2820
2851
  self.log(f"Registered chat request filter: {handler_name(handler)}")
2821
2852
  self.app.chat_request_filters.append(handler)
2822
2853
 
2823
- def register_chat_tool_filter(self, handler):
2854
+ def register_chat_tool_filter(self, handler: Callable):
2824
2855
  self.log(f"Registered chat tool filter: {handler_name(handler)}")
2825
2856
  self.app.chat_tool_filters.append(handler)
2826
2857
 
2827
- def register_chat_response_filter(self, handler):
2858
+ def register_chat_response_filter(self, handler: Callable):
2828
2859
  self.log(f"Registered chat response filter: {handler_name(handler)}")
2829
2860
  self.app.chat_response_filters.append(handler)
2830
2861
 
2831
- def register_chat_error_filter(self, handler):
2862
+ def register_chat_error_filter(self, handler: Callable):
2832
2863
  self.log(f"Registered chat error filter: {handler_name(handler)}")
2833
2864
  self.app.chat_error_filters.append(handler)
2834
2865
 
2835
- def register_cache_saved_filter(self, handler):
2866
+ def register_cache_saved_filter(self, handler: Callable):
2836
2867
  self.log(f"Registered cache saved filter: {handler_name(handler)}")
2837
2868
  self.app.cache_saved_filters.append(handler)
2838
2869
 
2839
- def register_shutdown_handler(self, handler):
2870
+ def register_shutdown_handler(self, handler: Callable):
2840
2871
  self.log(f"Registered shutdown handler: {handler_name(handler)}")
2841
2872
  self.app.shutdown_handlers.append(handler)
2842
2873
 
2843
- def add_static_files(self, ext_dir):
2874
+ def add_static_files(self, ext_dir: str):
2844
2875
  self.log(f"Registered static files: {ext_dir}")
2845
2876
 
2846
2877
  async def serve_static(request):
@@ -2852,57 +2883,63 @@ class ExtensionContext:
2852
2883
 
2853
2884
  self.app.server_add_get.append((os.path.join(self.ext_prefix, "{path:.*}"), serve_static, {}))
2854
2885
 
2855
- def web_path(self, method, path):
2886
+ def web_path(self, method: str, path: str) -> str:
2856
2887
  full_path = os.path.join(self.ext_prefix, path) if path else self.ext_prefix
2857
2888
  self.dbg(f"Registered {method:<6} {full_path}")
2858
2889
  return full_path
2859
2890
 
2860
- def add_get(self, path, handler, **kwargs):
2891
+ def add_get(self, path: str, handler: Callable, **kwargs: Any):
2861
2892
  self.app.server_add_get.append((self.web_path("GET", path), handler, kwargs))
2862
2893
 
2863
- def add_post(self, path, handler, **kwargs):
2894
+ def add_post(self, path: str, handler: Callable, **kwargs: Any):
2864
2895
  self.app.server_add_post.append((self.web_path("POST", path), handler, kwargs))
2865
2896
 
2866
- def add_put(self, path, handler, **kwargs):
2897
+ def add_put(self, path: str, handler: Callable, **kwargs: Any):
2867
2898
  self.app.server_add_put.append((self.web_path("PUT", path), handler, kwargs))
2868
2899
 
2869
- def add_delete(self, path, handler, **kwargs):
2900
+ def add_delete(self, path: str, handler: Callable, **kwargs: Any):
2870
2901
  self.app.server_add_delete.append((self.web_path("DELETE", path), handler, kwargs))
2871
2902
 
2872
- def add_patch(self, path, handler, **kwargs):
2903
+ def add_patch(self, path: str, handler: Callable, **kwargs: Any):
2873
2904
  self.app.server_add_patch.append((self.web_path("PATCH", path), handler, kwargs))
2874
2905
 
2875
- def add_importmaps(self, dict):
2906
+ def add_importmaps(self, dict: Dict[str, str]):
2876
2907
  self.app.import_maps.update(dict)
2877
2908
 
2878
- def add_index_header(self, html):
2909
+ def add_index_header(self, html: str):
2879
2910
  self.app.index_headers.append(html)
2880
2911
 
2881
- def add_index_footer(self, html):
2912
+ def add_index_footer(self, html: str):
2882
2913
  self.app.index_footers.append(html)
2883
2914
 
2884
- def get_config(self):
2915
+ def get_config(self) -> Optional[Dict[str, Any]]:
2885
2916
  return g_config
2886
2917
 
2887
- def get_cache_path(self, path=""):
2918
+ def get_cache_path(self, path: str = "") -> str:
2888
2919
  return get_cache_path(path)
2889
2920
 
2890
- def get_file_mime_type(self, filename):
2921
+ def get_file_mime_type(self, filename: str) -> str:
2891
2922
  return get_file_mime_type(filename)
2892
2923
 
2893
- def chat_request(self, template=None, text=None, model=None, system_prompt=None):
2924
+ def chat_request(
2925
+ self,
2926
+ template: Optional[str] = None,
2927
+ text: Optional[str] = None,
2928
+ model: Optional[str] = None,
2929
+ system_prompt: Optional[str] = None,
2930
+ ) -> Dict[str, Any]:
2894
2931
  return self.app.chat_request(template=template, text=text, model=model, system_prompt=system_prompt)
2895
2932
 
2896
- def chat_completion(self, chat, context=None):
2897
- return self.app.chat_completion(chat, context=context)
2933
+ async def chat_completion(self, chat: Dict[str, Any], context: Optional[Dict[str, Any]] = None) -> Any:
2934
+ return await self.app.chat_completion(chat, context=context)
2898
2935
 
2899
- def get_providers(self):
2936
+ def get_providers(self) -> Dict[str, Any]:
2900
2937
  return g_handlers
2901
2938
 
2902
- def get_provider(self, name):
2939
+ def get_provider(self, name: str) -> Optional[Any]:
2903
2940
  return g_handlers.get(name)
2904
2941
 
2905
- def sanitize_tool_def(self, tool_def):
2942
+ def sanitize_tool_def(self, tool_def: Dict[str, Any]) -> Dict[str, Any]:
2906
2943
  """
2907
2944
  Merge $defs parameter into tool_def property to reduce client/server complexity
2908
2945
  """
@@ -2942,7 +2979,7 @@ class ExtensionContext:
2942
2979
  parameters = func_def.get("parameters", {})
2943
2980
  defs = parameters.get("$defs", {})
2944
2981
  properties = parameters.get("properties", {})
2945
- for prop_name, prop_def in properties.items():
2982
+ for _, prop_def in properties.items():
2946
2983
  if "$ref" in prop_def:
2947
2984
  ref = prop_def["$ref"]
2948
2985
  if ref.startswith("#/$defs/"):
@@ -2954,7 +2991,7 @@ class ExtensionContext:
2954
2991
  del parameters["$defs"]
2955
2992
  return tool_def
2956
2993
 
2957
- def register_tool(self, func, tool_def=None, group=None):
2994
+ def register_tool(self, func: Callable, tool_def: Optional[Dict[str, Any]] = None, group: Optional[str] = None):
2958
2995
  if tool_def is None:
2959
2996
  tool_def = function_to_tool_definition(func)
2960
2997
 
@@ -2976,54 +3013,61 @@ class ExtensionContext:
2976
3013
  self.app.tool_groups[group] = []
2977
3014
  self.app.tool_groups[group].append(name)
2978
3015
 
2979
- def get_tool_definition(self, name):
3016
+ def get_tool_definition(self, name: str) -> Optional[Dict[str, Any]]:
2980
3017
  for tool_def in self.app.tool_definitions:
2981
3018
  if tool_def["function"]["name"] == name:
2982
3019
  return tool_def
2983
3020
  return None
2984
3021
 
2985
- def group_resources(self, resources: list):
3022
+ def group_resources(self, resources: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
2986
3023
  return group_resources(resources)
2987
3024
 
2988
- def check_auth(self, request):
3025
+ def check_auth(self, request: web.Request) -> Tuple[bool, Optional[Dict[str, Any]]]:
2989
3026
  return self.app.check_auth(request)
2990
3027
 
2991
- def get_session(self, request):
3028
+ def get_session(self, request: web.Request) -> Optional[Dict[str, Any]]:
2992
3029
  return self.app.get_session(request)
2993
3030
 
2994
- def get_username(self, request):
3031
+ def get_username(self, request: web.Request) -> Optional[str]:
2995
3032
  return self.app.get_username(request)
2996
3033
 
2997
- def get_user_path(self, username=None):
3034
+ def get_user_path(self, username: Optional[str] = None) -> str:
2998
3035
  return self.app.get_user_path(username)
2999
3036
 
3000
- def context_to_username(self, context):
3037
+ def context_to_username(self, context: Optional[Dict[str, Any]]) -> Optional[str]:
3001
3038
  if context and "request" in context:
3002
3039
  return self.get_username(context["request"])
3003
3040
  return None
3004
3041
 
3005
- def should_cancel_thread(self, context):
3042
+ def should_cancel_thread(self, context: Dict[str, Any]) -> bool:
3006
3043
  return should_cancel_thread(context)
3007
3044
 
3008
- def cache_message_inline_data(self, message):
3045
+ def cache_message_inline_data(self, message: Dict[str, Any]):
3009
3046
  return cache_message_inline_data(message)
3010
3047
 
3011
- async def exec_tool(self, name, args):
3048
+ async def exec_tool(self, name: str, args: Dict[str, Any]) -> Tuple[Optional[str], List[Dict[str, Any]]]:
3012
3049
  return await g_exec_tool(name, args)
3013
3050
 
3014
- def tool_result(self, result, function_name: Optional[str] = None, function_args: Optional[dict] = None):
3051
+ def tool_result(
3052
+ self, result: Any, function_name: Optional[str] = None, function_args: Optional[Dict[str, Any]] = None
3053
+ ) -> Dict[str, Any]:
3015
3054
  return g_tool_result(result, function_name, function_args)
3016
3055
 
3017
- def tool_result_part(self, result: dict, function_name: Optional[str] = None, function_args: Optional[dict] = None):
3056
+ def tool_result_part(
3057
+ self,
3058
+ result: Dict[str, Any],
3059
+ function_name: Optional[str] = None,
3060
+ function_args: Optional[Dict[str, Any]] = None,
3061
+ ) -> Dict[str, Any]:
3018
3062
  return tool_result_part(result, function_name, function_args)
3019
3063
 
3020
- def to_content(self, result):
3064
+ def to_content(self, result: Any) -> str:
3021
3065
  return to_content(result)
3022
3066
 
3023
- def create_chat_with_tools(self, chat, use_tools="all"):
3067
+ def create_chat_with_tools(self, chat: Dict[str, Any], use_tools: str = "all") -> Dict[str, Any]:
3024
3068
  return self.app.create_chat_with_tools(chat, use_tools)
3025
3069
 
3026
- def chat_to_aspect_ratio(self, chat):
3070
+ def chat_to_aspect_ratio(self, chat: Dict[str, Any]) -> str:
3027
3071
  return chat_to_aspect_ratio(chat)
3028
3072
 
3029
3073
 
@@ -3080,7 +3124,21 @@ def get_extensions_dirs():
3080
3124
  return ret
3081
3125
 
3082
3126
 
3127
+ def verify_root_path():
3128
+ global _ROOT
3129
+ _ROOT = os.getenv("LLMS_ROOT", resolve_root())
3130
+ if not _ROOT:
3131
+ print("Resource root not found")
3132
+ exit(1)
3133
+
3134
+
3083
3135
  def init_extensions(parser):
3136
+ """
3137
+ Programmatic entry point for the CLI.
3138
+ Example: cli("ls minimax")
3139
+ """
3140
+ verify_root_path()
3141
+
3084
3142
  """
3085
3143
  Initializes extensions by loading their __init__.py files and calling the __parser__ function if it exists.
3086
3144
  """
@@ -3235,14 +3293,7 @@ def run_extension_cli():
3235
3293
  return False
3236
3294
 
3237
3295
 
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
-
3296
+ def create_arg_parser():
3246
3297
  parser = argparse.ArgumentParser(description=f"llms v{VERSION}")
3247
3298
  parser.add_argument("--config", default=None, help="Path to config file", metavar="FILE")
3248
3299
  parser.add_argument("--providers", default=None, help="Path to models.dev providers file", metavar="FILE")
@@ -3311,11 +3362,13 @@ def main():
3311
3362
  help="Update an extension (use 'all' to update all extensions)",
3312
3363
  metavar="EXTENSION",
3313
3364
  )
3365
+ return parser
3314
3366
 
3315
- # Load parser extensions, go through all extensions and load their parser arguments
3316
- init_extensions(parser)
3317
3367
 
3318
- cli_args, extra_args = parser.parse_known_args()
3368
+ def cli_exec(cli_args, extra_args):
3369
+ global _ROOT, g_verbose, g_default_model, g_logprefix, g_providers, g_config, g_config_path, g_app
3370
+
3371
+ verify_root_path()
3319
3372
 
3320
3373
  g_app = AppExtensions(cli_args, extra_args)
3321
3374
 
@@ -3351,12 +3404,12 @@ def main():
3351
3404
  else:
3352
3405
  asyncio.run(save_text_url(github_url("providers-extra.json"), home_providers_extra_path))
3353
3406
  print(f"Created default extra providers config at {home_providers_extra_path}")
3354
- exit(0)
3407
+ return ExitCode.SUCCESS
3355
3408
 
3356
3409
  if cli_args.providers:
3357
3410
  if not os.path.exists(cli_args.providers):
3358
3411
  print(f"providers.json not found at {cli_args.providers}")
3359
- exit(1)
3412
+ return ExitCode.FAILED
3360
3413
  g_providers = json.loads(text_from_file(cli_args.providers))
3361
3414
 
3362
3415
  if cli_args.config:
@@ -3385,7 +3438,7 @@ def main():
3385
3438
  if cli_args.update_providers:
3386
3439
  asyncio.run(update_providers(home_providers_path))
3387
3440
  print(f"Updated {home_providers_path}")
3388
- exit(0)
3441
+ return ExitCode.SUCCESS
3389
3442
 
3390
3443
  # if home_providers_path is older than 1 day, update providers list
3391
3444
  if (
@@ -3418,7 +3471,7 @@ def main():
3418
3471
  print(" llms --add <github-user>/<repo>")
3419
3472
 
3420
3473
  asyncio.run(list_extensions())
3421
- exit(0)
3474
+ return ExitCode.SUCCESS
3422
3475
 
3423
3476
  async def install_extension(name):
3424
3477
  # Determine git URL and target directory name
@@ -3481,7 +3534,7 @@ def main():
3481
3534
  os.rmdir(target_path)
3482
3535
 
3483
3536
  asyncio.run(install_extension(cli_args.add))
3484
- exit(0)
3537
+ return ExitCode.SUCCESS
3485
3538
 
3486
3539
  if cli_args.remove is not None:
3487
3540
  if cli_args.remove == "ls":
@@ -3490,11 +3543,11 @@ def main():
3490
3543
  extensions = os.listdir(extensions_path)
3491
3544
  if len(extensions) == 0:
3492
3545
  print("No extensions installed.")
3493
- exit(0)
3546
+ return ExitCode.SUCCESS
3494
3547
  print("Installed extensions:")
3495
3548
  for extension in extensions:
3496
3549
  print(f" {extension}")
3497
- exit(0)
3550
+ return ExitCode.SUCCESS
3498
3551
  # Remove an extension
3499
3552
  extension_name = cli_args.remove
3500
3553
  extensions_path = get_extensions_path()
@@ -3502,7 +3555,7 @@ def main():
3502
3555
 
3503
3556
  if not os.path.exists(target_path):
3504
3557
  print(f"Extension {extension_name} not found at {target_path}")
3505
- exit(1)
3558
+ return ExitCode.FAILED
3506
3559
 
3507
3560
  print(f"Removing extension: {extension_name}...")
3508
3561
  try:
@@ -3510,9 +3563,9 @@ def main():
3510
3563
  print(f"Extension {extension_name} removed successfully.")
3511
3564
  except Exception as e:
3512
3565
  print(f"Failed to remove extension: {e}")
3513
- exit(1)
3566
+ return ExitCode.FAILED
3514
3567
 
3515
- exit(0)
3568
+ return ExitCode.SUCCESS
3516
3569
 
3517
3570
  if cli_args.update:
3518
3571
  if cli_args.update == "ls":
@@ -3521,7 +3574,7 @@ def main():
3521
3574
  extensions = os.listdir(extensions_path)
3522
3575
  if len(extensions) == 0:
3523
3576
  print("No extensions installed.")
3524
- exit(0)
3577
+ return ExitCode.SUCCESS
3525
3578
  print("Installed extensions:")
3526
3579
  for extension in extensions:
3527
3580
  print(f" {extension}")
@@ -3529,7 +3582,7 @@ def main():
3529
3582
  print("\nUsage:")
3530
3583
  print(" llms --update <extension>")
3531
3584
  print(" llms --update all")
3532
- exit(0)
3585
+ return ExitCode.SUCCESS
3533
3586
 
3534
3587
  async def update_extensions(extension_name):
3535
3588
  extensions_path = get_extensions_path()
@@ -3546,7 +3599,7 @@ def main():
3546
3599
  _log(result.stdout.decode("utf-8"))
3547
3600
 
3548
3601
  asyncio.run(update_extensions(cli_args.update))
3549
- exit(0)
3602
+ return ExitCode.SUCCESS
3550
3603
 
3551
3604
  g_app.extensions = install_extensions()
3552
3605
 
@@ -3604,7 +3657,7 @@ def main():
3604
3657
  print(f"\n{model_count} models available from {provider_count} providers")
3605
3658
 
3606
3659
  print_status()
3607
- g_app.exit(0)
3660
+ return ExitCode.SUCCESS
3608
3661
 
3609
3662
  if cli_args.check is not None:
3610
3663
  # Check validity of models for a provider
@@ -3613,7 +3666,7 @@ def main():
3613
3666
  provider_name = cli_args.check
3614
3667
  model_names = extra_args if len(extra_args) > 0 else None
3615
3668
  loop.run_until_complete(check_models(provider_name, model_names))
3616
- g_app.exit(0)
3669
+ return ExitCode.SUCCESS
3617
3670
 
3618
3671
  if cli_args.serve is not None:
3619
3672
  # Disable inactive providers and save to config before starting server
@@ -3658,7 +3711,7 @@ def main():
3658
3711
  print("ERROR: Authentication is enabled but GitHub OAuth is not properly configured.")
3659
3712
  print("Please set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET environment variables,")
3660
3713
  print("or disable authentication by setting 'auth.enabled' to false in llms.json")
3661
- exit(1)
3714
+ return ExitCode.FAILED
3662
3715
 
3663
3716
  _log("Authentication enabled - GitHub OAuth configured")
3664
3717
 
@@ -4252,7 +4305,7 @@ def main():
4252
4305
 
4253
4306
  print(f"Starting server on port {port}...")
4254
4307
  web.run_app(app, host="0.0.0.0", port=port, print=_log)
4255
- g_app.exit(0)
4308
+ return ExitCode.SUCCESS
4256
4309
 
4257
4310
  if cli_args.enable is not None:
4258
4311
  if cli_args.enable.endswith(","):
@@ -4271,7 +4324,7 @@ def main():
4271
4324
  if provider not in g_config["providers"]:
4272
4325
  print(f"Provider '{provider}' not found")
4273
4326
  print(f"Available providers: {', '.join(g_config['providers'].keys())}")
4274
- exit(1)
4327
+ return ExitCode.FAILED
4275
4328
  if provider in g_config["providers"]:
4276
4329
  provider_config, msg = enable_provider(provider)
4277
4330
  print(f"\nEnabled provider {provider}:")
@@ -4282,7 +4335,7 @@ def main():
4282
4335
  print_status()
4283
4336
  if len(msgs) > 0:
4284
4337
  print("\n" + "\n".join(msgs))
4285
- g_app.exit(0)
4338
+ return ExitCode.SUCCESS
4286
4339
 
4287
4340
  if cli_args.disable is not None:
4288
4341
  if cli_args.disable.endswith(","):
@@ -4300,24 +4353,24 @@ def main():
4300
4353
  if provider not in g_config["providers"]:
4301
4354
  print(f"Provider {provider} not found")
4302
4355
  print(f"Available providers: {', '.join(g_config['providers'].keys())}")
4303
- exit(1)
4356
+ return ExitCode.FAILED
4304
4357
  disable_provider(provider)
4305
4358
  print(f"\nDisabled provider {provider}")
4306
4359
 
4307
4360
  print_status()
4308
- g_app.exit(0)
4361
+ return ExitCode.SUCCESS
4309
4362
 
4310
4363
  if cli_args.default is not None:
4311
4364
  default_model = cli_args.default
4312
4365
  provider_model = get_provider_model(default_model)
4313
4366
  if provider_model is None:
4314
4367
  print(f"Model {default_model} not found")
4315
- exit(1)
4368
+ return ExitCode.FAILED
4316
4369
  default_text = g_config["defaults"]["text"]
4317
4370
  default_text["model"] = default_model
4318
4371
  save_config(g_config)
4319
4372
  print(f"\nDefault model set to: {default_model}")
4320
- g_app.exit(0)
4373
+ return ExitCode.SUCCESS
4321
4374
 
4322
4375
  if (
4323
4376
  cli_args.chat is not None
@@ -4339,13 +4392,13 @@ def main():
4339
4392
  template = f"out:{cli_args.out}"
4340
4393
  if template not in g_config["defaults"]:
4341
4394
  print(f"Template for output modality '{cli_args.out}' not found")
4342
- exit(1)
4395
+ return ExitCode.FAILED
4343
4396
  chat = g_config["defaults"][template]
4344
4397
  if cli_args.chat is not None:
4345
4398
  chat_path = os.path.join(os.path.dirname(__file__), cli_args.chat)
4346
4399
  if not os.path.exists(chat_path):
4347
4400
  print(f"Chat request template not found: {chat_path}")
4348
- exit(1)
4401
+ return ExitCode.FAILED
4349
4402
  _log(f"Using chat: {chat_path}")
4350
4403
 
4351
4404
  with open(chat_path) as f:
@@ -4386,19 +4439,48 @@ def main():
4386
4439
  raw=cli_args.raw,
4387
4440
  )
4388
4441
  )
4389
- g_app.exit(0)
4442
+ return ExitCode.SUCCESS
4390
4443
  except Exception as e:
4391
4444
  print(f"{cli_args.logprefix}Error: {e}")
4392
4445
  if cli_args.verbose:
4393
4446
  traceback.print_exc()
4394
- g_app.exit(1)
4447
+ return ExitCode.FAILED
4395
4448
 
4396
4449
  handled = run_extension_cli()
4450
+ return ExitCode.SUCCESS if handled else ExitCode.UNHANDLED
4451
+
4452
+
4453
+ def get_app():
4454
+ return g_app
4397
4455
 
4398
- if not handled:
4456
+
4457
+ def cli(command_line: str):
4458
+ parser = create_arg_parser()
4459
+
4460
+ # Load parser extensions, go through all extensions and load their parser arguments
4461
+ if load_extensions:
4462
+ init_extensions(parser)
4463
+
4464
+ args = shlex.split(command_line)
4465
+ cli_args, extra_args = parser.parse_known_args(args)
4466
+ return cli_exec(cli_args, extra_args)
4467
+
4468
+
4469
+ def main():
4470
+ parser = create_arg_parser()
4471
+
4472
+ # Load parser extensions, go through all extensions and load their parser arguments
4473
+ init_extensions(parser)
4474
+
4475
+ cli_args, extra_args = parser.parse_known_args()
4476
+ exit_code = cli_exec(cli_args, extra_args)
4477
+
4478
+ if exit_code == ExitCode.UNHANDLED:
4399
4479
  # show usage from ArgumentParser
4400
4480
  parser.print_help()
4401
- g_app.exit(0)
4481
+ g_app.exit(0) if g_app else exit(0)
4482
+
4483
+ g_app.exit(exit_code) if g_app else exit(exit_code)
4402
4484
 
4403
4485
 
4404
4486
  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.9',
9
+ version: '3.0.11',
10
10
  base,
11
11
  prefsKey,
12
12
  welcome: 'Welcome to llms.py',
@@ -12,6 +12,47 @@ function hasJsonStructure(str) {
12
12
  return tryParseJson(str) != null
13
13
  }
14
14
 
15
+ function isEmpty(v) {
16
+ return !v || v === '{}' || v === '[]' || v === 'null' || v === 'undefined' || v === '""' || v === "''" || v === "``"
17
+ }
18
+ function embedHtml(html) {
19
+ const resizeScript = `<script>
20
+ let lastH = 0;
21
+ const sendHeight = () => {
22
+ const body = document.body;
23
+ if (!body) return;
24
+ // Force re-calc
25
+ const h = document.documentElement.getBoundingClientRect().height;
26
+ if (Math.abs(h - lastH) > 2) {
27
+ lastH = h;
28
+ window.parent.postMessage({ type: 'iframe-resize', height: h }, '*');
29
+ }
30
+ }
31
+ const ro = new ResizeObserver(sendHeight);
32
+ window.addEventListener('load', () => {
33
+ // Inject styles to prevent infinite loops
34
+ const style = document.createElement('style');
35
+ style.textContent = 'html, body { height: auto !important; min-height: 0 !important; margin: 0 !important; padding: 0 !important; overflow: hidden !important; }';
36
+ document.head.appendChild(style);
37
+
38
+ const body = document.body;
39
+ if (body) {
40
+ ro.observe(body);
41
+ ro.observe(document.documentElement);
42
+ sendHeight();
43
+ }
44
+ });
45
+ <\/script>`
46
+
47
+ const escaped = (html + resizeScript)
48
+ .replace(/&/g, '&amp;')
49
+ .replace(/</g, '&lt;')
50
+ .replace(/>/g, '&gt;')
51
+ .replace(/"/g, '&quot;')
52
+ .replace(/'/g, '&#39;')
53
+ return `<iframe srcdoc="${escaped}" sandbox="allow-scripts" style="width:100%;height:auto;border:none;"></iframe>`
54
+ }
55
+
15
56
  export const TypeText = {
16
57
  template: `
17
58
  <div v-if="text.type === 'text'">
@@ -332,46 +373,6 @@ export const ToolArguments = {
332
373
  },
333
374
  setup(props) {
334
375
  const refArgs = ref()
335
- function isEmpty(v) {
336
- return !v || v === '{}' || v === '[]' || v === 'null' || v === 'undefined' || v === '""' || v === "''" || v === "``"
337
- }
338
- function embedHtml(html) {
339
- const resizeScript = `<script>
340
- let lastH = 0;
341
- const sendHeight = () => {
342
- const body = document.body;
343
- if (!body) return;
344
- // Force re-calc
345
- const h = document.documentElement.getBoundingClientRect().height;
346
- if (Math.abs(h - lastH) > 2) {
347
- lastH = h;
348
- window.parent.postMessage({ type: 'iframe-resize', height: h }, '*');
349
- }
350
- }
351
- const ro = new ResizeObserver(sendHeight);
352
- window.addEventListener('load', () => {
353
- // Inject styles to prevent infinite loops
354
- const style = document.createElement('style');
355
- style.textContent = 'html, body { height: auto !important; min-height: 0 !important; margin: 0 !important; padding: 0 !important; overflow: hidden !important; }';
356
- document.head.appendChild(style);
357
-
358
- const body = document.body;
359
- if (body) {
360
- ro.observe(body);
361
- ro.observe(document.documentElement);
362
- sendHeight();
363
- }
364
- });
365
- <\/script>`
366
-
367
- const escaped = (html + resizeScript)
368
- .replace(/&/g, '&amp;')
369
- .replace(/</g, '&lt;')
370
- .replace(/>/g, '&gt;')
371
- .replace(/"/g, '&quot;')
372
- .replace(/'/g, '&#39;')
373
- return `<iframe srcdoc="${escaped}" sandbox="allow-scripts" style="width:100%;height:auto;border:none;"></iframe>`
374
- }
375
376
  const dict = computed(() => {
376
377
  if (isEmpty(props.value)) return null
377
378
  const ret = tryParseJson(props.value)
@@ -417,9 +418,47 @@ export const ToolArguments = {
417
418
  }
418
419
 
419
420
  export const ToolOutput = {
420
- template: ``,
421
+ template: `
422
+ <div v-if="output" class="border-t border-gray-200 dark:border-gray-700">
423
+ <div class="px-3 py-1.5 flex justify-between items-center border-b border-gray-200 dark:border-gray-800 bg-gray-50/30 dark:bg-gray-800">
424
+ <div class="flex items-center gap-2 ">
425
+ <svg class="size-3.5 text-gray-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
426
+ <span class="text-[10px] uppercase tracking-wider text-gray-400 font-medium">Output</span>
427
+ </div>
428
+ <div v-if="hasJsonStructure(output.content)" class="flex items-center gap-2 text-[10px] uppercase tracking-wider font-medium select-none">
429
+ <span @click="$ctx.setPrefs({ toolFormat: 'text' })"
430
+ class="cursor-pointer transition-colors"
431
+ :class="$ctx.prefs.toolFormat !== 'preview' ? 'text-gray-600 dark:text-gray-300' : 'text-gray-400 hover:text-gray-600 dark:hover:text-gray-300'">
432
+ text
433
+ </span>
434
+ <span class="text-gray-300 dark:text-gray-700">|</span>
435
+ <span @click="$ctx.setPrefs({ toolFormat: 'preview' })"
436
+ class="cursor-pointer transition-colors"
437
+ :class="$ctx.prefs.toolFormat == 'preview' ? 'text-gray-600 dark:text-gray-300' : 'text-gray-400 hover:text-gray-600 dark:hover:text-gray-300'">
438
+ preview
439
+ </span>
440
+ </div>
441
+ </div>
442
+ <div class="not-prose px-3 py-2">
443
+ <pre v-if="$ctx.prefs.toolFormat !== 'preview' || !hasJsonStructure(output.content)" class="tool-output">{{ output.content }}</pre>
444
+ <div v-else class="text-xs">
445
+ <HtmlFormat v-if="tryParseJson(output.content)" :value="tryParseJson(output.content)" :classes="$utils.htmlFormatClasses" />
446
+ <div v-else class="text-gray-500 italic p-2">Invalid JSON content</div>
447
+ </div>
448
+ </div>
449
+ <ViewToolTypes :output="output" class="p-2" />
450
+ </div>
451
+ `,
452
+ props: {
453
+ tool: Object,
454
+ output: Object,
455
+ },
421
456
  setup(props) {
422
457
 
458
+ return {
459
+ tryParseJson,
460
+ hasJsonStructure,
461
+ }
423
462
  }
424
463
  }
425
464
 
@@ -523,36 +562,8 @@ export const ChatBody = {
523
562
 
524
563
  <ToolArguments :value="tool.function.arguments" />
525
564
 
526
- <!-- Tool Output (Nested) -->
527
- <div v-if="getToolOutput(tool.id)" class="border-t border-gray-200 dark:border-gray-700">
528
- <div class="px-3 py-1.5 flex justify-between items-center border-b border-gray-200 dark:border-gray-800 bg-gray-50/30 dark:bg-gray-800">
529
- <div class="flex items-center gap-2 ">
530
- <svg class="size-3.5 text-gray-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
531
- <span class="text-[10px] uppercase tracking-wider text-gray-400 font-medium">Output</span>
532
- </div>
533
- <div v-if="hasJsonStructure(getToolOutput(tool.id).content)" class="flex items-center gap-2 text-[10px] uppercase tracking-wider font-medium select-none">
534
- <span @click="setPrefs({ toolFormat: 'text' })"
535
- class="cursor-pointer transition-colors"
536
- :class="prefs.toolFormat !== 'preview' ? 'text-gray-600 dark:text-gray-300' : 'text-gray-400 hover:text-gray-600 dark:hover:text-gray-300'">
537
- text
538
- </span>
539
- <span class="text-gray-300 dark:text-gray-700">|</span>
540
- <span @click="setPrefs({ toolFormat: 'preview' })"
541
- class="cursor-pointer transition-colors"
542
- :class="prefs.toolFormat == 'preview' ? 'text-gray-600 dark:text-gray-300' : 'text-gray-400 hover:text-gray-600 dark:hover:text-gray-300'">
543
- preview
544
- </span>
545
- </div>
546
- </div>
547
- <div class="not-prose px-3 py-2">
548
- <pre v-if="prefs.toolFormat !== 'preview' || !hasJsonStructure(getToolOutput(tool.id).content)" class="tool-output">{{ getToolOutput(tool.id).content }}</pre>
549
- <div v-else class="text-xs">
550
- <HtmlFormat v-if="tryParseJson(getToolOutput(tool.id).content)" :value="tryParseJson(getToolOutput(tool.id).content)" :classes="$utils.htmlFormatClasses" />
551
- <div v-else class="text-gray-500 italic p-2">Invalid JSON content</div>
552
- </div>
553
- </div>
554
- <ViewToolTypes :output="getToolOutput(tool.id)" />
555
- </div>
565
+ <ToolOutput :tool="tool" :output="getToolOutput(tool.id)" />
566
+
556
567
  </div>
557
568
  </div>
558
569
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llms-py
3
- Version: 3.0.9
3
+ Version: 3.0.11
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
@@ -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=7nEm4UZph9cyswu_28_Vb14CSo3yg6BFhuQxdmIrFXk,169747
6
+ llms/main.py,sha256=IoKt4rGaAKAcc-Y2M-cTbxyIqOy9tmeiWxnrHjzHHv0,173872
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,7 +128,7 @@ 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
134
  llms/extensions/providers/google.py,sha256=9P90bEefRA18tpjfEuAz1T5YpwzdOngrTFhw-LI3eXg,24434
@@ -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=WRt2u5w8TdY2u9_rQqp7Ms7MOrWfKSLT4Z5rDS6VAZ8,6540
146
+ llms/ui/ai.mjs,sha256=GVyxu3Thsuck0b1Akvqk2c5w-maSzONU06QN-TUWoJk,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
@@ -166,12 +166,12 @@ llms/ui/lib/vue.mjs,sha256=75FuLhUTPk19sncwNIrm0BGEL0_Qw298-_v01fPWYoI,542872
166
166
  llms/ui/modules/icons.mjs,sha256=LGcH0ys0QLS2ZKCO42qHpwPYbBV_EssoWLezU4XZEzU,27751
167
167
  llms/ui/modules/layout.mjs,sha256=8pAxs8bedQI3b3eRA9nrfpLZznLmrpp4BZvigYAQjpQ,12572
168
168
  llms/ui/modules/model-selector.mjs,sha256=6U4rAZ7vmQELFRQGWk4YEtq02v3lyHdMq6yUOp-ArXg,43184
169
- llms/ui/modules/chat/ChatBody.mjs,sha256=8k81r1HTxrkmLsG7xvDmYMInThiZ37xVQBfld1tLWW0,50366
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.9.dist-info/licenses/LICENSE,sha256=bus9cuAOWeYqBk2OuhSABVV1P4z7hgrEFISpyda_H5w,1532
173
- llms_py-3.0.9.dist-info/METADATA,sha256=6PE0ikoQqoxK1TDYoo0iELmrYStuzIUewiHlmb3kZFY,2191
174
- llms_py-3.0.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
175
- llms_py-3.0.9.dist-info/entry_points.txt,sha256=WswyE7PfnkZMIxboC-MS6flBD6wm-CYU7JSUnMhqMfM,40
176
- llms_py-3.0.9.dist-info/top_level.txt,sha256=gC7hk9BKSeog8gyg-EM_g2gxm1mKHwFRfK-10BxOsa4,5
177
- llms_py-3.0.9.dist-info/RECORD,,
172
+ llms_py-3.0.11.dist-info/licenses/LICENSE,sha256=bus9cuAOWeYqBk2OuhSABVV1P4z7hgrEFISpyda_H5w,1532
173
+ llms_py-3.0.11.dist-info/METADATA,sha256=90LpikNrckf1EaRrQHKGLOGobshThyJsRcX75csIs1Y,2192
174
+ llms_py-3.0.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
175
+ llms_py-3.0.11.dist-info/entry_points.txt,sha256=WswyE7PfnkZMIxboC-MS6flBD6wm-CYU7JSUnMhqMfM,40
176
+ llms_py-3.0.11.dist-info/top_level.txt,sha256=gC7hk9BKSeog8gyg-EM_g2gxm1mKHwFRfK-10BxOsa4,5
177
+ llms_py-3.0.11.dist-info/RECORD,,