chore: format

This commit is contained in:
Timothy Jaeryang Baek
2026-06-01 13:56:55 -07:00
parent 1f49ebf431
commit 6fce92aa12
139 changed files with 92569 additions and 3249 deletions
+1 -1
View File
@@ -426,7 +426,7 @@ async def get_current_user_by_api_key(request, api_key: str):
allowed_paths = [
path.strip() for path in str(request.app.state.config.API_KEYS_ALLOWED_ENDPOINTS).split(',') if path.strip()
]
request_path = request.scope["path"] # Use raw ASGI path — not spoofable via Host header (CVE-2026-48710)
request_path = request.scope['path'] # Use raw ASGI path — not spoofable via Host header (CVE-2026-48710)
is_allowed = any(request_path == allowed or request_path.startswith(allowed + '/') for allowed in allowed_paths)
if not is_allowed:
raise HTTPException(
+1 -5
View File
@@ -191,11 +191,7 @@ async def get_image_base64_from_file_id(id: str, user=None) -> Optional[str]:
# Owner, admin, and explicit read-grant holders are allowed.
if user is None:
return None
if (
file.user_id != user.id
and user.role != 'admin'
and not await has_access_to_file(file.id, 'read', user)
):
if file.user_id != user.id and user.role != 'admin' and not await has_access_to_file(file.id, 'read', user):
return None
try:
-1
View File
@@ -41,4 +41,3 @@ def get_custom_headers(custom_headers: dict, user=None, metadata: dict = None) -
parsed_headers[key] = value
return parsed_headers
+1 -3
View File
@@ -67,9 +67,7 @@ def _json_sink(message: 'Message') -> None:
log_entry['error'] = {
'type': exc.type.__name__ if exc.type else None,
'message': str(exc.value) if exc.value else None,
'stacktrace': ''.join(
traceback.format_exception(exc.type, exc.value, exc.traceback)
).rstrip(),
'stacktrace': ''.join(traceback.format_exception(exc.type, exc.value, exc.traceback)).rstrip(),
}
sys.stdout.write(json.dumps(log_entry, ensure_ascii=False, default=str) + '\n')
+5 -1
View File
@@ -11,7 +11,11 @@ from mcp import ClientSession
from mcp.client.auth import OAuthClientProvider, TokenStorage
from mcp.client.streamable_http import streamablehttp_client
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
from open_webui.env import AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL, AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER, MCP_INITIALIZE_TIMEOUT
from open_webui.env import (
AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL,
AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER,
MCP_INITIALIZE_TIMEOUT,
)
def _build_httpx_client(headers=None, timeout=None, auth=None, verify=True):
+23 -19
View File
@@ -2257,7 +2257,6 @@ def strip_skill_mentions(messages: list[dict]) -> None:
part['text'] = strip_re.sub(r'\1', text).strip()
async def connect_mcp_server(
request,
server_id: str,
@@ -2271,10 +2270,7 @@ async def connect_mcp_server(
"""
mcp_server_connection = None
for server_connection in request.app.state.config.TOOL_SERVER_CONNECTIONS:
if (
server_connection.get('type', '') == 'mcp'
and server_connection.get('info', {}).get('id') == server_id
):
if server_connection.get('type', '') == 'mcp' and server_connection.get('info', {}).get('id') == server_id:
mcp_server_connection = server_connection
break
@@ -2287,8 +2283,12 @@ async def connect_mcp_server(
return None
headers, _ = await build_tool_server_headers(
mcp_server_connection, request, user,
server_id=server_id, metadata=metadata, extra_params=extra_params,
mcp_server_connection,
request,
user,
server_id=server_id,
metadata=metadata,
extra_params=extra_params,
)
client = MCPClient()
@@ -2297,18 +2297,13 @@ async def connect_mcp_server(
headers=headers if headers else None,
)
function_name_filter_list = mcp_server_connection.get('config', {}).get(
'function_name_filter_list', ''
)
function_name_filter_list = mcp_server_connection.get('config', {}).get('function_name_filter_list', '')
if isinstance(function_name_filter_list, str):
function_name_filter_list = function_name_filter_list.split(',')
tool_specs = await client.list_tool_specs()
if function_name_filter_list:
tool_specs = [
spec for spec in tool_specs
if is_string_allowed(spec['name'], function_name_filter_list)
]
tool_specs = [spec for spec in tool_specs if is_string_allowed(spec['name'], function_name_filter_list)]
return client, tool_specs
@@ -2730,10 +2725,14 @@ async def process_chat_payload(request, form_data, user, metadata, model):
for tool_id in tool_ids:
if tool_id.startswith('server:mcp:'):
try:
server_id = tool_id[len('server:mcp:'):]
server_id = tool_id[len('server:mcp:') :]
result = await connect_mcp_server(
request, server_id, user, metadata, extra_params,
request,
server_id,
user,
metadata,
extra_params,
)
if result is None:
continue
@@ -2742,6 +2741,7 @@ async def process_chat_payload(request, form_data, user, metadata, model):
mcp_clients[server_id] = client
for tool_spec in tool_specs:
async def make_tool_function(client, function_name):
async def tool_function(**kwargs):
return await client.call_tool(
@@ -3392,7 +3392,9 @@ async def outlet_filter_handler(ctx):
if outlet_message_id and outlet_message_id in messages_map:
original_message = messages_map[outlet_message_id]
content_changed = original_message.get('content') != message.get('content')
output_changed = message.get('output') and message.get('output') != original_message.get('output')
output_changed = message.get('output') and message.get('output') != original_message.get(
'output'
)
if content_changed or output_changed:
# If output was modified, re-derive content from it
new_content = message.get('content', original_message.get('content', ''))
@@ -3874,7 +3876,7 @@ async def streaming_chat_response_handler(response, ctx):
# Mirror the five gates from utils/tools.py get_builtin_tools so the
# legacy XML-tag path enforces the same authz as native FC.
features = metadata.get('features', {}) or {}
model_capabilities = (model.get('info', {}).get('meta', {}).get('capabilities') or {})
model_capabilities = model.get('info', {}).get('meta', {}).get('capabilities') or {}
builtin_tools_meta = model.get('info', {}).get('meta', {}).get('builtinTools', {})
DETECT_CODE_INTERPRETER = (
bool(features.get('code_interpreter'))
@@ -4401,7 +4403,9 @@ async def streaming_chat_response_handler(response, ctx):
if end:
break
if ENABLE_REALTIME_CHAT_SAVE and not metadata.get('chat_id', '').startswith('channel:'):
if ENABLE_REALTIME_CHAT_SAVE and not metadata.get('chat_id', '').startswith(
'channel:'
):
# Save message in the database
await Chats.upsert_message_to_chat_by_id_and_message_id(
metadata['chat_id'],
+3 -9
View File
@@ -141,9 +141,7 @@ def reconcile_tool_pairs(messages: list[dict]) -> list[dict]:
Well-formed output is unaffected: every id pairs, so nothing is stripped.
"""
completed_tool_call_ids = {
message['tool_call_id']
for message in messages
if message.get('role') == 'tool' and message.get('tool_call_id')
message['tool_call_id'] for message in messages if message.get('role') == 'tool' and message.get('tool_call_id')
}
requested_tool_call_ids = {
tool_call['id']
@@ -167,9 +165,7 @@ def reconcile_tool_pairs(messages: list[dict]) -> list[dict]:
# Keep only tool_calls whose id received a tool-role response.
valid_tool_calls = [
tool_call
for tool_call in message['tool_calls']
if tool_call.get('id') in completed_tool_call_ids
tool_call for tool_call in message['tool_calls'] if tool_call.get('id') in completed_tool_call_ids
]
if valid_tool_calls:
@@ -181,9 +177,7 @@ def reconcile_tool_pairs(messages: list[dict]) -> list[dict]:
content = message.get('content', '')
has_meaningful_content = content.strip() if isinstance(content, str) else content
if has_meaningful_content or message.get('reasoning_content'):
reconciled_messages.append(
{key: value for key, value in message.items() if key != 'tool_calls'}
)
reconciled_messages.append({key: value for key, value in message.items() if key != 'tool_calls'})
return reconciled_messages
+3 -1
View File
@@ -375,7 +375,9 @@ async def get_protected_resource_metadata(server_url: str) -> ProtectedResourceM
except Exception as e:
log.debug(f'MCP Protected Resource discovery failed: {e}')
return ProtectedResourceMetadata(resource=resource, authorization_servers=authorization_servers, scopes_supported=scopes)
return ProtectedResourceMetadata(
resource=resource, authorization_servers=authorization_servers, scopes_supported=scopes
)
def _build_well_known_urls(server_url: str) -> list[str]:
+32 -37
View File
@@ -29,12 +29,12 @@ from open_webui.env import (
log = logging.getLogger(__name__)
_ACCEPTED_SCHEMES = frozenset({"redis", "rediss"})
_ACCEPTED_SCHEMES = frozenset({'redis', 'rediss'})
_SENTINEL_RETRYABLE = (
_redis_sync.exceptions.ConnectionError,
_redis_sync.exceptions.ReadOnlyError,
)
_FACTORY_METHODS = frozenset({"pipeline", "pubsub", "monitor", "client", "transaction"})
_FACTORY_METHODS = frozenset({'pipeline', 'pubsub', 'monitor', 'client', 'transaction'})
_CONNECTION_POOL: dict[tuple, Any] = {}
@@ -42,17 +42,16 @@ def parse_redis_url(url: str) -> dict[str, Any]:
"""Break a ``redis://`` URL into its parts: service, port, db, username, password."""
parts: ParseResult = urlparse(url)
if parts.scheme not in _ACCEPTED_SCHEMES:
raise ValueError(
f"Invalid Redis URL scheme '{parts.scheme}'; expected 'redis' or 'rediss'."
)
raise ValueError(f"Invalid Redis URL scheme '{parts.scheme}'; expected 'redis' or 'rediss'.")
return {
"service": parts.hostname or "mymaster",
"port": parts.port or 6379,
"db": int(parts.path.lstrip("/") or "0"),
"username": parts.username or None,
"password": parts.password or None,
'service': parts.hostname or 'mymaster',
'port': parts.port or 6379,
'db': int(parts.path.lstrip('/') or '0'),
'username': parts.username or None,
'password': parts.password or None,
}
parse_redis_service_url = parse_redis_url
@@ -64,11 +63,7 @@ def get_sentinels_from_env(
if not hosts_csv:
return []
resolved_port = int(port) if port else 26379
return [
(host.strip(), resolved_port)
for host in hosts_csv.split(",")
if host.strip()
]
return [(host.strip(), resolved_port) for host in hosts_csv.split(',') if host.strip()]
def build_sentinel_url(
@@ -82,13 +77,11 @@ def build_sentinel_url(
``hosts_csv`` is a comma-separated list of sentinel hostnames.
"""
cfg = parse_redis_url(base_url)
auth = ""
if cfg["username"] or cfg["password"]:
auth = f"{cfg['username'] or ''}:{cfg['password'] or ''}@"
nodes = ",".join(
f"{host.strip()}:{port}" for host in hosts_csv.split(",") if host.strip()
)
return f"redis+sentinel://{auth}{nodes}/{cfg['db']}/{cfg['service']}"
auth = ''
if cfg['username'] or cfg['password']:
auth = f'{cfg["username"] or ""}:{cfg["password"] or ""}@'
nodes = ','.join(f'{host.strip()}:{port}' for host in hosts_csv.split(',') if host.strip())
return f'redis+sentinel://{auth}{nodes}/{cfg["db"]}/{cfg["service"]}'
def get_redis_client(async_mode: bool = False) -> Any | None:
@@ -107,7 +100,7 @@ def get_redis_client(async_mode: bool = False) -> Any | None:
async_mode=async_mode,
)
except Exception:
log.debug("Could not establish Redis connection", exc_info=True)
log.debug('Could not establish Redis connection', exc_info=True)
return None
@@ -157,7 +150,7 @@ class SentinelRedisProxy:
def _log_retry(self, exc: Exception, attempt: int) -> None:
log.debug(
"Sentinel failover (%s) — retry %d/%d",
'Sentinel failover (%s) — retry %d/%d',
type(exc).__name__,
attempt + 1,
REDIS_SENTINEL_MAX_RETRY_COUNT,
@@ -165,7 +158,7 @@ class SentinelRedisProxy:
def _log_exhausted(self, exc: Exception) -> None:
log.error(
"Redis operation failed after %d retries: %s",
'Redis operation failed after %d retries: %s',
REDIS_SENTINEL_MAX_RETRY_COUNT,
exc,
)
@@ -254,11 +247,11 @@ def _socket_options() -> dict[str, Any]:
"""Collect optional socket-level kwargs once instead of repeating them."""
opts: dict[str, Any] = {}
if REDIS_SOCKET_CONNECT_TIMEOUT is not None:
opts["socket_connect_timeout"] = REDIS_SOCKET_CONNECT_TIMEOUT
opts['socket_connect_timeout'] = REDIS_SOCKET_CONNECT_TIMEOUT
if REDIS_SOCKET_KEEPALIVE:
opts["socket_keepalive"] = True
opts['socket_keepalive'] = True
if REDIS_HEALTH_CHECK_INTERVAL:
opts["health_check_interval"] = REDIS_HEALTH_CHECK_INTERVAL
opts['health_check_interval'] = REDIS_HEALTH_CHECK_INTERVAL
return opts
@@ -273,15 +266,15 @@ def _build_sentinel(
cfg = parse_redis_url(url)
sentinel = redis_module.sentinel.Sentinel(
sentinels,
port=cfg["port"],
db=cfg["db"],
username=cfg["username"],
password=cfg["password"],
port=cfg['port'],
db=cfg['db'],
username=cfg['username'],
password=cfg['password'],
decode_responses=decode_responses,
socket_connect_timeout=REDIS_SOCKET_CONNECT_TIMEOUT,
**{k: v for k, v in _socket_options().items() if k != "socket_connect_timeout"},
**{k: v for k, v in _socket_options().items() if k != 'socket_connect_timeout'},
)
return SentinelRedisProxy(sentinel, cfg["service"], async_mode=async_mode)
return SentinelRedisProxy(sentinel, cfg['service'], async_mode=async_mode)
def get_redis_connection(
@@ -320,12 +313,14 @@ def get_redis_connection(
connection = _build_sentinel(redis_mod, redis_url, redis_sentinels, decode_responses, async_mode)
elif redis_cluster:
if not redis_url:
raise ValueError("Redis URL is required for cluster mode.")
raise ValueError('Redis URL is required for cluster mode.')
connection = redis_mod.cluster.RedisCluster.from_url(
redis_url, decode_responses=decode_responses, **extra,
redis_url,
decode_responses=decode_responses,
**extra,
)
elif redis_url:
factory = getattr(redis_mod, "from_url", None) or redis_mod.Redis.from_url
factory = getattr(redis_mod, 'from_url', None) or redis_mod.Redis.from_url
connection = factory(redis_url, decode_responses=decode_responses, **extra)
_CONNECTION_POOL[cache_key] = connection
+20 -8
View File
@@ -376,8 +376,12 @@ async def get_tools(request: Request, tool_ids: list[str], user: UserModel, extr
metadata = extra_params.get('__metadata__', {})
headers, cookies = await build_tool_server_headers(
tool_server_connection, request, user,
server_id=server_id, metadata=metadata, extra_params=extra_params,
tool_server_connection,
request,
user,
server_id=server_id,
metadata=metadata,
extra_params=extra_params,
)
headers.setdefault('Content-Type', 'application/json')
@@ -484,7 +488,9 @@ async def get_builtin_tools(
builtin_functions.append(query_knowledge_bases)
builtin_functions.append(search_knowledge_bases)
elif model_knowledge:
builtin_functions.extend([list_knowledge, search_knowledge_files, grep_knowledge_files, query_knowledge_files])
builtin_functions.extend(
[list_knowledge, search_knowledge_files, grep_knowledge_files, query_knowledge_files]
)
knowledge_types = {item.get('type') for item in model_knowledge}
if 'file' in knowledge_types or 'collection' in knowledge_types:
@@ -492,11 +498,17 @@ async def get_builtin_tools(
if 'note' in knowledge_types:
builtin_functions.append(view_note)
else:
builtin_functions.extend([
list_knowledge_bases, search_knowledge_bases, query_knowledge_bases,
grep_knowledge_files, search_knowledge_files, query_knowledge_files,
view_knowledge_file,
])
builtin_functions.extend(
[
list_knowledge_bases,
search_knowledge_bases,
query_knowledge_bases,
grep_knowledge_files,
search_knowledge_files,
query_knowledge_files,
view_knowledge_file,
]
)
# Chats tools - search and fetch user's chat history
if is_builtin_tool_enabled('chats'):
+1 -2
View File
@@ -76,8 +76,7 @@ def validate_profile_image_url(url: str) -> str:
if _SAFE_DATA_URI_RE.match(url):
if PROFILE_IMAGE_MAX_DATA_URI_SIZE and len(url) > PROFILE_IMAGE_MAX_DATA_URI_SIZE:
raise ValueError(
f'Invalid profile image URL: data URI exceeds the '
f'{PROFILE_IMAGE_MAX_DATA_URI_SIZE}-byte limit.'
f'Invalid profile image URL: data URI exceeds the {PROFILE_IMAGE_MAX_DATA_URI_SIZE}-byte limit.'
)
return url