llms-py 3.0.24__py3-none-any.whl → 3.0.26__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/main.py CHANGED
@@ -17,7 +17,6 @@ import json
17
17
  import mimetypes
18
18
  import os
19
19
  import re
20
- import secrets
21
20
  import shlex
22
21
  import shutil
23
22
  import site
@@ -45,7 +44,7 @@ from typing import (
45
44
  get_origin,
46
45
  get_type_hints,
47
46
  )
48
- from urllib.parse import parse_qs, urlencode, urljoin
47
+ from urllib.parse import parse_qs, urljoin
49
48
 
50
49
  import aiohttp
51
50
  from aiohttp import web
@@ -57,12 +56,16 @@ try:
57
56
  except ImportError:
58
57
  HAS_PIL = False
59
58
 
60
- VERSION = "3.0.24"
61
59
  _ROOT = None
60
+ VERSION = "3.0.26"
62
61
  DEBUG = os.getenv("DEBUG") == "1"
63
62
  MOCK = os.getenv("MOCK") == "1"
64
63
  MOCK_DIR = os.getenv("MOCK_DIR")
65
64
  DISABLE_EXTENSIONS = (os.getenv("LLMS_DISABLE") or "").split(",")
65
+ DEFAULT_LIMITS = {
66
+ "client_timeout": 120,
67
+ "client_max_size": 20971520,
68
+ }
66
69
  g_config_path = None
67
70
  g_config = None
68
71
  g_providers = None
@@ -70,8 +73,6 @@ g_handlers = {}
70
73
  g_verbose = False
71
74
  g_logprefix = ""
72
75
  g_default_model = ""
73
- g_sessions = {} # OAuth session storage: {session_token: {userId, userName, displayName, profileUrl, email, created}}
74
- g_oauth_states = {} # CSRF protection: {state: {created, redirect_uri}}
75
76
  g_app = None # ExtensionsContext Singleton
76
77
 
77
78
 
@@ -478,7 +479,7 @@ async def download_file(url):
478
479
 
479
480
  async def session_download_file(session, url, default_mimetype="application/octet-stream"):
480
481
  try:
481
- async with session.get(url, timeout=aiohttp.ClientTimeout(total=120)) as response:
482
+ async with session.get(url, timeout=get_client_timeout()) as response:
482
483
  response.raise_for_status()
483
484
  content = await response.read()
484
485
  mimetype = response.headers.get("Content-Type")
@@ -1297,7 +1298,7 @@ class OpenAiCompatible:
1297
1298
  async with aiohttp.ClientSession() as session:
1298
1299
  started_at = time.time()
1299
1300
  async with session.post(
1300
- self.chat_url, headers=self.headers, data=json.dumps(chat), timeout=aiohttp.ClientTimeout(total=120)
1301
+ self.chat_url, headers=self.headers, data=json.dumps(chat), timeout=get_client_timeout()
1301
1302
  ) as response:
1302
1303
  chat["metadata"] = metadata
1303
1304
  return self.to_response(await response_json(response), chat, started_at, context=context)
@@ -1364,7 +1365,7 @@ class OllamaProvider(OpenAiCompatible):
1364
1365
  async with aiohttp.ClientSession() as session:
1365
1366
  _log(f"GET {self.api}/api/tags")
1366
1367
  async with session.get(
1367
- f"{self.api}/api/tags", headers=self.headers, timeout=aiohttp.ClientTimeout(total=120)
1368
+ f"{self.api}/api/tags", headers=self.headers, timeout=get_client_timeout()
1368
1369
  ) as response:
1369
1370
  data = await response_json(response)
1370
1371
  for model in data.get("models", []):
@@ -1425,7 +1426,7 @@ class LMStudioProvider(OllamaProvider):
1425
1426
  async with aiohttp.ClientSession() as session:
1426
1427
  _log(f"GET {self.api}/models")
1427
1428
  async with session.get(
1428
- f"{self.api}/models", headers=self.headers, timeout=aiohttp.ClientTimeout(total=120)
1429
+ f"{self.api}/models", headers=self.headers, timeout=get_client_timeout()
1429
1430
  ) as response:
1430
1431
  data = await response_json(response)
1431
1432
  for model in data.get("data", []):
@@ -1762,6 +1763,15 @@ def convert_tool_args(function_name, function_args):
1762
1763
 
1763
1764
  return function_args
1764
1765
 
1766
+ def get_tool_property(function_name, prop_name):
1767
+ tool_def = g_app.get_tool_definition(function_name)
1768
+ if not tool_def:
1769
+ return None
1770
+ if "function" in tool_def and "parameters" in tool_def["function"]:
1771
+ parameters = tool_def.get("function", {}).get("parameters")
1772
+ properties = parameters.get("properties", {})
1773
+ return properties.get(prop_name)
1774
+ return None
1765
1775
 
1766
1776
  async def g_exec_tool(function_name, function_args):
1767
1777
  _log(f"g_exec_tool: {function_name}")
@@ -1916,6 +1926,8 @@ async def g_chat_completion(chat, context=None):
1916
1926
  except Exception as e:
1917
1927
  tool_result = f"Error: Failed to parse JSON arguments for tool '{function_name}': {to_error_message(e)}"
1918
1928
  else:
1929
+ if "user" in context and get_tool_property(function_name, "user"):
1930
+ function_args["user"] = context["user"]
1919
1931
  tool_result, resources = await g_exec_tool(function_name, function_args)
1920
1932
 
1921
1933
  # Append tool result to history
@@ -2790,8 +2802,45 @@ async def watch_config_files(config_path, providers_path, interval=1):
2790
2802
  pass
2791
2803
 
2792
2804
 
2793
- def get_session_token(request):
2794
- return request.query.get("session") or request.headers.get("X-Session-Token") or request.cookies.get("llms-token")
2805
+ class AuthProvider:
2806
+ def __init__(self, app):
2807
+ self.app = app
2808
+
2809
+ def get_session_token(self, request: web.Request):
2810
+ return (
2811
+ request.query.get("session") or request.headers.get("X-Session-Token") or request.cookies.get("llms-token")
2812
+ )
2813
+
2814
+ def get_session(self, request: web.Request) -> Optional[Dict[str, Any]]:
2815
+ session_token = self.get_session_token(request)
2816
+ # _dbg(
2817
+ # f"Session token: {session_token} / {len(self.app.sessions)} sessions = {session_token in self.app.sessions}"
2818
+ # )
2819
+
2820
+ if not session_token or session_token not in self.app.sessions:
2821
+ return None
2822
+
2823
+ session_data = self.app.sessions[session_token]
2824
+ return session_data
2825
+
2826
+ def get_username(self, request: web.Request) -> Optional[str]:
2827
+ session = self.get_session(request)
2828
+ if session:
2829
+ return session.get("userName")
2830
+ return None
2831
+
2832
+ def check_auth(self, request: web.Request) -> Tuple[bool, Optional[Dict[str, Any]]]:
2833
+ """Check if request is authenticated. Returns (is_authenticated, user_data)"""
2834
+ session = self.get_session(request)
2835
+ if session:
2836
+ return True, session
2837
+ return False, None
2838
+
2839
+
2840
+ def get_client_timeout(app=None):
2841
+ app = app or g_app
2842
+ timeout = app.limits.get("client_timeout", 120) if app else 120
2843
+ return aiohttp.ClientTimeout(total=timeout)
2795
2844
 
2796
2845
 
2797
2846
  class AppExtensions:
@@ -2803,6 +2852,7 @@ class AppExtensions:
2803
2852
  self.cli_args = cli_args
2804
2853
  self.extra_args = extra_args
2805
2854
  self.config = None
2855
+ self.limits = DEFAULT_LIMITS
2806
2856
  self.error_auth_required = create_error_response("Authentication required", "Unauthorized")
2807
2857
  self.ui_extensions = []
2808
2858
  self.chat_request_filters = []
@@ -2824,6 +2874,9 @@ class AppExtensions:
2824
2874
  self.index_headers = []
2825
2875
  self.index_footers = []
2826
2876
  self.allowed_directories = []
2877
+ self.auth_providers = []
2878
+ self.sessions = {} # OAuth session storage: {session_token: {userId, userName, displayName, profileUrl, email, created}}
2879
+ self.oauth_states = {} # CSRF protection: {state: {created, redirect_uri}}
2827
2880
  self.request_args = {
2828
2881
  "image_config": dict, # e.g. { "aspect_ratio": "1:1" }
2829
2882
  "temperature": float, # e.g: 0.7
@@ -2879,7 +2932,12 @@ class AppExtensions:
2879
2932
 
2880
2933
  def set_config(self, config: Dict[str, Any]):
2881
2934
  self.config = config
2882
- self.auth_enabled = self.config.get("auth", {}).get("enabled", False)
2935
+ self.limits = self.config.get("limits", DEFAULT_LIMITS)
2936
+ self.limits["client_timeout"] = self.limits.get("client_timeout", 120)
2937
+ self.limits["client_max_size"] = self.limits.get("client_max_size", 20971520)
2938
+
2939
+ def get_client_timeout(self):
2940
+ return get_client_timeout(self)
2883
2941
 
2884
2942
  def set_allowed_directories(
2885
2943
  self, directories: List[Annotated[str, "List of absolute paths that are allowed to be accessed."]]
@@ -2897,40 +2955,46 @@ class AppExtensions:
2897
2955
  """Get the list of allowed directories."""
2898
2956
  return self.allowed_directories
2899
2957
 
2900
- # Authentication middleware helper
2901
- def check_auth(self, request: web.Request) -> Tuple[bool, Optional[Dict[str, Any]]]:
2902
- """Check if request is authenticated. Returns (is_authenticated, user_data)"""
2903
- if not self.auth_enabled:
2904
- return True, None
2958
+ def add_auth_provider(self, auth_provider: AuthProvider) -> None:
2959
+ """Add an authentication provider."""
2960
+ self.auth_providers.append(auth_provider)
2905
2961
 
2906
- # Check for OAuth session token
2907
- session_token = get_session_token(request)
2908
- if session_token and session_token in g_sessions:
2909
- return True, g_sessions[session_token]
2910
-
2911
- # Check for API key
2912
- auth_header = request.headers.get("Authorization", "")
2913
- if auth_header.startswith("Bearer "):
2914
- api_key = auth_header[7:]
2915
- if api_key:
2916
- return True, {"authProvider": "apikey"}
2917
-
2918
- return False, None
2962
+ def is_auth_enabled(self) -> bool:
2963
+ return len(self.auth_providers) > 0
2919
2964
 
2920
2965
  def get_session(self, request: web.Request) -> Optional[Dict[str, Any]]:
2921
- session_token = get_session_token(request)
2966
+ for auth_provider in self.auth_providers:
2967
+ session = auth_provider.get_session(request)
2968
+ if session:
2969
+ return session
2970
+ return None
2922
2971
 
2923
- if not session_token or session_token not in g_sessions:
2972
+ def get_username(self, request: web.Request) -> Optional[str]:
2973
+ for auth_provider in self.auth_providers:
2974
+ username = auth_provider.get_username(request)
2975
+ if username:
2976
+ return username
2924
2977
  return None
2925
2978
 
2926
- session_data = g_sessions[session_token]
2927
- return session_data
2979
+ def assert_username(self, request: web.Request) -> str:
2980
+ if not self.is_auth_enabled():
2981
+ return None
2982
+ username = self.get_username(request)
2983
+ if not username:
2984
+ raise Exception("Authentication required")
2985
+ return username
2928
2986
 
2929
- def get_username(self, request: web.Request) -> Optional[str]:
2930
- session = self.get_session(request)
2931
- if session:
2932
- return session.get("userName")
2933
- return None
2987
+ def check_auth(self, request: web.Request) -> Tuple[bool, Optional[Dict[str, Any]]]:
2988
+ """Check if request is authenticated. Returns (is_authenticated, user_data)"""
2989
+ if len(self.auth_providers) == 0:
2990
+ return True, None
2991
+
2992
+ for auth_provider in self.auth_providers:
2993
+ is_authenticated, user_data = auth_provider.check_auth(request)
2994
+ if is_authenticated:
2995
+ return True, user_data
2996
+
2997
+ return False, None
2934
2998
 
2935
2999
  def get_user_path(self, username: Optional[str] = None) -> str:
2936
3000
  if username:
@@ -3032,6 +3096,8 @@ def handler_name(handler):
3032
3096
  class ExtensionContext:
3033
3097
  def __init__(self, app: AppExtensions, path: str):
3034
3098
  self.app = app
3099
+ self.config = app.config
3100
+ self.limits = app.limits
3035
3101
  self.cli_args = app.cli_args
3036
3102
  self.extra_args = app.extra_args
3037
3103
  self.error_auth_required = app.error_auth_required
@@ -3046,8 +3112,33 @@ class ExtensionContext:
3046
3112
  self.verbose = g_verbose
3047
3113
  self.aspect_ratios = app.aspect_ratios
3048
3114
  self.request_args = app.request_args
3115
+ self.sessions = app.sessions
3116
+ self.oauth_states = app.oauth_states
3049
3117
  self.disabled = False
3050
3118
 
3119
+ def get_client_timeout(self):
3120
+ return self.app.get_client_timeout()
3121
+
3122
+ def add_auth_provider(self, auth_provider: AuthProvider) -> None:
3123
+ """Add an authentication provider."""
3124
+ self.app.add_auth_provider(auth_provider)
3125
+ self.log(f"Added Auth Provider: {auth_provider.__class__.__name__}, Authentication is now enabled")
3126
+
3127
+ def is_auth_enabled(self) -> bool:
3128
+ return self.app.is_auth_enabled()
3129
+
3130
+ def get_session(self, request: web.Request) -> Optional[Dict[str, Any]]:
3131
+ return self.app.get_session(request)
3132
+
3133
+ def get_username(self, request: web.Request) -> Optional[str]:
3134
+ return self.app.get_username(request)
3135
+
3136
+ def assert_username(self, request: web.Request) -> Optional[str]:
3137
+ return self.app.assert_username(request)
3138
+
3139
+ def check_auth(self, request: web.Request) -> Tuple[bool, Optional[Dict[str, Any]]]:
3140
+ return self.app.check_auth(request)
3141
+
3051
3142
  def set_allowed_directories(
3052
3143
  self, directories: List[Annotated[str, "List of absolute paths that are allowed to be accessed."]]
3053
3144
  ) -> None:
@@ -3129,6 +3220,9 @@ class ExtensionContext:
3129
3220
  def error_response(self, e: Exception, stacktrace: bool = False) -> Dict[str, Any]:
3130
3221
  return to_error_response(e, stacktrace=stacktrace)
3131
3222
 
3223
+ def create_error_response(self, message, error_code="Error", stack_trace=None):
3224
+ return create_error_response(message, error_code, stack_trace)
3225
+
3132
3226
  def add_provider(self, provider: Any):
3133
3227
  self.log(f"Registered provider: {provider.__name__}")
3134
3228
  self.app.all_providers.append(provider)
@@ -3983,33 +4077,11 @@ def cli_exec(cli_args, extra_args):
3983
4077
  port = int(cli_args.serve)
3984
4078
 
3985
4079
  # Validate auth configuration if enabled
3986
- auth_enabled = g_config.get("auth", {}).get("enabled", False)
3987
- if auth_enabled:
3988
- github_config = g_config.get("auth", {}).get("github", {})
3989
- client_id = github_config.get("client_id", "")
3990
- client_secret = github_config.get("client_secret", "")
3991
-
3992
- # Expand environment variables
3993
- if client_id.startswith("$"):
3994
- client_id = client_id[1:]
3995
- if client_secret.startswith("$"):
3996
- client_secret = client_secret[1:]
3997
-
3998
- client_id = os.getenv(client_id, client_id)
3999
- client_secret = os.getenv(client_secret, client_secret)
4000
-
4001
- if (
4002
- not client_id
4003
- or not client_secret
4004
- or client_id == "GITHUB_CLIENT_ID"
4005
- or client_secret == "GITHUB_CLIENT_SECRET"
4006
- ):
4007
- print("ERROR: Authentication is enabled but GitHub OAuth is not properly configured.")
4008
- print("Please set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET environment variables,")
4009
- print("or disable authentication by setting 'auth.enabled' to false in llms.json")
4010
- return ExitCode.FAILED
4011
-
4012
- _log("Authentication enabled - GitHub OAuth configured")
4080
+ if g_config.get("auth", {}).get("enabled", False):
4081
+ print("ERROR: GitHub Authentication has moved to the github_auth extension.")
4082
+ print("Please remove the auth configuration from llms.json.")
4083
+ print("Learn more: https://llmspy.org/docs/deployment/github-oauth")
4084
+ return ExitCode.FAILED
4013
4085
 
4014
4086
  client_max_size = g_config.get("limits", {}).get(
4015
4087
  "client_max_size", 20 * 1024 * 1024
@@ -4226,236 +4298,6 @@ def cli_exec(cli_args, extra_args):
4226
4298
 
4227
4299
  app.router.add_get("/~cache/{tail:.*}", cache_handler)
4228
4300
 
4229
- # OAuth handlers
4230
- async def github_auth_handler(request):
4231
- """Initiate GitHub OAuth flow"""
4232
- if "auth" not in g_config or "github" not in g_config["auth"]:
4233
- return web.json_response(create_error_response("GitHub OAuth not configured"), status=500)
4234
-
4235
- auth_config = g_config["auth"]["github"]
4236
- client_id = auth_config.get("client_id", "")
4237
- redirect_uri = auth_config.get("redirect_uri", "")
4238
-
4239
- # Expand environment variables
4240
- if client_id.startswith("$"):
4241
- client_id = client_id[1:]
4242
- if redirect_uri.startswith("$"):
4243
- redirect_uri = redirect_uri[1:]
4244
-
4245
- client_id = os.getenv(client_id, client_id)
4246
- redirect_uri = os.getenv(redirect_uri, redirect_uri)
4247
-
4248
- if not client_id:
4249
- return web.json_response(create_error_response("GitHub client_id not configured"), status=500)
4250
-
4251
- # Generate CSRF state token
4252
- state = secrets.token_urlsafe(32)
4253
- g_oauth_states[state] = {"created": time.time(), "redirect_uri": redirect_uri}
4254
-
4255
- # Clean up old states (older than 10 minutes)
4256
- current_time = time.time()
4257
- expired_states = [s for s, data in g_oauth_states.items() if current_time - data["created"] > 600]
4258
- for s in expired_states:
4259
- del g_oauth_states[s]
4260
-
4261
- # Build GitHub authorization URL
4262
- params = {
4263
- "client_id": client_id,
4264
- "redirect_uri": redirect_uri,
4265
- "state": state,
4266
- "scope": "read:user user:email",
4267
- }
4268
- auth_url = f"https://github.com/login/oauth/authorize?{urlencode(params)}"
4269
-
4270
- return web.HTTPFound(auth_url)
4271
-
4272
- def validate_user(github_username):
4273
- auth_config = g_config["auth"]["github"]
4274
- # Check if user is restricted
4275
- restrict_to = auth_config.get("restrict_to", "")
4276
-
4277
- # Expand environment variables
4278
- if restrict_to.startswith("$"):
4279
- restrict_to = restrict_to[1:]
4280
-
4281
- restrict_to = os.getenv(restrict_to, None if restrict_to == "GITHUB_USERS" else restrict_to)
4282
-
4283
- # If restrict_to is configured, validate the user
4284
- if restrict_to:
4285
- # Parse allowed users (comma or space delimited)
4286
- allowed_users = [u.strip() for u in re.split(r"[,\s]+", restrict_to) if u.strip()]
4287
-
4288
- # Check if user is in the allowed list
4289
- if not github_username or github_username not in allowed_users:
4290
- _log(f"Access denied for user: {github_username}. Not in allowed list: {allowed_users}")
4291
- return web.Response(
4292
- text=f"Access denied. User '{github_username}' is not authorized to access this application.",
4293
- status=403,
4294
- )
4295
- return None
4296
-
4297
- async def github_callback_handler(request):
4298
- """Handle GitHub OAuth callback"""
4299
- code = request.query.get("code")
4300
- state = request.query.get("state")
4301
-
4302
- # Handle malformed URLs where query params are appended with & instead of ?
4303
- if not code and "tail" in request.match_info:
4304
- tail = request.match_info["tail"]
4305
- if tail.startswith("&"):
4306
- params = parse_qs(tail[1:])
4307
- code = params.get("code", [None])[0]
4308
- state = params.get("state", [None])[0]
4309
-
4310
- if not code or not state:
4311
- return web.Response(text="Missing code or state parameter", status=400)
4312
-
4313
- # Verify state token (CSRF protection)
4314
- if state not in g_oauth_states:
4315
- return web.Response(text="Invalid state parameter", status=400)
4316
-
4317
- g_oauth_states.pop(state)
4318
-
4319
- if "auth" not in g_config or "github" not in g_config["auth"]:
4320
- return web.json_response(create_error_response("GitHub OAuth not configured"), status=500)
4321
-
4322
- auth_config = g_config["auth"]["github"]
4323
- client_id = auth_config.get("client_id", "")
4324
- client_secret = auth_config.get("client_secret", "")
4325
- redirect_uri = auth_config.get("redirect_uri", "")
4326
-
4327
- # Expand environment variables
4328
- if client_id.startswith("$"):
4329
- client_id = client_id[1:]
4330
- if client_secret.startswith("$"):
4331
- client_secret = client_secret[1:]
4332
- if redirect_uri.startswith("$"):
4333
- redirect_uri = redirect_uri[1:]
4334
-
4335
- client_id = os.getenv(client_id, client_id)
4336
- client_secret = os.getenv(client_secret, client_secret)
4337
- redirect_uri = os.getenv(redirect_uri, redirect_uri)
4338
-
4339
- if not client_id or not client_secret:
4340
- return web.json_response(create_error_response("GitHub OAuth credentials not configured"), status=500)
4341
-
4342
- # Exchange code for access token
4343
- async with aiohttp.ClientSession() as session:
4344
- token_url = "https://github.com/login/oauth/access_token"
4345
- token_data = {
4346
- "client_id": client_id,
4347
- "client_secret": client_secret,
4348
- "code": code,
4349
- "redirect_uri": redirect_uri,
4350
- }
4351
- headers = {"Accept": "application/json"}
4352
-
4353
- async with session.post(token_url, data=token_data, headers=headers) as resp:
4354
- token_response = await resp.json()
4355
- access_token = token_response.get("access_token")
4356
-
4357
- if not access_token:
4358
- error = token_response.get("error_description", "Failed to get access token")
4359
- return web.json_response(create_error_response(f"OAuth error: {error}"), status=400)
4360
-
4361
- # Fetch user info
4362
- user_url = "https://api.github.com/user"
4363
- headers = {"Authorization": f"Bearer {access_token}", "Accept": "application/json"}
4364
-
4365
- async with session.get(user_url, headers=headers) as resp:
4366
- user_data = await resp.json()
4367
-
4368
- # Validate user
4369
- error_response = validate_user(user_data.get("login", ""))
4370
- if error_response:
4371
- return error_response
4372
-
4373
- # Create session
4374
- session_token = secrets.token_urlsafe(32)
4375
- g_sessions[session_token] = {
4376
- "userId": str(user_data.get("id", "")),
4377
- "userName": user_data.get("login", ""),
4378
- "displayName": user_data.get("name", ""),
4379
- "profileUrl": user_data.get("avatar_url", ""),
4380
- "email": user_data.get("email", ""),
4381
- "created": time.time(),
4382
- }
4383
-
4384
- # Redirect to UI with session token
4385
- response = web.HTTPFound(f"/?session={session_token}")
4386
- response.set_cookie("llms-token", session_token, httponly=True, path="/", max_age=86400)
4387
- return response
4388
-
4389
- async def session_handler(request):
4390
- """Validate and return session info"""
4391
- session_token = get_session_token(request)
4392
-
4393
- if not session_token or session_token not in g_sessions:
4394
- return web.json_response(create_error_response("Invalid or expired session"), status=401)
4395
-
4396
- session_data = g_sessions[session_token]
4397
-
4398
- # Clean up old sessions (older than 24 hours)
4399
- current_time = time.time()
4400
- expired_sessions = [token for token, data in g_sessions.items() if current_time - data["created"] > 86400]
4401
- for token in expired_sessions:
4402
- del g_sessions[token]
4403
-
4404
- return web.json_response({**session_data, "sessionToken": session_token})
4405
-
4406
- async def logout_handler(request):
4407
- """End OAuth session"""
4408
- session_token = get_session_token(request)
4409
-
4410
- if session_token and session_token in g_sessions:
4411
- del g_sessions[session_token]
4412
-
4413
- response = web.json_response({"success": True})
4414
- response.del_cookie("llms-token")
4415
- return response
4416
-
4417
- async def auth_handler(request):
4418
- """Check authentication status and return user info"""
4419
- # Check for OAuth session token
4420
- session_token = get_session_token(request)
4421
-
4422
- if session_token and session_token in g_sessions:
4423
- session_data = g_sessions[session_token]
4424
- return web.json_response(
4425
- {
4426
- "userId": session_data.get("userId", ""),
4427
- "userName": session_data.get("userName", ""),
4428
- "displayName": session_data.get("displayName", ""),
4429
- "profileUrl": session_data.get("profileUrl", ""),
4430
- "authProvider": "github",
4431
- }
4432
- )
4433
-
4434
- # Check for API key in Authorization header
4435
- # auth_header = request.headers.get('Authorization', '')
4436
- # if auth_header.startswith('Bearer '):
4437
- # # For API key auth, return a basic response
4438
- # # You can customize this based on your API key validation logic
4439
- # api_key = auth_header[7:]
4440
- # if api_key: # Add your API key validation logic here
4441
- # return web.json_response({
4442
- # "userId": "1",
4443
- # "userName": "apiuser",
4444
- # "displayName": "API User",
4445
- # "profileUrl": "",
4446
- # "authProvider": "apikey"
4447
- # })
4448
-
4449
- # Not authenticated - return error in expected format
4450
- return web.json_response(g_app.error_auth_required, status=401)
4451
-
4452
- app.router.add_get("/auth", auth_handler)
4453
- app.router.add_get("/auth/github", github_auth_handler)
4454
- app.router.add_get("/auth/github/callback", github_callback_handler)
4455
- app.router.add_get("/auth/github/callback{tail:.*}", github_callback_handler)
4456
- app.router.add_get("/auth/session", session_handler)
4457
- app.router.add_post("/auth/logout", logout_handler)
4458
-
4459
4301
  async def ui_static(request: web.Request) -> web.Response:
4460
4302
  path = Path(request.match_info["path"])
4461
4303
 
@@ -4494,8 +4336,8 @@ def cli_exec(cli_args, extra_args):
4494
4336
  enabled, disabled = provider_status()
4495
4337
  ret["status"] = {"all": list(g_config["providers"].keys()), "enabled": enabled, "disabled": disabled}
4496
4338
  # Add auth configuration
4497
- ret["requiresAuth"] = auth_enabled
4498
- ret["authType"] = "oauth" if auth_enabled else "apikey"
4339
+ ret["requiresAuth"] = g_app.is_auth_enabled()
4340
+ ret["authTypes"] = [provider.__class__.__name__ for provider in g_app.auth_providers]
4499
4341
  return web.json_response(ret)
4500
4342
 
4501
4343
  app.router.add_get("/config", config_handler)
llms/ui/App.mjs CHANGED
@@ -173,7 +173,10 @@ export default {
173
173
  <div>
174
174
  <ModelSelector :models="$state.models" v-model="$state.selectedModel" />
175
175
  </div>
176
- <TopBar id="top-bar" />
176
+ <div class="flex items-center gap-2">
177
+ <TopBar id="top-bar" />
178
+ <Avatar />
179
+ </div>
177
180
  </div>
178
181
  <TopPanel v-if="$ai.hasAccess" id="top-panel" :class="$ctx.cls('top-panel', 'shrink-0')" />
179
182
  <div id="page" :class="$ctx.cls('page', 'flex-1 overflow-y-auto min-h-0 flex flex-col')">
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.24',
9
+ version: '3.0.26',
10
10
  base,
11
11
  prefsKey,
12
12
  welcome: 'Welcome to llms.py',
@@ -165,7 +165,6 @@ export const o = {
165
165
  return { config, models, extensions, auth }
166
166
  },
167
167
 
168
-
169
168
  async uploadFile(file) {
170
169
  const formData = new FormData()
171
170
  formData.append('file', file)
@@ -643,8 +643,7 @@ export const ChatBody = {
643
643
  <div class="mx-auto max-w-6xl px-4 py-6">
644
644
 
645
645
  <div v-if="!$ai.hasAccess">
646
- <OAuthSignIn v-if="$ai.authType === 'oauth'" @done="$ai.signIn($event)" />
647
- <SignIn v-else @done="$ai.signIn($event)" />
646
+ <SignIn @done="$ai.signIn($event)" />
648
647
  </div>
649
648
  <!-- Welcome message when no thread is selected -->
650
649
  <div v-else-if="!currentThread" class="text-center py-12">