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 +2 -2
- llms/extensions/providers/anthropic.py +9 -1
- llms/main.py +203 -121
- llms/ui/ai.mjs +1 -1
- llms/ui/modules/chat/ChatBody.mjs +82 -71
- {llms_py-3.0.9.dist-info → llms_py-3.0.11.dist-info}/METADATA +1 -1
- {llms_py-3.0.9.dist-info → llms_py-3.0.11.dist-info}/RECORD +11 -11
- {llms_py-3.0.9.dist-info → llms_py-3.0.11.dist-info}/WHEEL +0 -0
- {llms_py-3.0.9.dist-info → llms_py-3.0.11.dist-info}/entry_points.txt +0 -0
- {llms_py-3.0.9.dist-info → llms_py-3.0.11.dist-info}/licenses/LICENSE +0 -0
- {llms_py-3.0.9.dist-info → llms_py-3.0.11.dist-info}/top_level.txt +0 -0
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"]
|
|
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.
|
|
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
|
|
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'
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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:
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3546
|
+
return ExitCode.SUCCESS
|
|
3494
3547
|
print("Installed extensions:")
|
|
3495
3548
|
for extension in extensions:
|
|
3496
3549
|
print(f" {extension}")
|
|
3497
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3566
|
+
return ExitCode.FAILED
|
|
3514
3567
|
|
|
3515
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4356
|
+
return ExitCode.FAILED
|
|
4304
4357
|
disable_provider(provider)
|
|
4305
4358
|
print(f"\nDisabled provider {provider}")
|
|
4306
4359
|
|
|
4307
4360
|
print_status()
|
|
4308
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -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, '&')
|
|
49
|
+
.replace(/</g, '<')
|
|
50
|
+
.replace(/>/g, '>')
|
|
51
|
+
.replace(/"/g, '"')
|
|
52
|
+
.replace(/'/g, ''')
|
|
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, '&')
|
|
369
|
-
.replace(/</g, '<')
|
|
370
|
-
.replace(/>/g, '>')
|
|
371
|
-
.replace(/"/g, '"')
|
|
372
|
-
.replace(/'/g, ''')
|
|
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
|
-
|
|
527
|
-
|
|
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
|
|
|
@@ -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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
173
|
-
llms_py-3.0.
|
|
174
|
-
llms_py-3.0.
|
|
175
|
-
llms_py-3.0.
|
|
176
|
-
llms_py-3.0.
|
|
177
|
-
llms_py-3.0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|