This commit is contained in:
Timothy Jaeryang Baek 2026-06-29 13:03:14 -05:00
parent 7ea7680f56
commit 517cd8d102
45 changed files with 1293 additions and 969 deletions

View file

@ -405,14 +405,28 @@ CODE_INTERPRETER_PROMPT_TEMPLATE = os.getenv('CODE_INTERPRETER_PROMPT_TEMPLATE',
CODE_INTERPRETER_JUPYTER_URL = os.getenv('CODE_INTERPRETER_JUPYTER_URL', os.getenv('CODE_EXECUTION_JUPYTER_URL', ''))
CODE_INTERPRETER_JUPYTER_AUTH = os.getenv( 'CODE_INTERPRETER_JUPYTER_AUTH', os.getenv('CODE_EXECUTION_JUPYTER_AUTH', ''), )
CODE_INTERPRETER_JUPYTER_AUTH = os.getenv(
'CODE_INTERPRETER_JUPYTER_AUTH',
os.getenv('CODE_EXECUTION_JUPYTER_AUTH', ''),
)
CODE_INTERPRETER_JUPYTER_AUTH_TOKEN = os.getenv( 'CODE_INTERPRETER_JUPYTER_AUTH_TOKEN', os.getenv('CODE_EXECUTION_JUPYTER_AUTH_TOKEN', ''), )
CODE_INTERPRETER_JUPYTER_AUTH_TOKEN = os.getenv(
'CODE_INTERPRETER_JUPYTER_AUTH_TOKEN',
os.getenv('CODE_EXECUTION_JUPYTER_AUTH_TOKEN', ''),
)
CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD = os.getenv( 'CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD', os.getenv('CODE_EXECUTION_JUPYTER_AUTH_PASSWORD', ''), )
CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD = os.getenv(
'CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD',
os.getenv('CODE_EXECUTION_JUPYTER_AUTH_PASSWORD', ''),
)
CODE_INTERPRETER_JUPYTER_TIMEOUT = int( os.getenv( 'CODE_INTERPRETER_JUPYTER_TIMEOUT', os.getenv('CODE_EXECUTION_JUPYTER_TIMEOUT', '60'), ) )
CODE_INTERPRETER_JUPYTER_TIMEOUT = int(
os.getenv(
'CODE_INTERPRETER_JUPYTER_TIMEOUT',
os.getenv('CODE_EXECUTION_JUPYTER_TIMEOUT', '60'),
)
)
CODE_INTERPRETER_BLOCKED_MODULES = [
library.strip() for library in os.getenv('CODE_INTERPRETER_BLOCKED_MODULES', '').split(',') if library.strip()
@ -834,7 +848,9 @@ DATALAB_MARKER_PAGINATE = os.getenv('DATALAB_MARKER_PAGINATE', 'false').lower()
DATALAB_MARKER_STRIP_EXISTING_OCR = os.getenv('DATALAB_MARKER_STRIP_EXISTING_OCR', 'false').lower() == 'true'
DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION = os.getenv('DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION', 'false').lower() == 'true'
DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION = (
os.getenv('DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION', 'false').lower() == 'true'
)
DATALAB_MARKER_FORMAT_LINES = os.getenv('DATALAB_MARKER_FORMAT_LINES', 'false').lower() == 'true'
@ -910,7 +926,9 @@ RAG_HYBRID_BM25_WEIGHT = float(os.getenv('RAG_HYBRID_BM25_WEIGHT', '0.5'))
ENABLE_RAG_HYBRID_SEARCH = os.getenv('ENABLE_RAG_HYBRID_SEARCH', '').lower() == 'true'
ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS = os.getenv('ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS', 'False').lower() == 'true'
ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS = (
os.getenv('ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS', 'False').lower() == 'true'
)
RAG_FULL_CONTEXT = os.getenv('RAG_FULL_CONTEXT', 'False').lower() == 'true'
@ -920,12 +938,18 @@ RAG_FILE_MAX_SIZE = int(os.getenv('RAG_FILE_MAX_SIZE')) if os.getenv('RAG_FILE_M
RAG_FILE_CONTENT_SEARCH_MAX_CHARS = int(os.getenv('RAG_FILE_CONTENT_SEARCH_MAX_CHARS', str(64 * 1024 * 1024)))
FILE_IMAGE_COMPRESSION_WIDTH = int(os.getenv('FILE_IMAGE_COMPRESSION_WIDTH')) if os.getenv('FILE_IMAGE_COMPRESSION_WIDTH') else None
FILE_IMAGE_COMPRESSION_WIDTH = (
int(os.getenv('FILE_IMAGE_COMPRESSION_WIDTH')) if os.getenv('FILE_IMAGE_COMPRESSION_WIDTH') else None
)
FILE_IMAGE_COMPRESSION_HEIGHT = int(os.getenv('FILE_IMAGE_COMPRESSION_HEIGHT')) if os.getenv('FILE_IMAGE_COMPRESSION_HEIGHT') else None
FILE_IMAGE_COMPRESSION_HEIGHT = (
int(os.getenv('FILE_IMAGE_COMPRESSION_HEIGHT')) if os.getenv('FILE_IMAGE_COMPRESSION_HEIGHT') else None
)
RAG_ALLOWED_FILE_EXTENSIONS = [ext.strip() for ext in os.getenv('RAG_ALLOWED_FILE_EXTENSIONS', '').split(',') if ext.strip()]
RAG_ALLOWED_FILE_EXTENSIONS = [
ext.strip() for ext in os.getenv('RAG_ALLOWED_FILE_EXTENSIONS', '').split(',') if ext.strip()
]
RAG_EMBEDDING_ENGINE = os.getenv('RAG_EMBEDDING_ENGINE', '')
@ -944,7 +968,9 @@ RAG_EMBEDDING_MODEL_AUTO_UPDATE = (
RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE = os.getenv('RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE', 'True').lower() == 'true'
RAG_EMBEDDING_BATCH_SIZE = int(os.getenv('RAG_EMBEDDING_BATCH_SIZE') or os.getenv('RAG_EMBEDDING_OPENAI_BATCH_SIZE', '1'))
RAG_EMBEDDING_BATCH_SIZE = int(
os.getenv('RAG_EMBEDDING_BATCH_SIZE') or os.getenv('RAG_EMBEDDING_OPENAI_BATCH_SIZE', '1')
)
ENABLE_ASYNC_EMBEDDING = os.getenv('ENABLE_ASYNC_EMBEDDING', 'True').lower() == 'true'
@ -1081,7 +1107,9 @@ WEB_SEARCH_CONFIRMATION_CONTENT = os.getenv(
WEB_SEARCH_ENGINE = os.getenv('WEB_SEARCH_ENGINE', '')
BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = os.getenv('BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL', 'False').lower() == 'true'
BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = (
os.getenv('BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL', 'False').lower() == 'true'
)
BYPASS_WEB_SEARCH_WEB_LOADER = os.getenv('BYPASS_WEB_SEARCH_WEB_LOADER', 'False').lower() == 'true'
@ -1105,7 +1133,9 @@ WEB_SEARCH_DOMAIN_FILTER_LIST = web_search_domain_filter_list
WEB_SEARCH_CONCURRENT_REQUESTS = int(os.getenv('WEB_SEARCH_CONCURRENT_REQUESTS', '0'))
WEB_FETCH_MAX_CONTENT_LENGTH = int(os.getenv('WEB_FETCH_MAX_CONTENT_LENGTH')) if os.getenv('WEB_FETCH_MAX_CONTENT_LENGTH') else None
WEB_FETCH_MAX_CONTENT_LENGTH = (
int(os.getenv('WEB_FETCH_MAX_CONTENT_LENGTH')) if os.getenv('WEB_FETCH_MAX_CONTENT_LENGTH') else None
)
WEB_LOADER_ENGINE = os.getenv('WEB_LOADER_ENGINE', '')
@ -1230,7 +1260,7 @@ YANDEX_WEB_SEARCH_API_KEY = os.getenv('YANDEX_WEB_SEARCH_API_KEY', '')
YANDEX_WEB_SEARCH_CONFIG = os.getenv('YANDEX_WEB_SEARCH_CONFIG', '')
YOUCOM_API_KEY = os.getenv('YOUCOM_API_KEY', os.getenv('YDC_API_KEY',''))
YOUCOM_API_KEY = os.getenv('YOUCOM_API_KEY', os.getenv('YDC_API_KEY', ''))
LINKUP_API_KEY = os.getenv('LINKUP_API_KEY', '')
@ -1483,9 +1513,20 @@ AUDIO_STT_ENGINE = os.getenv('AUDIO_STT_ENGINE', '')
AUDIO_STT_MODEL = os.getenv('AUDIO_STT_MODEL', '')
AUDIO_STT_SUPPORTED_CONTENT_TYPES = [ content_type.strip() for content_type in os.getenv('AUDIO_STT_SUPPORTED_CONTENT_TYPES', '').split(',') if content_type.strip() ]
AUDIO_STT_SUPPORTED_CONTENT_TYPES = [
content_type.strip()
for content_type in os.getenv('AUDIO_STT_SUPPORTED_CONTENT_TYPES', '').split(',')
if content_type.strip()
]
AUDIO_STT_ALLOWED_EXTENSIONS = [ ext.strip() for ext in os.getenv( 'AUDIO_STT_ALLOWED_EXTENSIONS', 'mp3,wav,m4a,webm,ogg,flac,mp4,mpga,mpeg', ).split(',') if ext.strip() ]
AUDIO_STT_ALLOWED_EXTENSIONS = [
ext.strip()
for ext in os.getenv(
'AUDIO_STT_ALLOWED_EXTENSIONS',
'mp3,wav,m4a,webm,ogg,flac,mp4,mpga,mpeg',
).split(',')
if ext.strip()
]
AUDIO_STT_AZURE_API_KEY = os.getenv('AUDIO_STT_AZURE_API_KEY', '')
@ -1530,7 +1571,9 @@ AUDIO_TTS_AZURE_SPEECH_REGION = os.getenv('AUDIO_TTS_AZURE_SPEECH_REGION', '')
AUDIO_TTS_AZURE_SPEECH_BASE_URL = os.getenv('AUDIO_TTS_AZURE_SPEECH_BASE_URL', '')
AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT = os.getenv('AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT', 'audio-24khz-160kbitrate-mono-mp3')
AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT = os.getenv(
'AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT', 'audio-24khz-160kbitrate-mono-mp3'
)
AUDIO_TTS_MISTRAL_API_KEY = os.getenv('AUDIO_TTS_MISTRAL_API_KEY', '')
@ -1728,9 +1771,7 @@ USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING = (
os.getenv('USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true'
)
USER_PERMISSIONS_FOLDERS_ALLOW_SHARING = (
os.getenv('USER_PERMISSIONS_FOLDERS_ALLOW_SHARING', 'False').lower() == 'true'
)
USER_PERMISSIONS_FOLDERS_ALLOW_SHARING = os.getenv('USER_PERMISSIONS_FOLDERS_ALLOW_SHARING', 'False').lower() == 'true'
USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING = (
@ -2312,7 +2353,13 @@ Responses from models: {{responses}}"""
ENABLE_API_KEYS = os.getenv('ENABLE_API_KEYS', 'False').lower() == 'true'
ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS = os.getenv( 'ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS', os.getenv('ENABLE_API_KEY_ENDPOINT_RESTRICTIONS', 'False'), ).lower() == 'true'
ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS = (
os.getenv(
'ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS',
os.getenv('ENABLE_API_KEY_ENDPOINT_RESTRICTIONS', 'False'),
).lower()
== 'true'
)
API_KEYS_ALLOWED_ENDPOINTS = os.getenv('API_KEYS_ALLOWED_ENDPOINTS', os.getenv('API_KEY_ALLOWED_ENDPOINTS', ''))
@ -2368,7 +2415,10 @@ MICROSOFT_CLIENT_TENANT_ID = os.getenv('MICROSOFT_CLIENT_TENANT_ID', '')
MICROSOFT_CLIENT_LOGIN_BASE_URL = os.getenv('MICROSOFT_CLIENT_LOGIN_BASE_URL', 'https://login.microsoftonline.com')
MICROSOFT_CLIENT_PICTURE_URL = os.getenv( 'MICROSOFT_CLIENT_PICTURE_URL', 'https://graph.microsoft.com/v1.0/me/photo/$value', )
MICROSOFT_CLIENT_PICTURE_URL = os.getenv(
'MICROSOFT_CLIENT_PICTURE_URL',
'https://graph.microsoft.com/v1.0/me/photo/$value',
)
MICROSOFT_OAUTH_SCOPE = os.getenv('MICROSOFT_OAUTH_SCOPE', 'openid email profile')
@ -2441,9 +2491,15 @@ OAUTH_ROLES_CLAIM = os.getenv('OAUTH_ROLES_CLAIM', 'roles')
OAUTH_ROLES_SEPARATOR = os.getenv('OAUTH_ROLES_SEPARATOR', ',')
OAUTH_ALLOWED_ROLES = [ role.strip() for role in os.getenv('OAUTH_ALLOWED_ROLES', f'user{OAUTH_ROLES_SEPARATOR}admin').split(OAUTH_ROLES_SEPARATOR) if role ]
OAUTH_ALLOWED_ROLES = [
role.strip()
for role in os.getenv('OAUTH_ALLOWED_ROLES', f'user{OAUTH_ROLES_SEPARATOR}admin').split(OAUTH_ROLES_SEPARATOR)
if role
]
OAUTH_ADMIN_ROLES = [role.strip() for role in os.getenv('OAUTH_ADMIN_ROLES', 'admin').split(OAUTH_ROLES_SEPARATOR) if role]
OAUTH_ADMIN_ROLES = [
role.strip() for role in os.getenv('OAUTH_ADMIN_ROLES', 'admin').split(OAUTH_ROLES_SEPARATOR) if role
]
OAUTH_ALLOWED_DOMAINS = [domain.strip() for domain in os.getenv('OAUTH_ALLOWED_DOMAINS', '*').split(',')]
@ -2540,11 +2596,7 @@ def load_oauth_providers():
'sub_claim': 'id',
}
if (
OAUTH_CLIENT_ID
and (OAUTH_CLIENT_SECRET or OAUTH_CODE_CHALLENGE_METHOD)
and OPENID_PROVIDER_URL
):
if OAUTH_CLIENT_ID and (OAUTH_CLIENT_SECRET or OAUTH_CODE_CHALLENGE_METHOD) and OPENID_PROVIDER_URL:
def oidc_oauth_register(oauth: OAuth):
client_kwargs = {

View file

@ -599,9 +599,7 @@ except (ValueError, TypeError):
# SSL verification for tool server connections specifically.
# Accepts "True", "False", or a path to a CA bundle file.
# When "True", falls back to AIOHTTP_CLIENT_SSL_CERT_FILE if set.
AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL = _parse_ssl_env(
os.getenv('AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL', 'True')
)
AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL = _parse_ssl_env(os.getenv('AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL', 'True'))
AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER = os.getenv('AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER', '')

View file

@ -42,177 +42,581 @@ class EventDefinition(BaseModel):
class EventDefinitions(BaseModel):
model_config = ConfigDict(frozen=True)
SYSTEM_STARTUP_STARTED: EventDefinition = EventDefinition(name='system.startup.started', description='Application startup began.', message='Startup started')
SYSTEM_STARTUP_COMPLETED: EventDefinition = EventDefinition(name='system.startup.completed', description='Application startup completed.', message='Startup completed')
SYSTEM_SHUTDOWN_STARTED: EventDefinition = EventDefinition(name='system.shutdown.started', description='Application shutdown began.', message='Shutdown started')
SYSTEM_SHUTDOWN_COMPLETED: EventDefinition = EventDefinition(name='system.shutdown.completed', description='Application shutdown completed.', message='Shutdown completed')
CONFIG_IMPORTED: EventDefinition = EventDefinition(name='config.imported', description='Configuration was imported.', message='Config imported')
CONFIG_UPDATED: EventDefinition = EventDefinition(name='config.updated', description='Configuration was updated.', message='Config updated')
CONFIG_WEBHOOK_UPDATED: EventDefinition = EventDefinition(name='config.webhook.updated', description='Event webhook configuration was updated.', message='Webhook configuration updated')
CONFIG_CONNECTIONS_UPDATED: EventDefinition = EventDefinition(name='config.connections.updated', description='Connection configuration was updated.', message='Config Connections updated')
CONFIG_TOOL_SERVERS_UPDATED: EventDefinition = EventDefinition(name='config.tool_servers.updated', description='Tool server configuration was updated.', message='Config Tool Servers updated')
CONFIG_TERMINAL_SERVERS_UPDATED: EventDefinition = EventDefinition(name='config.terminal_servers.updated', description='Terminal server configuration was updated.', message='Config Terminal Servers updated')
CONFIG_CODE_EXECUTION_UPDATED: EventDefinition = EventDefinition(name='config.code_execution.updated', description='Code execution configuration was updated.', message='Config Code Execution updated')
CONFIG_MODELS_UPDATED: EventDefinition = EventDefinition(name='config.models.updated', description='Model configuration was updated.', message='Config Models updated')
CONFIG_BANNERS_UPDATED: EventDefinition = EventDefinition(name='config.banners.updated', description='Banner configuration was updated.', message='Config Banners updated')
CONFIG_SUGGESTIONS_UPDATED: EventDefinition = EventDefinition(name='config.suggestions.updated', description='Suggestion configuration was updated.', message='Config Suggestions updated')
AUTH_SIGNUP: EventDefinition = EventDefinition(name='auth.signup', description='A user account was created through signup.', message='User signed up')
AUTH_LOGIN: EventDefinition = EventDefinition(name='auth.login', description='A user successfully logged in.', message='User logged in')
AUTH_LOGOUT: EventDefinition = EventDefinition(name='auth.logout', description='A user logged out.', message='User logged out')
AUTH_PASSWORD_CHANGED: EventDefinition = EventDefinition(name='auth.password_changed', description='A user password was changed.', message='Password changed')
AUTH_API_KEY_CREATED: EventDefinition = EventDefinition(name='auth.api_key.created', description='A user API key was created.', message='API key created')
AUTH_API_KEY_DELETED: EventDefinition = EventDefinition(name='auth.api_key.deleted', description='A user API key was deleted.', message='API key deleted')
AUTH_OAUTH_SESSION_DELETED: EventDefinition = EventDefinition(name='auth.oauth_session.deleted', description='An OAuth session was deleted.', message='OAuth session deleted')
USER_CREATED: EventDefinition = EventDefinition(name='user.created', description='A user account was created.', message='User created')
USER_UPDATED: EventDefinition = EventDefinition(name='user.updated', description='A user account was updated.', message='User updated')
USER_DELETED: EventDefinition = EventDefinition(name='user.deleted', description='A user account was deleted.', message='User deleted')
USER_ROLE_UPDATED: EventDefinition = EventDefinition(name='user.role_updated', description='A user role was updated.', message='User role updated')
USER_STATUS_UPDATED: EventDefinition = EventDefinition(name='user.status_updated', description='A user status was updated.', message='User status updated')
USER_SETTINGS_UPDATED: EventDefinition = EventDefinition(name='user.settings_updated', description='A user settings object was updated.', message='User settings updated')
USER_PROFILE_UPDATED: EventDefinition = EventDefinition(name='user.profile_updated', description='A user profile was updated.', message='User profile updated')
USER_PERMISSIONS_UPDATED: EventDefinition = EventDefinition(name='user.permissions_updated', description='A user permissions object was updated.', message='User permissions updated')
GROUP_CREATED: EventDefinition = EventDefinition(name='group.created', description='A group was created.', message='Group created')
GROUP_UPDATED: EventDefinition = EventDefinition(name='group.updated', description='A group was updated.', message='Group updated')
GROUP_DELETED: EventDefinition = EventDefinition(name='group.deleted', description='A group was deleted.', message='Group deleted')
GROUP_MEMBER_ADDED: EventDefinition = EventDefinition(name='group.member_added', description='A user was added to a group.', message='Group member added')
GROUP_MEMBER_REMOVED: EventDefinition = EventDefinition(name='group.member_removed', description='A user was removed from a group.', message='Group member removed')
CHAT_CREATED: EventDefinition = EventDefinition(name='chat.created', description='A chat was created.', message='Chat created')
CHAT_IMPORTED: EventDefinition = EventDefinition(name='chat.imported', description='A chat was imported.', message='Chat imported')
CHAT_UPDATED: EventDefinition = EventDefinition(name='chat.updated', description='A chat was updated.', message='Chat updated')
CHAT_DELETED: EventDefinition = EventDefinition(name='chat.deleted', description='A chat was deleted.', message='Chat deleted')
CHAT_DELETED_ALL: EventDefinition = EventDefinition(name='chat.deleted_all', description='All chats for a scope were deleted.', message='Chat deleted all')
CHAT_COMPACTED: EventDefinition = EventDefinition(name='chat.compacted', description='A chat was compacted.', message='Chat compacted')
CHAT_PINNED: EventDefinition = EventDefinition(name='chat.pinned', description='A chat was pinned.', message='Chat pinned')
CHAT_UNPINNED: EventDefinition = EventDefinition(name='chat.unpinned', description='A chat was unpinned.', message='Chat unpinned')
CHAT_CLONED: EventDefinition = EventDefinition(name='chat.cloned', description='A chat was cloned.', message='Chat cloned')
CHAT_ARCHIVED: EventDefinition = EventDefinition(name='chat.archived', description='A chat was archived.', message='Chat archived')
CHAT_UNARCHIVED: EventDefinition = EventDefinition(name='chat.unarchived', description='A chat was unarchived.', message='Chat unarchived')
CHAT_SHARED: EventDefinition = EventDefinition(name='chat.shared', description='A chat was shared.', message='Chat shared')
CHAT_UNSHARED: EventDefinition = EventDefinition(name='chat.unshared', description='A chat was unshared.', message='Chat unshared')
CHAT_FOLDER_UPDATED: EventDefinition = EventDefinition(name='chat.folder_updated', description='A chat folder assignment was updated.', message='Chat folder updated')
CHAT_TAG_ADDED: EventDefinition = EventDefinition(name='chat.tag_added', description='A tag was added to a chat.', message='Chat tag added')
CHAT_TAG_REMOVED: EventDefinition = EventDefinition(name='chat.tag_removed', description='A tag was removed from a chat.', message='Chat tag removed')
MESSAGE_CREATED: EventDefinition = EventDefinition(name='message.created', description='A message was created.', message='Message created')
MESSAGE_UPDATED: EventDefinition = EventDefinition(name='message.updated', description='A message was updated.', message='Message updated')
MESSAGE_DELETED: EventDefinition = EventDefinition(name='message.deleted', description='A message was deleted.', message='Message deleted')
MESSAGE_EVENT_RECEIVED: EventDefinition = EventDefinition(name='message.event_received', description='A message-level event was received.', message='Message event received')
MESSAGE_REACTION_ADDED: EventDefinition = EventDefinition(name='message.reaction_added', description='A reaction was added to a message.', message='Message reaction added')
MESSAGE_REACTION_REMOVED: EventDefinition = EventDefinition(name='message.reaction_removed', description='A reaction was removed from a message.', message='Message reaction removed')
MESSAGE_PINNED: EventDefinition = EventDefinition(name='message.pinned', description='A message was pinned.', message='Message pinned')
MESSAGE_UNPINNED: EventDefinition = EventDefinition(name='message.unpinned', description='A message was unpinned.', message='Message unpinned')
CHANNEL_CREATED: EventDefinition = EventDefinition(name='channel.created', description='A channel was created.', message='Channel created')
CHANNEL_UPDATED: EventDefinition = EventDefinition(name='channel.updated', description='A channel was updated.', message='Channel updated')
CHANNEL_DELETED: EventDefinition = EventDefinition(name='channel.deleted', description='A channel was deleted.', message='Channel deleted')
CHANNEL_MEMBER_ADDED: EventDefinition = EventDefinition(name='channel.member_added', description='A member was added to a channel.', message='Channel member added')
CHANNEL_MEMBER_REMOVED: EventDefinition = EventDefinition(name='channel.member_removed', description='A member was removed from a channel.', message='Channel member removed')
CHANNEL_MEMBER_ACTIVE_UPDATED: EventDefinition = EventDefinition(name='channel.member_active_updated', description='A channel member active state was updated.', message='Channel member active updated')
CHANNEL_WEBHOOK_CREATED: EventDefinition = EventDefinition(name='channel.webhook.created', description='A channel incoming webhook was created.', message='Channel Webhook created')
CHANNEL_WEBHOOK_UPDATED: EventDefinition = EventDefinition(name='channel.webhook.updated', description='A channel incoming webhook was updated.', message='Channel Webhook updated')
CHANNEL_WEBHOOK_DELETED: EventDefinition = EventDefinition(name='channel.webhook.deleted', description='A channel incoming webhook was deleted.', message='Channel Webhook deleted')
FILE_UPLOADED: EventDefinition = EventDefinition(name='file.uploaded', description='A file was uploaded.', message='File uploaded')
FILE_CONTENT_UPDATED: EventDefinition = EventDefinition(name='file.content_updated', description='File content was updated.', message='File content updated')
FILE_RENAMED: EventDefinition = EventDefinition(name='file.renamed', description='A file was renamed.', message='File renamed')
FILE_DELETED: EventDefinition = EventDefinition(name='file.deleted', description='A file was deleted.', message='File deleted')
FILE_DELETED_ALL: EventDefinition = EventDefinition(name='file.deleted_all', description='All files for a scope were deleted.', message='File deleted all')
FOLDER_CREATED: EventDefinition = EventDefinition(name='folder.created', description='A folder was created.', message='Folder created')
FOLDER_UPDATED: EventDefinition = EventDefinition(name='folder.updated', description='A folder was updated.', message='Folder updated')
FOLDER_PARENT_UPDATED: EventDefinition = EventDefinition(name='folder.parent_updated', description='A folder parent was updated.', message='Folder parent updated')
FOLDER_ACCESS_UPDATED: EventDefinition = EventDefinition(name='folder.access_updated', description='Folder access was updated.', message='Folder access updated')
FOLDER_DELETED: EventDefinition = EventDefinition(name='folder.deleted', description='A folder was deleted.', message='Folder deleted')
NOTE_CREATED: EventDefinition = EventDefinition(name='note.created', description='A note was created.', message='Note created')
NOTE_UPDATED: EventDefinition = EventDefinition(name='note.updated', description='A note was updated.', message='Note updated')
NOTE_ACCESS_UPDATED: EventDefinition = EventDefinition(name='note.access_updated', description='Note access was updated.', message='Note access updated')
NOTE_PINNED: EventDefinition = EventDefinition(name='note.pinned', description='A note was pinned.', message='Note pinned')
NOTE_UNPINNED: EventDefinition = EventDefinition(name='note.unpinned', description='A note was unpinned.', message='Note unpinned')
NOTE_DELETED: EventDefinition = EventDefinition(name='note.deleted', description='A note was deleted.', message='Note deleted')
MEMORY_CREATED: EventDefinition = EventDefinition(name='memory.created', description='A memory was created.', message='Memory created')
MEMORY_UPDATED: EventDefinition = EventDefinition(name='memory.updated', description='A memory was updated.', message='Memory updated')
MEMORY_DELETED: EventDefinition = EventDefinition(name='memory.deleted', description='A memory was deleted.', message='Memory deleted')
MEMORY_RESET: EventDefinition = EventDefinition(name='memory.reset', description='A memory was reset.', message='Memory reset')
KNOWLEDGE_CREATED: EventDefinition = EventDefinition(name='knowledge.created', description='A knowledge was created.', message='Knowledge created')
KNOWLEDGE_UPDATED: EventDefinition = EventDefinition(name='knowledge.updated', description='A knowledge was updated.', message='Knowledge updated')
KNOWLEDGE_DELETED: EventDefinition = EventDefinition(name='knowledge.deleted', description='A knowledge was deleted.', message='Knowledge deleted')
KNOWLEDGE_RESET: EventDefinition = EventDefinition(name='knowledge.reset', description='A knowledge was reset.', message='Knowledge reset')
KNOWLEDGE_REINDEXED: EventDefinition = EventDefinition(name='knowledge.reindexed', description='A knowledge was reindexed.', message='Knowledge reindexed')
KNOWLEDGE_ACCESS_UPDATED: EventDefinition = EventDefinition(name='knowledge.access_updated', description='Knowledge access was updated.', message='Knowledge access updated')
KNOWLEDGE_FILE_ADDED: EventDefinition = EventDefinition(name='knowledge.file.added', description='A file was added to a knowledge base.', message='Knowledge File added')
KNOWLEDGE_FILE_UPDATED: EventDefinition = EventDefinition(name='knowledge.file.updated', description='A knowledge file was updated.', message='Knowledge File updated')
KNOWLEDGE_FILE_REMOVED: EventDefinition = EventDefinition(name='knowledge.file.removed', description='A file was removed from a knowledge base.', message='Knowledge File removed')
KNOWLEDGE_FILE_MOVED: EventDefinition = EventDefinition(name='knowledge.file.moved', description='A knowledge file was moved.', message='Knowledge File moved')
KNOWLEDGE_DIRECTORY_CREATED: EventDefinition = EventDefinition(name='knowledge.directory.created', description='A knowledge directory was created.', message='Knowledge Directory created')
KNOWLEDGE_DIRECTORY_UPDATED: EventDefinition = EventDefinition(name='knowledge.directory.updated', description='A knowledge directory was updated.', message='Knowledge Directory updated')
KNOWLEDGE_DIRECTORY_DELETED: EventDefinition = EventDefinition(name='knowledge.directory.deleted', description='A knowledge directory was deleted.', message='Knowledge Directory deleted')
KNOWLEDGE_EXTERNAL_CONNECTION_CREATED: EventDefinition = EventDefinition(name='knowledge.external_connection.created', description='A knowledge external connection was created.', message='Knowledge External Connection created')
KNOWLEDGE_EXTERNAL_CONNECTION_UPDATED: EventDefinition = EventDefinition(name='knowledge.external_connection.updated', description='A knowledge external connection was updated.', message='Knowledge External Connection updated')
KNOWLEDGE_EXTERNAL_CONNECTION_DELETED: EventDefinition = EventDefinition(name='knowledge.external_connection.deleted', description='A knowledge external connection was deleted.', message='Knowledge External Connection deleted')
RETRIEVAL_CONTENT_PROCESSED: EventDefinition = EventDefinition(name='retrieval.content.processed', description='Retrieval content was processed.', message='Retrieval Content processed')
RETRIEVAL_COLLECTION_DELETED: EventDefinition = EventDefinition(name='retrieval.collection.deleted', description='A retrieval collection was deleted.', message='Retrieval Collection deleted')
RETRIEVAL_VECTOR_DB_RESET: EventDefinition = EventDefinition(name='retrieval.vector_db.reset', description='The retrieval vector database was reset.', message='Retrieval Vector Db reset')
RETRIEVAL_UPLOADS_RESET: EventDefinition = EventDefinition(name='retrieval.uploads.reset', description='Retrieval uploads were reset.', message='Retrieval Uploads reset')
MODEL_CREATED: EventDefinition = EventDefinition(name='model.created', description='A model was created.', message='Model created')
MODEL_IMPORTED: EventDefinition = EventDefinition(name='model.imported', description='A model was imported.', message='Model imported')
MODEL_SYNCED: EventDefinition = EventDefinition(name='model.synced', description='A model was synced.', message='Model synced')
MODEL_UPDATED: EventDefinition = EventDefinition(name='model.updated', description='A model was updated.', message='Model updated')
MODEL_DELETED: EventDefinition = EventDefinition(name='model.deleted', description='A model was deleted.', message='Model deleted')
MODEL_ENABLED: EventDefinition = EventDefinition(name='model.enabled', description='A model was enabled.', message='Model enabled')
MODEL_DISABLED: EventDefinition = EventDefinition(name='model.disabled', description='A model was disabled.', message='Model disabled')
MODEL_ACCESS_UPDATED: EventDefinition = EventDefinition(name='model.access_updated', description='Model access was updated.', message='Model access updated')
MODEL_PROVIDER_CONFIG_UPDATED: EventDefinition = EventDefinition(name='model.provider_config.updated', description='Model provider configuration was updated.', message='Model Provider Config updated')
MODEL_PROVIDER_MODEL_CREATED: EventDefinition = EventDefinition(name='model.provider_model.created', description='A provider model was created.', message='Provider model created')
MODEL_PROVIDER_MODEL_DELETED: EventDefinition = EventDefinition(name='model.provider_model.deleted', description='A provider model was deleted.', message='Provider model deleted')
FUNCTION_CREATED: EventDefinition = EventDefinition(name='function.created', description='A function was created.', message='Function created')
FUNCTION_UPDATED: EventDefinition = EventDefinition(name='function.updated', description='A function was updated.', message='Function updated')
FUNCTION_DELETED: EventDefinition = EventDefinition(name='function.deleted', description='A function was deleted.', message='Function deleted')
FUNCTION_ENABLED: EventDefinition = EventDefinition(name='function.enabled', description='A function was enabled.', message='Function enabled')
FUNCTION_DISABLED: EventDefinition = EventDefinition(name='function.disabled', description='A function was disabled.', message='Function disabled')
FUNCTION_VALVES_UPDATED: EventDefinition = EventDefinition(name='function.valves_updated', description='Function valves were updated.', message='Function valves updated')
TOOL_CREATED: EventDefinition = EventDefinition(name='tool.created', description='A tool was created.', message='Tool created')
TOOL_UPDATED: EventDefinition = EventDefinition(name='tool.updated', description='A tool was updated.', message='Tool updated')
TOOL_DELETED: EventDefinition = EventDefinition(name='tool.deleted', description='A tool was deleted.', message='Tool deleted')
TOOL_ACCESS_UPDATED: EventDefinition = EventDefinition(name='tool.access_updated', description='Tool access was updated.', message='Tool access updated')
TOOL_VALVES_UPDATED: EventDefinition = EventDefinition(name='tool.valves_updated', description='Tool valves were updated.', message='Tool valves updated')
SKILL_CREATED: EventDefinition = EventDefinition(name='skill.created', description='A skill was created.', message='Skill created')
SKILL_UPDATED: EventDefinition = EventDefinition(name='skill.updated', description='A skill was updated.', message='Skill updated')
SKILL_DELETED: EventDefinition = EventDefinition(name='skill.deleted', description='A skill was deleted.', message='Skill deleted')
SKILL_ENABLED: EventDefinition = EventDefinition(name='skill.enabled', description='A skill was enabled.', message='Skill enabled')
SKILL_DISABLED: EventDefinition = EventDefinition(name='skill.disabled', description='A skill was disabled.', message='Skill disabled')
PROMPT_CREATED: EventDefinition = EventDefinition(name='prompt.created', description='A prompt was created.', message='Prompt created')
PROMPT_UPDATED: EventDefinition = EventDefinition(name='prompt.updated', description='A prompt was updated.', message='Prompt updated')
PROMPT_DELETED: EventDefinition = EventDefinition(name='prompt.deleted', description='A prompt was deleted.', message='Prompt deleted')
PROMPT_ENABLED: EventDefinition = EventDefinition(name='prompt.enabled', description='A prompt was enabled.', message='Prompt enabled')
PROMPT_DISABLED: EventDefinition = EventDefinition(name='prompt.disabled', description='A prompt was disabled.', message='Prompt disabled')
PROMPT_VERSION_UPDATED: EventDefinition = EventDefinition(name='prompt.version_updated', description='A prompt version was updated.', message='Prompt version updated')
PROMPT_ACCESS_UPDATED: EventDefinition = EventDefinition(name='prompt.access_updated', description='Prompt access was updated.', message='Prompt access updated')
PIPELINE_UPLOADED: EventDefinition = EventDefinition(name='pipeline.uploaded', description='A pipeline was uploaded.', message='Pipeline uploaded')
PIPELINE_ADDED: EventDefinition = EventDefinition(name='pipeline.added', description='A pipeline was added.', message='Pipeline added')
PIPELINE_DELETED: EventDefinition = EventDefinition(name='pipeline.deleted', description='A pipeline was deleted.', message='Pipeline deleted')
PIPELINE_VALVES_UPDATED: EventDefinition = EventDefinition(name='pipeline.valves_updated', description='Pipeline valves were updated.', message='Pipeline valves updated')
CALENDAR_CREATED: EventDefinition = EventDefinition(name='calendar.created', description='A calendar was created.', message='Calendar created')
CALENDAR_UPDATED: EventDefinition = EventDefinition(name='calendar.updated', description='A calendar was updated.', message='Calendar updated')
CALENDAR_DELETED: EventDefinition = EventDefinition(name='calendar.deleted', description='A calendar was deleted.', message='Calendar deleted')
CALENDAR_DEFAULT_UPDATED: EventDefinition = EventDefinition(name='calendar.default_updated', description='The default calendar was updated.', message='Calendar default updated')
CALENDAR_EVENT_CREATED: EventDefinition = EventDefinition(name='calendar.event.created', description='A calendar event was created.', message='Calendar Event created')
CALENDAR_EVENT_UPDATED: EventDefinition = EventDefinition(name='calendar.event.updated', description='A calendar event was updated.', message='Calendar Event updated')
CALENDAR_EVENT_DELETED: EventDefinition = EventDefinition(name='calendar.event.deleted', description='A calendar event was deleted.', message='Calendar Event deleted')
CALENDAR_EVENT_RSVP_UPDATED: EventDefinition = EventDefinition(name='calendar.event.rsvp_updated', description='A calendar event RSVP was updated.', message='Calendar Event rsvp updated')
AUTOMATION_CREATED: EventDefinition = EventDefinition(name='automation.created', description='An automation was created.', message='Automation created')
AUTOMATION_UPDATED: EventDefinition = EventDefinition(name='automation.updated', description='An automation was updated.', message='Automation updated')
AUTOMATION_ENABLED: EventDefinition = EventDefinition(name='automation.enabled', description='An automation was enabled.', message='Automation enabled')
AUTOMATION_DISABLED: EventDefinition = EventDefinition(name='automation.disabled', description='An automation was disabled.', message='Automation disabled')
AUTOMATION_DELETED: EventDefinition = EventDefinition(name='automation.deleted', description='An automation was deleted.', message='Automation deleted')
AUTOMATION_RUN_STARTED: EventDefinition = EventDefinition(name='automation.run_started', description='An automation run started.', message='Automation run started')
AUTOMATION_RUN_COMPLETED: EventDefinition = EventDefinition(name='automation.run_completed', description='An automation run completed.', message='Automation run completed')
AUTOMATION_RUN_FAILED: EventDefinition = EventDefinition(name='automation.run_failed', description='An automation run failed.', message='Automation run failed')
FEEDBACK_CREATED: EventDefinition = EventDefinition(name='feedback.created', description='A feedback was created.', message='Feedback created')
FEEDBACK_UPDATED: EventDefinition = EventDefinition(name='feedback.updated', description='A feedback was updated.', message='Feedback updated')
FEEDBACK_DELETED: EventDefinition = EventDefinition(name='feedback.deleted', description='A feedback was deleted.', message='Feedback deleted')
FEEDBACK_DELETED_ALL: EventDefinition = EventDefinition(name='feedback.deleted_all', description='All feedback for a scope was deleted.', message='Feedback deleted all')
IMAGE_GENERATED: EventDefinition = EventDefinition(name='image.generated', description='An image was generated.', message='Image generated')
IMAGE_EDITED: EventDefinition = EventDefinition(name='image.edited', description='An image was edited.', message='Image edited')
AUDIO_SPEECH_REQUESTED: EventDefinition = EventDefinition(name='audio.speech_requested', description='Speech generation was requested.', message='Speech requested')
AUDIO_TRANSCRIPTION_REQUESTED: EventDefinition = EventDefinition(name='audio.transcription_requested', description='Audio transcription was requested.', message='Transcription requested')
TERMINAL_SESSION_OPENED: EventDefinition = EventDefinition(name='terminal.session.opened', description='A terminal session was opened.', message='Terminal Session opened')
TERMINAL_SESSION_CLOSED: EventDefinition = EventDefinition(name='terminal.session.closed', description='A terminal session was closed.', message='Terminal Session closed')
SYSTEM_STARTUP_STARTED: EventDefinition = EventDefinition(
name='system.startup.started', description='Application startup began.', message='Startup started'
)
SYSTEM_STARTUP_COMPLETED: EventDefinition = EventDefinition(
name='system.startup.completed', description='Application startup completed.', message='Startup completed'
)
SYSTEM_SHUTDOWN_STARTED: EventDefinition = EventDefinition(
name='system.shutdown.started', description='Application shutdown began.', message='Shutdown started'
)
SYSTEM_SHUTDOWN_COMPLETED: EventDefinition = EventDefinition(
name='system.shutdown.completed', description='Application shutdown completed.', message='Shutdown completed'
)
CONFIG_IMPORTED: EventDefinition = EventDefinition(
name='config.imported', description='Configuration was imported.', message='Config imported'
)
CONFIG_UPDATED: EventDefinition = EventDefinition(
name='config.updated', description='Configuration was updated.', message='Config updated'
)
CONFIG_WEBHOOK_UPDATED: EventDefinition = EventDefinition(
name='config.webhook.updated',
description='Event webhook configuration was updated.',
message='Webhook configuration updated',
)
CONFIG_CONNECTIONS_UPDATED: EventDefinition = EventDefinition(
name='config.connections.updated',
description='Connection configuration was updated.',
message='Config Connections updated',
)
CONFIG_TOOL_SERVERS_UPDATED: EventDefinition = EventDefinition(
name='config.tool_servers.updated',
description='Tool server configuration was updated.',
message='Config Tool Servers updated',
)
CONFIG_TERMINAL_SERVERS_UPDATED: EventDefinition = EventDefinition(
name='config.terminal_servers.updated',
description='Terminal server configuration was updated.',
message='Config Terminal Servers updated',
)
CONFIG_CODE_EXECUTION_UPDATED: EventDefinition = EventDefinition(
name='config.code_execution.updated',
description='Code execution configuration was updated.',
message='Config Code Execution updated',
)
CONFIG_MODELS_UPDATED: EventDefinition = EventDefinition(
name='config.models.updated', description='Model configuration was updated.', message='Config Models updated'
)
CONFIG_BANNERS_UPDATED: EventDefinition = EventDefinition(
name='config.banners.updated', description='Banner configuration was updated.', message='Config Banners updated'
)
CONFIG_SUGGESTIONS_UPDATED: EventDefinition = EventDefinition(
name='config.suggestions.updated',
description='Suggestion configuration was updated.',
message='Config Suggestions updated',
)
AUTH_SIGNUP: EventDefinition = EventDefinition(
name='auth.signup', description='A user account was created through signup.', message='User signed up'
)
AUTH_LOGIN: EventDefinition = EventDefinition(
name='auth.login', description='A user successfully logged in.', message='User logged in'
)
AUTH_LOGOUT: EventDefinition = EventDefinition(
name='auth.logout', description='A user logged out.', message='User logged out'
)
AUTH_PASSWORD_CHANGED: EventDefinition = EventDefinition(
name='auth.password_changed', description='A user password was changed.', message='Password changed'
)
AUTH_API_KEY_CREATED: EventDefinition = EventDefinition(
name='auth.api_key.created', description='A user API key was created.', message='API key created'
)
AUTH_API_KEY_DELETED: EventDefinition = EventDefinition(
name='auth.api_key.deleted', description='A user API key was deleted.', message='API key deleted'
)
AUTH_OAUTH_SESSION_DELETED: EventDefinition = EventDefinition(
name='auth.oauth_session.deleted', description='An OAuth session was deleted.', message='OAuth session deleted'
)
USER_CREATED: EventDefinition = EventDefinition(
name='user.created', description='A user account was created.', message='User created'
)
USER_UPDATED: EventDefinition = EventDefinition(
name='user.updated', description='A user account was updated.', message='User updated'
)
USER_DELETED: EventDefinition = EventDefinition(
name='user.deleted', description='A user account was deleted.', message='User deleted'
)
USER_ROLE_UPDATED: EventDefinition = EventDefinition(
name='user.role_updated', description='A user role was updated.', message='User role updated'
)
USER_STATUS_UPDATED: EventDefinition = EventDefinition(
name='user.status_updated', description='A user status was updated.', message='User status updated'
)
USER_SETTINGS_UPDATED: EventDefinition = EventDefinition(
name='user.settings_updated', description='A user settings object was updated.', message='User settings updated'
)
USER_PROFILE_UPDATED: EventDefinition = EventDefinition(
name='user.profile_updated', description='A user profile was updated.', message='User profile updated'
)
USER_PERMISSIONS_UPDATED: EventDefinition = EventDefinition(
name='user.permissions_updated',
description='A user permissions object was updated.',
message='User permissions updated',
)
GROUP_CREATED: EventDefinition = EventDefinition(
name='group.created', description='A group was created.', message='Group created'
)
GROUP_UPDATED: EventDefinition = EventDefinition(
name='group.updated', description='A group was updated.', message='Group updated'
)
GROUP_DELETED: EventDefinition = EventDefinition(
name='group.deleted', description='A group was deleted.', message='Group deleted'
)
GROUP_MEMBER_ADDED: EventDefinition = EventDefinition(
name='group.member_added', description='A user was added to a group.', message='Group member added'
)
GROUP_MEMBER_REMOVED: EventDefinition = EventDefinition(
name='group.member_removed', description='A user was removed from a group.', message='Group member removed'
)
CHAT_CREATED: EventDefinition = EventDefinition(
name='chat.created', description='A chat was created.', message='Chat created'
)
CHAT_IMPORTED: EventDefinition = EventDefinition(
name='chat.imported', description='A chat was imported.', message='Chat imported'
)
CHAT_UPDATED: EventDefinition = EventDefinition(
name='chat.updated', description='A chat was updated.', message='Chat updated'
)
CHAT_DELETED: EventDefinition = EventDefinition(
name='chat.deleted', description='A chat was deleted.', message='Chat deleted'
)
CHAT_DELETED_ALL: EventDefinition = EventDefinition(
name='chat.deleted_all', description='All chats for a scope were deleted.', message='Chat deleted all'
)
CHAT_COMPACTED: EventDefinition = EventDefinition(
name='chat.compacted', description='A chat was compacted.', message='Chat compacted'
)
CHAT_PINNED: EventDefinition = EventDefinition(
name='chat.pinned', description='A chat was pinned.', message='Chat pinned'
)
CHAT_UNPINNED: EventDefinition = EventDefinition(
name='chat.unpinned', description='A chat was unpinned.', message='Chat unpinned'
)
CHAT_CLONED: EventDefinition = EventDefinition(
name='chat.cloned', description='A chat was cloned.', message='Chat cloned'
)
CHAT_ARCHIVED: EventDefinition = EventDefinition(
name='chat.archived', description='A chat was archived.', message='Chat archived'
)
CHAT_UNARCHIVED: EventDefinition = EventDefinition(
name='chat.unarchived', description='A chat was unarchived.', message='Chat unarchived'
)
CHAT_SHARED: EventDefinition = EventDefinition(
name='chat.shared', description='A chat was shared.', message='Chat shared'
)
CHAT_UNSHARED: EventDefinition = EventDefinition(
name='chat.unshared', description='A chat was unshared.', message='Chat unshared'
)
CHAT_FOLDER_UPDATED: EventDefinition = EventDefinition(
name='chat.folder_updated', description='A chat folder assignment was updated.', message='Chat folder updated'
)
CHAT_TAG_ADDED: EventDefinition = EventDefinition(
name='chat.tag_added', description='A tag was added to a chat.', message='Chat tag added'
)
CHAT_TAG_REMOVED: EventDefinition = EventDefinition(
name='chat.tag_removed', description='A tag was removed from a chat.', message='Chat tag removed'
)
MESSAGE_CREATED: EventDefinition = EventDefinition(
name='message.created', description='A message was created.', message='Message created'
)
MESSAGE_UPDATED: EventDefinition = EventDefinition(
name='message.updated', description='A message was updated.', message='Message updated'
)
MESSAGE_DELETED: EventDefinition = EventDefinition(
name='message.deleted', description='A message was deleted.', message='Message deleted'
)
MESSAGE_EVENT_RECEIVED: EventDefinition = EventDefinition(
name='message.event_received',
description='A message-level event was received.',
message='Message event received',
)
MESSAGE_REACTION_ADDED: EventDefinition = EventDefinition(
name='message.reaction_added',
description='A reaction was added to a message.',
message='Message reaction added',
)
MESSAGE_REACTION_REMOVED: EventDefinition = EventDefinition(
name='message.reaction_removed',
description='A reaction was removed from a message.',
message='Message reaction removed',
)
MESSAGE_PINNED: EventDefinition = EventDefinition(
name='message.pinned', description='A message was pinned.', message='Message pinned'
)
MESSAGE_UNPINNED: EventDefinition = EventDefinition(
name='message.unpinned', description='A message was unpinned.', message='Message unpinned'
)
CHANNEL_CREATED: EventDefinition = EventDefinition(
name='channel.created', description='A channel was created.', message='Channel created'
)
CHANNEL_UPDATED: EventDefinition = EventDefinition(
name='channel.updated', description='A channel was updated.', message='Channel updated'
)
CHANNEL_DELETED: EventDefinition = EventDefinition(
name='channel.deleted', description='A channel was deleted.', message='Channel deleted'
)
CHANNEL_MEMBER_ADDED: EventDefinition = EventDefinition(
name='channel.member_added', description='A member was added to a channel.', message='Channel member added'
)
CHANNEL_MEMBER_REMOVED: EventDefinition = EventDefinition(
name='channel.member_removed',
description='A member was removed from a channel.',
message='Channel member removed',
)
CHANNEL_MEMBER_ACTIVE_UPDATED: EventDefinition = EventDefinition(
name='channel.member_active_updated',
description='A channel member active state was updated.',
message='Channel member active updated',
)
CHANNEL_WEBHOOK_CREATED: EventDefinition = EventDefinition(
name='channel.webhook.created',
description='A channel incoming webhook was created.',
message='Channel Webhook created',
)
CHANNEL_WEBHOOK_UPDATED: EventDefinition = EventDefinition(
name='channel.webhook.updated',
description='A channel incoming webhook was updated.',
message='Channel Webhook updated',
)
CHANNEL_WEBHOOK_DELETED: EventDefinition = EventDefinition(
name='channel.webhook.deleted',
description='A channel incoming webhook was deleted.',
message='Channel Webhook deleted',
)
FILE_UPLOADED: EventDefinition = EventDefinition(
name='file.uploaded', description='A file was uploaded.', message='File uploaded'
)
FILE_CONTENT_UPDATED: EventDefinition = EventDefinition(
name='file.content_updated', description='File content was updated.', message='File content updated'
)
FILE_RENAMED: EventDefinition = EventDefinition(
name='file.renamed', description='A file was renamed.', message='File renamed'
)
FILE_DELETED: EventDefinition = EventDefinition(
name='file.deleted', description='A file was deleted.', message='File deleted'
)
FILE_DELETED_ALL: EventDefinition = EventDefinition(
name='file.deleted_all', description='All files for a scope were deleted.', message='File deleted all'
)
FOLDER_CREATED: EventDefinition = EventDefinition(
name='folder.created', description='A folder was created.', message='Folder created'
)
FOLDER_UPDATED: EventDefinition = EventDefinition(
name='folder.updated', description='A folder was updated.', message='Folder updated'
)
FOLDER_PARENT_UPDATED: EventDefinition = EventDefinition(
name='folder.parent_updated', description='A folder parent was updated.', message='Folder parent updated'
)
FOLDER_ACCESS_UPDATED: EventDefinition = EventDefinition(
name='folder.access_updated', description='Folder access was updated.', message='Folder access updated'
)
FOLDER_DELETED: EventDefinition = EventDefinition(
name='folder.deleted', description='A folder was deleted.', message='Folder deleted'
)
NOTE_CREATED: EventDefinition = EventDefinition(
name='note.created', description='A note was created.', message='Note created'
)
NOTE_UPDATED: EventDefinition = EventDefinition(
name='note.updated', description='A note was updated.', message='Note updated'
)
NOTE_ACCESS_UPDATED: EventDefinition = EventDefinition(
name='note.access_updated', description='Note access was updated.', message='Note access updated'
)
NOTE_PINNED: EventDefinition = EventDefinition(
name='note.pinned', description='A note was pinned.', message='Note pinned'
)
NOTE_UNPINNED: EventDefinition = EventDefinition(
name='note.unpinned', description='A note was unpinned.', message='Note unpinned'
)
NOTE_DELETED: EventDefinition = EventDefinition(
name='note.deleted', description='A note was deleted.', message='Note deleted'
)
MEMORY_CREATED: EventDefinition = EventDefinition(
name='memory.created', description='A memory was created.', message='Memory created'
)
MEMORY_UPDATED: EventDefinition = EventDefinition(
name='memory.updated', description='A memory was updated.', message='Memory updated'
)
MEMORY_DELETED: EventDefinition = EventDefinition(
name='memory.deleted', description='A memory was deleted.', message='Memory deleted'
)
MEMORY_RESET: EventDefinition = EventDefinition(
name='memory.reset', description='A memory was reset.', message='Memory reset'
)
KNOWLEDGE_CREATED: EventDefinition = EventDefinition(
name='knowledge.created', description='A knowledge was created.', message='Knowledge created'
)
KNOWLEDGE_UPDATED: EventDefinition = EventDefinition(
name='knowledge.updated', description='A knowledge was updated.', message='Knowledge updated'
)
KNOWLEDGE_DELETED: EventDefinition = EventDefinition(
name='knowledge.deleted', description='A knowledge was deleted.', message='Knowledge deleted'
)
KNOWLEDGE_RESET: EventDefinition = EventDefinition(
name='knowledge.reset', description='A knowledge was reset.', message='Knowledge reset'
)
KNOWLEDGE_REINDEXED: EventDefinition = EventDefinition(
name='knowledge.reindexed', description='A knowledge was reindexed.', message='Knowledge reindexed'
)
KNOWLEDGE_ACCESS_UPDATED: EventDefinition = EventDefinition(
name='knowledge.access_updated', description='Knowledge access was updated.', message='Knowledge access updated'
)
KNOWLEDGE_FILE_ADDED: EventDefinition = EventDefinition(
name='knowledge.file.added', description='A file was added to a knowledge base.', message='Knowledge File added'
)
KNOWLEDGE_FILE_UPDATED: EventDefinition = EventDefinition(
name='knowledge.file.updated', description='A knowledge file was updated.', message='Knowledge File updated'
)
KNOWLEDGE_FILE_REMOVED: EventDefinition = EventDefinition(
name='knowledge.file.removed',
description='A file was removed from a knowledge base.',
message='Knowledge File removed',
)
KNOWLEDGE_FILE_MOVED: EventDefinition = EventDefinition(
name='knowledge.file.moved', description='A knowledge file was moved.', message='Knowledge File moved'
)
KNOWLEDGE_DIRECTORY_CREATED: EventDefinition = EventDefinition(
name='knowledge.directory.created',
description='A knowledge directory was created.',
message='Knowledge Directory created',
)
KNOWLEDGE_DIRECTORY_UPDATED: EventDefinition = EventDefinition(
name='knowledge.directory.updated',
description='A knowledge directory was updated.',
message='Knowledge Directory updated',
)
KNOWLEDGE_DIRECTORY_DELETED: EventDefinition = EventDefinition(
name='knowledge.directory.deleted',
description='A knowledge directory was deleted.',
message='Knowledge Directory deleted',
)
KNOWLEDGE_EXTERNAL_CONNECTION_CREATED: EventDefinition = EventDefinition(
name='knowledge.external_connection.created',
description='A knowledge external connection was created.',
message='Knowledge External Connection created',
)
KNOWLEDGE_EXTERNAL_CONNECTION_UPDATED: EventDefinition = EventDefinition(
name='knowledge.external_connection.updated',
description='A knowledge external connection was updated.',
message='Knowledge External Connection updated',
)
KNOWLEDGE_EXTERNAL_CONNECTION_DELETED: EventDefinition = EventDefinition(
name='knowledge.external_connection.deleted',
description='A knowledge external connection was deleted.',
message='Knowledge External Connection deleted',
)
RETRIEVAL_CONTENT_PROCESSED: EventDefinition = EventDefinition(
name='retrieval.content.processed',
description='Retrieval content was processed.',
message='Retrieval Content processed',
)
RETRIEVAL_COLLECTION_DELETED: EventDefinition = EventDefinition(
name='retrieval.collection.deleted',
description='A retrieval collection was deleted.',
message='Retrieval Collection deleted',
)
RETRIEVAL_VECTOR_DB_RESET: EventDefinition = EventDefinition(
name='retrieval.vector_db.reset',
description='The retrieval vector database was reset.',
message='Retrieval Vector Db reset',
)
RETRIEVAL_UPLOADS_RESET: EventDefinition = EventDefinition(
name='retrieval.uploads.reset', description='Retrieval uploads were reset.', message='Retrieval Uploads reset'
)
MODEL_CREATED: EventDefinition = EventDefinition(
name='model.created', description='A model was created.', message='Model created'
)
MODEL_IMPORTED: EventDefinition = EventDefinition(
name='model.imported', description='A model was imported.', message='Model imported'
)
MODEL_SYNCED: EventDefinition = EventDefinition(
name='model.synced', description='A model was synced.', message='Model synced'
)
MODEL_UPDATED: EventDefinition = EventDefinition(
name='model.updated', description='A model was updated.', message='Model updated'
)
MODEL_DELETED: EventDefinition = EventDefinition(
name='model.deleted', description='A model was deleted.', message='Model deleted'
)
MODEL_ENABLED: EventDefinition = EventDefinition(
name='model.enabled', description='A model was enabled.', message='Model enabled'
)
MODEL_DISABLED: EventDefinition = EventDefinition(
name='model.disabled', description='A model was disabled.', message='Model disabled'
)
MODEL_ACCESS_UPDATED: EventDefinition = EventDefinition(
name='model.access_updated', description='Model access was updated.', message='Model access updated'
)
MODEL_PROVIDER_CONFIG_UPDATED: EventDefinition = EventDefinition(
name='model.provider_config.updated',
description='Model provider configuration was updated.',
message='Model Provider Config updated',
)
MODEL_PROVIDER_MODEL_CREATED: EventDefinition = EventDefinition(
name='model.provider_model.created',
description='A provider model was created.',
message='Provider model created',
)
MODEL_PROVIDER_MODEL_DELETED: EventDefinition = EventDefinition(
name='model.provider_model.deleted',
description='A provider model was deleted.',
message='Provider model deleted',
)
FUNCTION_CREATED: EventDefinition = EventDefinition(
name='function.created', description='A function was created.', message='Function created'
)
FUNCTION_UPDATED: EventDefinition = EventDefinition(
name='function.updated', description='A function was updated.', message='Function updated'
)
FUNCTION_DELETED: EventDefinition = EventDefinition(
name='function.deleted', description='A function was deleted.', message='Function deleted'
)
FUNCTION_ENABLED: EventDefinition = EventDefinition(
name='function.enabled', description='A function was enabled.', message='Function enabled'
)
FUNCTION_DISABLED: EventDefinition = EventDefinition(
name='function.disabled', description='A function was disabled.', message='Function disabled'
)
FUNCTION_VALVES_UPDATED: EventDefinition = EventDefinition(
name='function.valves_updated', description='Function valves were updated.', message='Function valves updated'
)
TOOL_CREATED: EventDefinition = EventDefinition(
name='tool.created', description='A tool was created.', message='Tool created'
)
TOOL_UPDATED: EventDefinition = EventDefinition(
name='tool.updated', description='A tool was updated.', message='Tool updated'
)
TOOL_DELETED: EventDefinition = EventDefinition(
name='tool.deleted', description='A tool was deleted.', message='Tool deleted'
)
TOOL_ACCESS_UPDATED: EventDefinition = EventDefinition(
name='tool.access_updated', description='Tool access was updated.', message='Tool access updated'
)
TOOL_VALVES_UPDATED: EventDefinition = EventDefinition(
name='tool.valves_updated', description='Tool valves were updated.', message='Tool valves updated'
)
SKILL_CREATED: EventDefinition = EventDefinition(
name='skill.created', description='A skill was created.', message='Skill created'
)
SKILL_UPDATED: EventDefinition = EventDefinition(
name='skill.updated', description='A skill was updated.', message='Skill updated'
)
SKILL_DELETED: EventDefinition = EventDefinition(
name='skill.deleted', description='A skill was deleted.', message='Skill deleted'
)
SKILL_ENABLED: EventDefinition = EventDefinition(
name='skill.enabled', description='A skill was enabled.', message='Skill enabled'
)
SKILL_DISABLED: EventDefinition = EventDefinition(
name='skill.disabled', description='A skill was disabled.', message='Skill disabled'
)
PROMPT_CREATED: EventDefinition = EventDefinition(
name='prompt.created', description='A prompt was created.', message='Prompt created'
)
PROMPT_UPDATED: EventDefinition = EventDefinition(
name='prompt.updated', description='A prompt was updated.', message='Prompt updated'
)
PROMPT_DELETED: EventDefinition = EventDefinition(
name='prompt.deleted', description='A prompt was deleted.', message='Prompt deleted'
)
PROMPT_ENABLED: EventDefinition = EventDefinition(
name='prompt.enabled', description='A prompt was enabled.', message='Prompt enabled'
)
PROMPT_DISABLED: EventDefinition = EventDefinition(
name='prompt.disabled', description='A prompt was disabled.', message='Prompt disabled'
)
PROMPT_VERSION_UPDATED: EventDefinition = EventDefinition(
name='prompt.version_updated', description='A prompt version was updated.', message='Prompt version updated'
)
PROMPT_ACCESS_UPDATED: EventDefinition = EventDefinition(
name='prompt.access_updated', description='Prompt access was updated.', message='Prompt access updated'
)
PIPELINE_UPLOADED: EventDefinition = EventDefinition(
name='pipeline.uploaded', description='A pipeline was uploaded.', message='Pipeline uploaded'
)
PIPELINE_ADDED: EventDefinition = EventDefinition(
name='pipeline.added', description='A pipeline was added.', message='Pipeline added'
)
PIPELINE_DELETED: EventDefinition = EventDefinition(
name='pipeline.deleted', description='A pipeline was deleted.', message='Pipeline deleted'
)
PIPELINE_VALVES_UPDATED: EventDefinition = EventDefinition(
name='pipeline.valves_updated', description='Pipeline valves were updated.', message='Pipeline valves updated'
)
CALENDAR_CREATED: EventDefinition = EventDefinition(
name='calendar.created', description='A calendar was created.', message='Calendar created'
)
CALENDAR_UPDATED: EventDefinition = EventDefinition(
name='calendar.updated', description='A calendar was updated.', message='Calendar updated'
)
CALENDAR_DELETED: EventDefinition = EventDefinition(
name='calendar.deleted', description='A calendar was deleted.', message='Calendar deleted'
)
CALENDAR_DEFAULT_UPDATED: EventDefinition = EventDefinition(
name='calendar.default_updated',
description='The default calendar was updated.',
message='Calendar default updated',
)
CALENDAR_EVENT_CREATED: EventDefinition = EventDefinition(
name='calendar.event.created', description='A calendar event was created.', message='Calendar Event created'
)
CALENDAR_EVENT_UPDATED: EventDefinition = EventDefinition(
name='calendar.event.updated', description='A calendar event was updated.', message='Calendar Event updated'
)
CALENDAR_EVENT_DELETED: EventDefinition = EventDefinition(
name='calendar.event.deleted', description='A calendar event was deleted.', message='Calendar Event deleted'
)
CALENDAR_EVENT_RSVP_UPDATED: EventDefinition = EventDefinition(
name='calendar.event.rsvp_updated',
description='A calendar event RSVP was updated.',
message='Calendar Event rsvp updated',
)
AUTOMATION_CREATED: EventDefinition = EventDefinition(
name='automation.created', description='An automation was created.', message='Automation created'
)
AUTOMATION_UPDATED: EventDefinition = EventDefinition(
name='automation.updated', description='An automation was updated.', message='Automation updated'
)
AUTOMATION_ENABLED: EventDefinition = EventDefinition(
name='automation.enabled', description='An automation was enabled.', message='Automation enabled'
)
AUTOMATION_DISABLED: EventDefinition = EventDefinition(
name='automation.disabled', description='An automation was disabled.', message='Automation disabled'
)
AUTOMATION_DELETED: EventDefinition = EventDefinition(
name='automation.deleted', description='An automation was deleted.', message='Automation deleted'
)
AUTOMATION_RUN_STARTED: EventDefinition = EventDefinition(
name='automation.run_started', description='An automation run started.', message='Automation run started'
)
AUTOMATION_RUN_COMPLETED: EventDefinition = EventDefinition(
name='automation.run_completed', description='An automation run completed.', message='Automation run completed'
)
AUTOMATION_RUN_FAILED: EventDefinition = EventDefinition(
name='automation.run_failed', description='An automation run failed.', message='Automation run failed'
)
FEEDBACK_CREATED: EventDefinition = EventDefinition(
name='feedback.created', description='A feedback was created.', message='Feedback created'
)
FEEDBACK_UPDATED: EventDefinition = EventDefinition(
name='feedback.updated', description='A feedback was updated.', message='Feedback updated'
)
FEEDBACK_DELETED: EventDefinition = EventDefinition(
name='feedback.deleted', description='A feedback was deleted.', message='Feedback deleted'
)
FEEDBACK_DELETED_ALL: EventDefinition = EventDefinition(
name='feedback.deleted_all', description='All feedback for a scope was deleted.', message='Feedback deleted all'
)
IMAGE_GENERATED: EventDefinition = EventDefinition(
name='image.generated', description='An image was generated.', message='Image generated'
)
IMAGE_EDITED: EventDefinition = EventDefinition(
name='image.edited', description='An image was edited.', message='Image edited'
)
AUDIO_SPEECH_REQUESTED: EventDefinition = EventDefinition(
name='audio.speech_requested', description='Speech generation was requested.', message='Speech requested'
)
AUDIO_TRANSCRIPTION_REQUESTED: EventDefinition = EventDefinition(
name='audio.transcription_requested',
description='Audio transcription was requested.',
message='Transcription requested',
)
TERMINAL_SESSION_OPENED: EventDefinition = EventDefinition(
name='terminal.session.opened', description='A terminal session was opened.', message='Terminal Session opened'
)
TERMINAL_SESSION_CLOSED: EventDefinition = EventDefinition(
name='terminal.session.closed', description='A terminal session was closed.', message='Terminal Session closed'
)
EVENTS = EventDefinitions()
@ -232,6 +636,7 @@ def get_event_catalog() -> list[dict[str, str]]:
for definition in EVENT_DEFINITIONS
]
SENSITIVE_KEYS = {
'password',
'hashed_password',
@ -369,10 +774,7 @@ async def event_target_matches(
from open_webui.models.groups import Groups
groups_by_user = await Groups.get_groups_by_member_ids(list(user_ids))
user_group_ids = {
user_id: {group.id for group in groups}
for user_id, groups in groups_by_user.items()
}
user_group_ids = {user_id: {group.id for group in groups} for user_id, groups in groups_by_user.items()}
return any(group_ids.intersection(target_group_ids) for group_ids in user_group_ids.values())
@ -577,7 +979,7 @@ def build_event(
subject=_sanitize(subject) if subject else None,
data=_sanitize(data or {}),
message=message,
)
)
async def dispatch_webhook_event(app: Any, event: Event) -> None:
@ -652,9 +1054,7 @@ async def dispatch_event_functions(app: Any, event: Event, request: Any | None =
'__app__': app,
'__request__': request,
}
params = {
key: value for key, value in extra_params.items() if accepts_kwargs or key in sig.parameters
}
params = {key: value for key, value in extra_params.items() if accepts_kwargs or key in sig.parameters}
if inspect.iscoroutinefunction(handler):
await handler(**params)

View file

@ -24,421 +24,419 @@ depends_on: Union[str, Sequence[str], None] = None
# Maps every dot-notation blob path to its legacy env/config key name.
# Built from the legacy persistent config declarations in config.py.
BLOB_PATH_TO_KEY = {
"audio.stt.allowed_extensions": "AUDIO_STT_ALLOWED_EXTENSIONS",
"audio.stt.azure.api_key": "AUDIO_STT_AZURE_API_KEY",
"audio.stt.azure.base_url": "AUDIO_STT_AZURE_BASE_URL",
"audio.stt.azure.locales": "AUDIO_STT_AZURE_LOCALES",
"audio.stt.azure.max_speakers": "AUDIO_STT_AZURE_MAX_SPEAKERS",
"audio.stt.azure.region": "AUDIO_STT_AZURE_REGION",
"audio.stt.deepgram.api_key": "DEEPGRAM_API_KEY",
"audio.stt.engine": "AUDIO_STT_ENGINE",
"audio.stt.mistral.api_base_url": "AUDIO_STT_MISTRAL_API_BASE_URL",
"audio.stt.mistral.api_key": "AUDIO_STT_MISTRAL_API_KEY",
"audio.stt.mistral.use_chat_completions": "AUDIO_STT_MISTRAL_USE_CHAT_COMPLETIONS",
"audio.stt.model": "AUDIO_STT_MODEL",
"audio.stt.openai.api_base_url": "AUDIO_STT_OPENAI_API_BASE_URL",
"audio.stt.openai.api_key": "AUDIO_STT_OPENAI_API_KEY",
"audio.stt.supported_content_types": "AUDIO_STT_SUPPORTED_CONTENT_TYPES",
"audio.stt.whisper_model": "WHISPER_MODEL",
"audio.tts.api_key": "AUDIO_TTS_API_KEY",
"audio.tts.azure.speech_base_url": "AUDIO_TTS_AZURE_SPEECH_BASE_URL",
"audio.tts.azure.speech_output_format": "AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT",
"audio.tts.azure.speech_region": "AUDIO_TTS_AZURE_SPEECH_REGION",
"audio.tts.engine": "AUDIO_TTS_ENGINE",
"audio.tts.mistral.api_base_url": "AUDIO_TTS_MISTRAL_API_BASE_URL",
"audio.tts.mistral.api_key": "AUDIO_TTS_MISTRAL_API_KEY",
"audio.tts.model": "AUDIO_TTS_MODEL",
"audio.tts.openai.api_base_url": "AUDIO_TTS_OPENAI_API_BASE_URL",
"audio.tts.openai.api_key": "AUDIO_TTS_OPENAI_API_KEY",
"audio.tts.openai.params": "AUDIO_TTS_OPENAI_PARAMS",
"audio.tts.split_on": "AUDIO_TTS_SPLIT_ON",
"audio.tts.voice": "AUDIO_TTS_VOICE",
"auth.admin.email": "ADMIN_EMAIL",
"auth.admin.show": "SHOW_ADMIN_DETAILS",
"auth.api_key.allowed_endpoints": "API_KEYS_ALLOWED_ENDPOINTS",
"auth.api_key.endpoint_restrictions": "ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS",
"auth.enable_api_keys": "ENABLE_API_KEYS",
"auth.jwt_expiry": "JWT_EXPIRES_IN",
"automations.enable": "ENABLE_AUTOMATIONS",
"automations.max_count": "AUTOMATION_MAX_COUNT",
"automations.min_interval": "AUTOMATION_MIN_INTERVAL",
"calendar.enable": "ENABLE_CALENDAR",
"channels.enable": "ENABLE_CHANNELS",
"code_execution.enable": "ENABLE_CODE_EXECUTION",
"code_execution.engine": "CODE_EXECUTION_ENGINE",
"code_execution.jupyter.auth": "CODE_EXECUTION_JUPYTER_AUTH",
"code_execution.jupyter.auth_password": "CODE_EXECUTION_JUPYTER_AUTH_PASSWORD",
"code_execution.jupyter.auth_token": "CODE_EXECUTION_JUPYTER_AUTH_TOKEN",
"code_execution.jupyter.timeout": "CODE_EXECUTION_JUPYTER_TIMEOUT",
"code_execution.jupyter.url": "CODE_EXECUTION_JUPYTER_URL",
"code_interpreter.enable": "ENABLE_CODE_INTERPRETER",
"code_interpreter.engine": "CODE_INTERPRETER_ENGINE",
"code_interpreter.jupyter.auth": "CODE_INTERPRETER_JUPYTER_AUTH",
"code_interpreter.jupyter.auth_password": "CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD",
"code_interpreter.jupyter.auth_token": "CODE_INTERPRETER_JUPYTER_AUTH_TOKEN",
"code_interpreter.jupyter.timeout": "CODE_INTERPRETER_JUPYTER_TIMEOUT",
"code_interpreter.jupyter.url": "CODE_INTERPRETER_JUPYTER_URL",
"code_interpreter.prompt_template": "CODE_INTERPRETER_PROMPT_TEMPLATE",
"direct.enable": "ENABLE_DIRECT_CONNECTIONS",
"evaluation.arena.enable": "ENABLE_EVALUATION_ARENA_MODELS",
"evaluation.arena.models": "EVALUATION_ARENA_MODELS",
"file.image_compression_height": "FILE_IMAGE_COMPRESSION_HEIGHT",
"file.image_compression_width": "FILE_IMAGE_COMPRESSION_WIDTH",
"folders.enable": "ENABLE_FOLDERS",
"folders.max_file_count": "FOLDER_MAX_FILE_COUNT",
"google_drive.api_key": "GOOGLE_DRIVE_API_KEY",
"google_drive.client_id": "GOOGLE_DRIVE_CLIENT_ID",
"google_drive.enable": "ENABLE_GOOGLE_DRIVE_INTEGRATION",
"image_generation.automatic1111.api_auth": "AUTOMATIC1111_API_AUTH",
"image_generation.automatic1111.api_params": "AUTOMATIC1111_PARAMS",
"image_generation.automatic1111.base_url": "AUTOMATIC1111_BASE_URL",
"image_generation.comfyui.api_key": "COMFYUI_API_KEY",
"image_generation.comfyui.base_url": "COMFYUI_BASE_URL",
"image_generation.comfyui.nodes": "COMFYUI_WORKFLOW_NODES",
"image_generation.comfyui.workflow": "COMFYUI_WORKFLOW",
"image_generation.enable": "ENABLE_IMAGE_GENERATION",
"image_generation.engine": "IMAGE_GENERATION_ENGINE",
"image_generation.gemini.api_base_url": "IMAGES_GEMINI_API_BASE_URL",
"image_generation.gemini.api_key": "IMAGES_GEMINI_API_KEY",
"image_generation.gemini.endpoint_method": "IMAGES_GEMINI_ENDPOINT_METHOD",
"image_generation.model": "IMAGE_GENERATION_MODEL",
"image_generation.openai.api_base_url": "IMAGES_OPENAI_API_BASE_URL",
"image_generation.openai.api_key": "IMAGES_OPENAI_API_KEY",
"image_generation.openai.api_version": "IMAGES_OPENAI_API_VERSION",
"image_generation.openai.params": "IMAGES_OPENAI_API_PARAMS",
"image_generation.prompt.enable": "ENABLE_IMAGE_PROMPT_GENERATION",
"image_generation.size": "IMAGE_SIZE",
"image_generation.steps": "IMAGE_STEPS",
"images.edit.comfyui.api_key": "IMAGES_EDIT_COMFYUI_API_KEY",
"images.edit.comfyui.base_url": "IMAGES_EDIT_COMFYUI_BASE_URL",
"images.edit.comfyui.nodes": "IMAGES_EDIT_COMFYUI_WORKFLOW_NODES",
"images.edit.comfyui.workflow": "IMAGES_EDIT_COMFYUI_WORKFLOW",
"images.edit.enable": "ENABLE_IMAGE_EDIT",
"images.edit.engine": "IMAGE_EDIT_ENGINE",
"images.edit.gemini.api_base_url": "IMAGES_EDIT_GEMINI_API_BASE_URL",
"images.edit.gemini.api_key": "IMAGES_EDIT_GEMINI_API_KEY",
"images.edit.model": "IMAGE_EDIT_MODEL",
"images.edit.openai.api_base_url": "IMAGES_EDIT_OPENAI_API_BASE_URL",
"images.edit.openai.api_key": "IMAGES_EDIT_OPENAI_API_KEY",
"images.edit.openai.api_version": "IMAGES_EDIT_OPENAI_API_VERSION",
"images.edit.size": "IMAGE_EDIT_SIZE",
"ldap.enable": "ENABLE_LDAP",
"ldap.group.enable_creation": "ENABLE_LDAP_GROUP_CREATION",
"ldap.group.enable_management": "ENABLE_LDAP_GROUP_MANAGEMENT",
"ldap.server.app_dn": "LDAP_APP_DN",
"ldap.server.app_password": "LDAP_APP_PASSWORD",
"ldap.server.attribute_for_groups": "LDAP_ATTRIBUTE_FOR_GROUPS",
"ldap.server.attribute_for_mail": "LDAP_ATTRIBUTE_FOR_MAIL",
"ldap.server.attribute_for_username": "LDAP_ATTRIBUTE_FOR_USERNAME",
"ldap.server.ca_cert_file": "LDAP_CA_CERT_FILE",
"ldap.server.ciphers": "LDAP_CIPHERS",
"ldap.server.host": "LDAP_SERVER_HOST",
"ldap.server.label": "LDAP_SERVER_LABEL",
"ldap.server.port": "LDAP_SERVER_PORT",
"ldap.server.search_filter": "LDAP_SEARCH_FILTER",
"ldap.server.use_tls": "LDAP_USE_TLS",
"ldap.server.users_dn": "LDAP_SEARCH_BASE",
"ldap.server.validate_cert": "LDAP_VALIDATE_CERT",
"memories.enable": "ENABLE_MEMORIES",
"models.base_models_cache": "ENABLE_BASE_MODELS_CACHE",
"models.default_metadata": "DEFAULT_MODEL_METADATA",
"models.default_params": "DEFAULT_MODEL_PARAMS",
"notes.enable": "ENABLE_NOTES",
'audio.stt.allowed_extensions': 'AUDIO_STT_ALLOWED_EXTENSIONS',
'audio.stt.azure.api_key': 'AUDIO_STT_AZURE_API_KEY',
'audio.stt.azure.base_url': 'AUDIO_STT_AZURE_BASE_URL',
'audio.stt.azure.locales': 'AUDIO_STT_AZURE_LOCALES',
'audio.stt.azure.max_speakers': 'AUDIO_STT_AZURE_MAX_SPEAKERS',
'audio.stt.azure.region': 'AUDIO_STT_AZURE_REGION',
'audio.stt.deepgram.api_key': 'DEEPGRAM_API_KEY',
'audio.stt.engine': 'AUDIO_STT_ENGINE',
'audio.stt.mistral.api_base_url': 'AUDIO_STT_MISTRAL_API_BASE_URL',
'audio.stt.mistral.api_key': 'AUDIO_STT_MISTRAL_API_KEY',
'audio.stt.mistral.use_chat_completions': 'AUDIO_STT_MISTRAL_USE_CHAT_COMPLETIONS',
'audio.stt.model': 'AUDIO_STT_MODEL',
'audio.stt.openai.api_base_url': 'AUDIO_STT_OPENAI_API_BASE_URL',
'audio.stt.openai.api_key': 'AUDIO_STT_OPENAI_API_KEY',
'audio.stt.supported_content_types': 'AUDIO_STT_SUPPORTED_CONTENT_TYPES',
'audio.stt.whisper_model': 'WHISPER_MODEL',
'audio.tts.api_key': 'AUDIO_TTS_API_KEY',
'audio.tts.azure.speech_base_url': 'AUDIO_TTS_AZURE_SPEECH_BASE_URL',
'audio.tts.azure.speech_output_format': 'AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT',
'audio.tts.azure.speech_region': 'AUDIO_TTS_AZURE_SPEECH_REGION',
'audio.tts.engine': 'AUDIO_TTS_ENGINE',
'audio.tts.mistral.api_base_url': 'AUDIO_TTS_MISTRAL_API_BASE_URL',
'audio.tts.mistral.api_key': 'AUDIO_TTS_MISTRAL_API_KEY',
'audio.tts.model': 'AUDIO_TTS_MODEL',
'audio.tts.openai.api_base_url': 'AUDIO_TTS_OPENAI_API_BASE_URL',
'audio.tts.openai.api_key': 'AUDIO_TTS_OPENAI_API_KEY',
'audio.tts.openai.params': 'AUDIO_TTS_OPENAI_PARAMS',
'audio.tts.split_on': 'AUDIO_TTS_SPLIT_ON',
'audio.tts.voice': 'AUDIO_TTS_VOICE',
'auth.admin.email': 'ADMIN_EMAIL',
'auth.admin.show': 'SHOW_ADMIN_DETAILS',
'auth.api_key.allowed_endpoints': 'API_KEYS_ALLOWED_ENDPOINTS',
'auth.api_key.endpoint_restrictions': 'ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS',
'auth.enable_api_keys': 'ENABLE_API_KEYS',
'auth.jwt_expiry': 'JWT_EXPIRES_IN',
'automations.enable': 'ENABLE_AUTOMATIONS',
'automations.max_count': 'AUTOMATION_MAX_COUNT',
'automations.min_interval': 'AUTOMATION_MIN_INTERVAL',
'calendar.enable': 'ENABLE_CALENDAR',
'channels.enable': 'ENABLE_CHANNELS',
'code_execution.enable': 'ENABLE_CODE_EXECUTION',
'code_execution.engine': 'CODE_EXECUTION_ENGINE',
'code_execution.jupyter.auth': 'CODE_EXECUTION_JUPYTER_AUTH',
'code_execution.jupyter.auth_password': 'CODE_EXECUTION_JUPYTER_AUTH_PASSWORD',
'code_execution.jupyter.auth_token': 'CODE_EXECUTION_JUPYTER_AUTH_TOKEN',
'code_execution.jupyter.timeout': 'CODE_EXECUTION_JUPYTER_TIMEOUT',
'code_execution.jupyter.url': 'CODE_EXECUTION_JUPYTER_URL',
'code_interpreter.enable': 'ENABLE_CODE_INTERPRETER',
'code_interpreter.engine': 'CODE_INTERPRETER_ENGINE',
'code_interpreter.jupyter.auth': 'CODE_INTERPRETER_JUPYTER_AUTH',
'code_interpreter.jupyter.auth_password': 'CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD',
'code_interpreter.jupyter.auth_token': 'CODE_INTERPRETER_JUPYTER_AUTH_TOKEN',
'code_interpreter.jupyter.timeout': 'CODE_INTERPRETER_JUPYTER_TIMEOUT',
'code_interpreter.jupyter.url': 'CODE_INTERPRETER_JUPYTER_URL',
'code_interpreter.prompt_template': 'CODE_INTERPRETER_PROMPT_TEMPLATE',
'direct.enable': 'ENABLE_DIRECT_CONNECTIONS',
'evaluation.arena.enable': 'ENABLE_EVALUATION_ARENA_MODELS',
'evaluation.arena.models': 'EVALUATION_ARENA_MODELS',
'file.image_compression_height': 'FILE_IMAGE_COMPRESSION_HEIGHT',
'file.image_compression_width': 'FILE_IMAGE_COMPRESSION_WIDTH',
'folders.enable': 'ENABLE_FOLDERS',
'folders.max_file_count': 'FOLDER_MAX_FILE_COUNT',
'google_drive.api_key': 'GOOGLE_DRIVE_API_KEY',
'google_drive.client_id': 'GOOGLE_DRIVE_CLIENT_ID',
'google_drive.enable': 'ENABLE_GOOGLE_DRIVE_INTEGRATION',
'image_generation.automatic1111.api_auth': 'AUTOMATIC1111_API_AUTH',
'image_generation.automatic1111.api_params': 'AUTOMATIC1111_PARAMS',
'image_generation.automatic1111.base_url': 'AUTOMATIC1111_BASE_URL',
'image_generation.comfyui.api_key': 'COMFYUI_API_KEY',
'image_generation.comfyui.base_url': 'COMFYUI_BASE_URL',
'image_generation.comfyui.nodes': 'COMFYUI_WORKFLOW_NODES',
'image_generation.comfyui.workflow': 'COMFYUI_WORKFLOW',
'image_generation.enable': 'ENABLE_IMAGE_GENERATION',
'image_generation.engine': 'IMAGE_GENERATION_ENGINE',
'image_generation.gemini.api_base_url': 'IMAGES_GEMINI_API_BASE_URL',
'image_generation.gemini.api_key': 'IMAGES_GEMINI_API_KEY',
'image_generation.gemini.endpoint_method': 'IMAGES_GEMINI_ENDPOINT_METHOD',
'image_generation.model': 'IMAGE_GENERATION_MODEL',
'image_generation.openai.api_base_url': 'IMAGES_OPENAI_API_BASE_URL',
'image_generation.openai.api_key': 'IMAGES_OPENAI_API_KEY',
'image_generation.openai.api_version': 'IMAGES_OPENAI_API_VERSION',
'image_generation.openai.params': 'IMAGES_OPENAI_API_PARAMS',
'image_generation.prompt.enable': 'ENABLE_IMAGE_PROMPT_GENERATION',
'image_generation.size': 'IMAGE_SIZE',
'image_generation.steps': 'IMAGE_STEPS',
'images.edit.comfyui.api_key': 'IMAGES_EDIT_COMFYUI_API_KEY',
'images.edit.comfyui.base_url': 'IMAGES_EDIT_COMFYUI_BASE_URL',
'images.edit.comfyui.nodes': 'IMAGES_EDIT_COMFYUI_WORKFLOW_NODES',
'images.edit.comfyui.workflow': 'IMAGES_EDIT_COMFYUI_WORKFLOW',
'images.edit.enable': 'ENABLE_IMAGE_EDIT',
'images.edit.engine': 'IMAGE_EDIT_ENGINE',
'images.edit.gemini.api_base_url': 'IMAGES_EDIT_GEMINI_API_BASE_URL',
'images.edit.gemini.api_key': 'IMAGES_EDIT_GEMINI_API_KEY',
'images.edit.model': 'IMAGE_EDIT_MODEL',
'images.edit.openai.api_base_url': 'IMAGES_EDIT_OPENAI_API_BASE_URL',
'images.edit.openai.api_key': 'IMAGES_EDIT_OPENAI_API_KEY',
'images.edit.openai.api_version': 'IMAGES_EDIT_OPENAI_API_VERSION',
'images.edit.size': 'IMAGE_EDIT_SIZE',
'ldap.enable': 'ENABLE_LDAP',
'ldap.group.enable_creation': 'ENABLE_LDAP_GROUP_CREATION',
'ldap.group.enable_management': 'ENABLE_LDAP_GROUP_MANAGEMENT',
'ldap.server.app_dn': 'LDAP_APP_DN',
'ldap.server.app_password': 'LDAP_APP_PASSWORD',
'ldap.server.attribute_for_groups': 'LDAP_ATTRIBUTE_FOR_GROUPS',
'ldap.server.attribute_for_mail': 'LDAP_ATTRIBUTE_FOR_MAIL',
'ldap.server.attribute_for_username': 'LDAP_ATTRIBUTE_FOR_USERNAME',
'ldap.server.ca_cert_file': 'LDAP_CA_CERT_FILE',
'ldap.server.ciphers': 'LDAP_CIPHERS',
'ldap.server.host': 'LDAP_SERVER_HOST',
'ldap.server.label': 'LDAP_SERVER_LABEL',
'ldap.server.port': 'LDAP_SERVER_PORT',
'ldap.server.search_filter': 'LDAP_SEARCH_FILTER',
'ldap.server.use_tls': 'LDAP_USE_TLS',
'ldap.server.users_dn': 'LDAP_SEARCH_BASE',
'ldap.server.validate_cert': 'LDAP_VALIDATE_CERT',
'memories.enable': 'ENABLE_MEMORIES',
'models.base_models_cache': 'ENABLE_BASE_MODELS_CACHE',
'models.default_metadata': 'DEFAULT_MODEL_METADATA',
'models.default_params': 'DEFAULT_MODEL_PARAMS',
'notes.enable': 'ENABLE_NOTES',
# OAuth — direct paths
"oauth.admin_roles": "OAUTH_ADMIN_ROLES",
"oauth.allowed_domains": "OAUTH_ALLOWED_DOMAINS",
"oauth.allowed_roles": "OAUTH_ALLOWED_ROLES",
"oauth.audience": "OAUTH_AUDIENCE",
"oauth.auto_redirect": "OAUTH_AUTO_REDIRECT",
"oauth.blocked_groups": "OAUTH_BLOCKED_GROUPS",
"oauth.client.timeout": "OAUTH_CLIENT_TIMEOUT",
"oauth.enable_group_creation": "ENABLE_OAUTH_GROUP_CREATION",
"oauth.enable_group_mapping": "ENABLE_OAUTH_GROUP_MANAGEMENT",
"oauth.enable_role_mapping": "ENABLE_OAUTH_ROLE_MANAGEMENT",
"oauth.enable_signup": "ENABLE_OAUTH_SIGNUP",
"oauth.group_default_share": "OAUTH_GROUP_DEFAULT_SHARE",
"oauth.merge_accounts_by_email": "OAUTH_MERGE_ACCOUNTS_BY_EMAIL",
"oauth.refresh_token_include_scope": "OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE",
"oauth.roles_claim": "OAUTH_ROLES_CLAIM",
"oauth.update_email_on_login": "OAUTH_UPDATE_EMAIL_ON_LOGIN",
"oauth.update_name_on_login": "OAUTH_UPDATE_NAME_ON_LOGIN",
"oauth.update_picture_on_login": "OAUTH_UPDATE_PICTURE_ON_LOGIN",
'oauth.admin_roles': 'OAUTH_ADMIN_ROLES',
'oauth.allowed_domains': 'OAUTH_ALLOWED_DOMAINS',
'oauth.allowed_roles': 'OAUTH_ALLOWED_ROLES',
'oauth.audience': 'OAUTH_AUDIENCE',
'oauth.auto_redirect': 'OAUTH_AUTO_REDIRECT',
'oauth.blocked_groups': 'OAUTH_BLOCKED_GROUPS',
'oauth.client.timeout': 'OAUTH_CLIENT_TIMEOUT',
'oauth.enable_group_creation': 'ENABLE_OAUTH_GROUP_CREATION',
'oauth.enable_group_mapping': 'ENABLE_OAUTH_GROUP_MANAGEMENT',
'oauth.enable_role_mapping': 'ENABLE_OAUTH_ROLE_MANAGEMENT',
'oauth.enable_signup': 'ENABLE_OAUTH_SIGNUP',
'oauth.group_default_share': 'OAUTH_GROUP_DEFAULT_SHARE',
'oauth.merge_accounts_by_email': 'OAUTH_MERGE_ACCOUNTS_BY_EMAIL',
'oauth.refresh_token_include_scope': 'OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE',
'oauth.roles_claim': 'OAUTH_ROLES_CLAIM',
'oauth.update_email_on_login': 'OAUTH_UPDATE_EMAIL_ON_LOGIN',
'oauth.update_name_on_login': 'OAUTH_UPDATE_NAME_ON_LOGIN',
'oauth.update_picture_on_login': 'OAUTH_UPDATE_PICTURE_ON_LOGIN',
# OAuth — generic provider paths
"oauth.client_id": "OAUTH_CLIENT_ID",
"oauth.client_secret": "OAUTH_CLIENT_SECRET",
"oauth.code_challenge_method": "OAUTH_CODE_CHALLENGE_METHOD",
"oauth.email_claim": "OAUTH_EMAIL_CLAIM",
"oauth.end_session_endpoint": "OPENID_END_SESSION_ENDPOINT",
"oauth.group_claim": "OAUTH_GROUP_CLAIM",
"oauth.picture_claim": "OAUTH_PICTURE_CLAIM",
"oauth.provider_name": "OAUTH_PROVIDER_NAME",
"oauth.provider_url": "OPENID_PROVIDER_URL",
"oauth.redirect_uri": "OPENID_REDIRECT_URI",
"oauth.scopes": "OAUTH_SCOPES",
"oauth.sub_claim": "OAUTH_SUB_CLAIM",
"oauth.timeout": "OAUTH_TIMEOUT",
"oauth.token_endpoint_auth_method": "OAUTH_TOKEN_ENDPOINT_AUTH_METHOD",
"oauth.username_claim": "OAUTH_USERNAME_CLAIM",
'oauth.client_id': 'OAUTH_CLIENT_ID',
'oauth.client_secret': 'OAUTH_CLIENT_SECRET',
'oauth.code_challenge_method': 'OAUTH_CODE_CHALLENGE_METHOD',
'oauth.email_claim': 'OAUTH_EMAIL_CLAIM',
'oauth.end_session_endpoint': 'OPENID_END_SESSION_ENDPOINT',
'oauth.group_claim': 'OAUTH_GROUP_CLAIM',
'oauth.picture_claim': 'OAUTH_PICTURE_CLAIM',
'oauth.provider_name': 'OAUTH_PROVIDER_NAME',
'oauth.provider_url': 'OPENID_PROVIDER_URL',
'oauth.redirect_uri': 'OPENID_REDIRECT_URI',
'oauth.scopes': 'OAUTH_SCOPES',
'oauth.sub_claim': 'OAUTH_SUB_CLAIM',
'oauth.timeout': 'OAUTH_TIMEOUT',
'oauth.token_endpoint_auth_method': 'OAUTH_TOKEN_ENDPOINT_AUTH_METHOD',
'oauth.username_claim': 'OAUTH_USERNAME_CLAIM',
# OAuth — OIDC nested paths (flattened)
"oauth.oidc.avatar_claim": "OAUTH_PICTURE_CLAIM",
"oauth.oidc.client_id": "OAUTH_CLIENT_ID",
"oauth.oidc.client_secret": "OAUTH_CLIENT_SECRET",
"oauth.oidc.code_challenge_method": "OAUTH_CODE_CHALLENGE_METHOD",
"oauth.oidc.email_claim": "OAUTH_EMAIL_CLAIM",
"oauth.oidc.end_session_endpoint": "OPENID_END_SESSION_ENDPOINT",
"oauth.oidc.group_claim": "OAUTH_GROUP_CLAIM", # renamed from OAUTH_GROUPS_CLAIM
"oauth.oidc.oauth_timeout": "OAUTH_TIMEOUT",
"oauth.oidc.provider_name": "OAUTH_PROVIDER_NAME",
"oauth.oidc.provider_url": "OPENID_PROVIDER_URL",
"oauth.oidc.redirect_uri": "OPENID_REDIRECT_URI",
"oauth.oidc.scopes": "OAUTH_SCOPES",
"oauth.oidc.sub_claim": "OAUTH_SUB_CLAIM",
"oauth.oidc.token_endpoint_auth_method": "OAUTH_TOKEN_ENDPOINT_AUTH_METHOD",
"oauth.oidc.username_claim": "OAUTH_USERNAME_CLAIM",
'oauth.oidc.avatar_claim': 'OAUTH_PICTURE_CLAIM',
'oauth.oidc.client_id': 'OAUTH_CLIENT_ID',
'oauth.oidc.client_secret': 'OAUTH_CLIENT_SECRET',
'oauth.oidc.code_challenge_method': 'OAUTH_CODE_CHALLENGE_METHOD',
'oauth.oidc.email_claim': 'OAUTH_EMAIL_CLAIM',
'oauth.oidc.end_session_endpoint': 'OPENID_END_SESSION_ENDPOINT',
'oauth.oidc.group_claim': 'OAUTH_GROUP_CLAIM', # renamed from OAUTH_GROUPS_CLAIM
'oauth.oidc.oauth_timeout': 'OAUTH_TIMEOUT',
'oauth.oidc.provider_name': 'OAUTH_PROVIDER_NAME',
'oauth.oidc.provider_url': 'OPENID_PROVIDER_URL',
'oauth.oidc.redirect_uri': 'OPENID_REDIRECT_URI',
'oauth.oidc.scopes': 'OAUTH_SCOPES',
'oauth.oidc.sub_claim': 'OAUTH_SUB_CLAIM',
'oauth.oidc.token_endpoint_auth_method': 'OAUTH_TOKEN_ENDPOINT_AUTH_METHOD',
'oauth.oidc.username_claim': 'OAUTH_USERNAME_CLAIM',
# OAuth — provider-specific
"oauth.feishu.client_id": "FEISHU_CLIENT_ID",
"oauth.feishu.client_secret": "FEISHU_CLIENT_SECRET",
"oauth.feishu.redirect_uri": "FEISHU_REDIRECT_URI",
"oauth.feishu.scope": "FEISHU_OAUTH_SCOPE",
"oauth.github.client_id": "GITHUB_CLIENT_ID",
"oauth.github.client_secret": "GITHUB_CLIENT_SECRET",
"oauth.github.redirect_uri": "GITHUB_CLIENT_REDIRECT_URI",
"oauth.github.scope": "GITHUB_CLIENT_SCOPE",
"oauth.google.client_id": "GOOGLE_CLIENT_ID",
"oauth.google.client_secret": "GOOGLE_CLIENT_SECRET",
"oauth.google.redirect_uri": "GOOGLE_REDIRECT_URI",
"oauth.google.scope": "GOOGLE_OAUTH_SCOPE",
"oauth.microsoft.client_id": "MICROSOFT_CLIENT_ID",
"oauth.microsoft.client_secret": "MICROSOFT_CLIENT_SECRET",
"oauth.microsoft.login_base_url": "MICROSOFT_CLIENT_LOGIN_BASE_URL",
"oauth.microsoft.picture_url": "MICROSOFT_CLIENT_PICTURE_URL",
"oauth.microsoft.redirect_uri": "MICROSOFT_REDIRECT_URI",
"oauth.microsoft.scope": "MICROSOFT_OAUTH_SCOPE",
"oauth.microsoft.tenant_id": "MICROSOFT_CLIENT_TENANT_ID",
'oauth.feishu.client_id': 'FEISHU_CLIENT_ID',
'oauth.feishu.client_secret': 'FEISHU_CLIENT_SECRET',
'oauth.feishu.redirect_uri': 'FEISHU_REDIRECT_URI',
'oauth.feishu.scope': 'FEISHU_OAUTH_SCOPE',
'oauth.github.client_id': 'GITHUB_CLIENT_ID',
'oauth.github.client_secret': 'GITHUB_CLIENT_SECRET',
'oauth.github.redirect_uri': 'GITHUB_CLIENT_REDIRECT_URI',
'oauth.github.scope': 'GITHUB_CLIENT_SCOPE',
'oauth.google.client_id': 'GOOGLE_CLIENT_ID',
'oauth.google.client_secret': 'GOOGLE_CLIENT_SECRET',
'oauth.google.redirect_uri': 'GOOGLE_REDIRECT_URI',
'oauth.google.scope': 'GOOGLE_OAUTH_SCOPE',
'oauth.microsoft.client_id': 'MICROSOFT_CLIENT_ID',
'oauth.microsoft.client_secret': 'MICROSOFT_CLIENT_SECRET',
'oauth.microsoft.login_base_url': 'MICROSOFT_CLIENT_LOGIN_BASE_URL',
'oauth.microsoft.picture_url': 'MICROSOFT_CLIENT_PICTURE_URL',
'oauth.microsoft.redirect_uri': 'MICROSOFT_REDIRECT_URI',
'oauth.microsoft.scope': 'MICROSOFT_OAUTH_SCOPE',
'oauth.microsoft.tenant_id': 'MICROSOFT_CLIENT_TENANT_ID',
# Ollama / OpenAI
"ollama.api_configs": "OLLAMA_API_CONFIGS",
"ollama.base_urls": "OLLAMA_BASE_URLS",
"ollama.enable": "ENABLE_OLLAMA_API",
"onedrive.enable": "ENABLE_ONEDRIVE_INTEGRATION",
"onedrive.sharepoint_tenant_id": "ONEDRIVE_SHAREPOINT_TENANT_ID",
"onedrive.sharepoint_url": "ONEDRIVE_SHAREPOINT_URL",
"openai.api_base_urls": "OPENAI_API_BASE_URLS",
"openai.api_configs": "OPENAI_API_CONFIGS",
"openai.api_keys": "OPENAI_API_KEYS",
"openai.enable": "ENABLE_OPENAI_API",
'ollama.api_configs': 'OLLAMA_API_CONFIGS',
'ollama.base_urls': 'OLLAMA_BASE_URLS',
'ollama.enable': 'ENABLE_OLLAMA_API',
'onedrive.enable': 'ENABLE_ONEDRIVE_INTEGRATION',
'onedrive.sharepoint_tenant_id': 'ONEDRIVE_SHAREPOINT_TENANT_ID',
'onedrive.sharepoint_url': 'ONEDRIVE_SHAREPOINT_URL',
'openai.api_base_urls': 'OPENAI_API_BASE_URLS',
'openai.api_configs': 'OPENAI_API_CONFIGS',
'openai.api_keys': 'OPENAI_API_KEYS',
'openai.enable': 'ENABLE_OPENAI_API',
# RAG
"rag.content_extraction_engine": "CONTENT_EXTRACTION_ENGINE",
"rag.datalab_marker_use_llm": "DATALAB_MARKER_USE_LLM",
"rag.mistral_ocr_api_base_url": "MISTRAL_OCR_API_BASE_URL",
"rag.azure_openai.api_key": "RAG_AZURE_OPENAI_API_KEY",
"rag.azure_openai.api_version": "RAG_AZURE_OPENAI_API_VERSION",
"rag.azure_openai.base_url": "RAG_AZURE_OPENAI_BASE_URL",
"rag.bypass_embedding_and_retrieval": "BYPASS_EMBEDDING_AND_RETRIEVAL",
"rag.chunk_min_size_target": "CHUNK_MIN_SIZE_TARGET",
"rag.chunk_overlap": "CHUNK_OVERLAP",
"rag.chunk_size": "CHUNK_SIZE",
"rag.datalab_marker_additional_config": "DATALAB_MARKER_ADDITIONAL_CONFIG",
"rag.datalab_marker_api_base_url": "DATALAB_MARKER_API_BASE_URL",
"rag.datalab_marker_api_key": "DATALAB_MARKER_API_KEY",
"rag.datalab_marker_disable_image_extraction": "DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION",
"rag.datalab_marker_force_ocr": "DATALAB_MARKER_FORCE_OCR",
"rag.datalab_marker_format_lines": "DATALAB_MARKER_FORMAT_LINES",
"rag.datalab_marker_output_format": "DATALAB_MARKER_OUTPUT_FORMAT",
"rag.datalab_marker_paginate": "DATALAB_MARKER_PAGINATE",
"rag.datalab_marker_skip_cache": "DATALAB_MARKER_SKIP_CACHE",
"rag.datalab_marker_strip_existing_ocr": "DATALAB_MARKER_STRIP_EXISTING_OCR",
"rag.docling_api_key": "DOCLING_API_KEY",
"rag.docling_params": "DOCLING_PARAMS",
"rag.docling_server_url": "DOCLING_SERVER_URL",
"rag.document_intelligence_endpoint": "DOCUMENT_INTELLIGENCE_ENDPOINT",
"rag.document_intelligence_key": "DOCUMENT_INTELLIGENCE_KEY",
"rag.document_intelligence_model": "DOCUMENT_INTELLIGENCE_MODEL",
"rag.embedding_batch_size": "RAG_EMBEDDING_BATCH_SIZE",
"rag.embedding_concurrent_requests": "RAG_EMBEDDING_CONCURRENT_REQUESTS",
"rag.embedding_engine": "RAG_EMBEDDING_ENGINE",
"rag.embedding_model": "RAG_EMBEDDING_MODEL",
"rag.enable_async_embedding": "ENABLE_ASYNC_EMBEDDING",
"rag.enable_hybrid_search": "ENABLE_RAG_HYBRID_SEARCH",
"rag.enable_hybrid_search_enriched_texts": "ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS",
"rag.enable_markdown_header_text_splitter": "ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER",
"rag.external_document_loader_api_key": "EXTERNAL_DOCUMENT_LOADER_API_KEY",
"rag.external_document_loader_url": "EXTERNAL_DOCUMENT_LOADER_URL",
"rag.external_reranker_api_key": "RAG_EXTERNAL_RERANKER_API_KEY",
"rag.external_reranker_timeout": "RAG_EXTERNAL_RERANKER_TIMEOUT",
"rag.external_reranker_url": "RAG_EXTERNAL_RERANKER_URL",
"rag.file.allowed_extensions": "RAG_ALLOWED_FILE_EXTENSIONS",
"rag.file.max_count": "RAG_FILE_MAX_COUNT",
"rag.file.max_size": "RAG_FILE_MAX_SIZE",
"rag.full_context": "RAG_FULL_CONTEXT",
"rag.hybrid_bm25_weight": "RAG_HYBRID_BM25_WEIGHT",
"rag.mineru_api_key": "MINERU_API_KEY",
"rag.mineru_api_mode": "MINERU_API_MODE",
"rag.mineru_api_timeout": "MINERU_API_TIMEOUT",
"rag.mineru_api_url": "MINERU_API_URL",
"rag.mineru_file_extensions": "MINERU_FILE_EXTENSIONS",
"rag.mineru_params": "MINERU_PARAMS",
"rag.mistral_ocr_api_key": "MISTRAL_OCR_API_KEY",
"rag.ollama.key": "RAG_OLLAMA_API_KEY",
"rag.ollama.url": "RAG_OLLAMA_BASE_URL",
"rag.openai_api_base_url": "RAG_OPENAI_API_BASE_URL",
"rag.openai_api_key": "RAG_OPENAI_API_KEY",
"rag.paddleocr_vl_base_url": "PADDLEOCR_VL_BASE_URL",
"rag.paddleocr_vl_token": "PADDLEOCR_VL_TOKEN",
"rag.pdf_extract_images": "PDF_EXTRACT_IMAGES",
"rag.pdf_loader_mode": "PDF_LOADER_MODE",
"rag.relevance_threshold": "RAG_RELEVANCE_THRESHOLD",
"rag.reranking_batch_size": "RAG_RERANKING_BATCH_SIZE",
"rag.reranking_engine": "RAG_RERANKING_ENGINE",
"rag.reranking_model": "RAG_RERANKING_MODEL",
"rag.template": "RAG_TEMPLATE",
"rag.text_splitter": "RAG_TEXT_SPLITTER",
"rag.tika_server_url": "TIKA_SERVER_URL",
"rag.tiktoken_encoding_name": "TIKTOKEN_ENCODING_NAME",
"rag.top_k": "RAG_TOP_K",
"rag.top_k_reranker": "RAG_TOP_K_RERANKER",
'rag.content_extraction_engine': 'CONTENT_EXTRACTION_ENGINE',
'rag.datalab_marker_use_llm': 'DATALAB_MARKER_USE_LLM',
'rag.mistral_ocr_api_base_url': 'MISTRAL_OCR_API_BASE_URL',
'rag.azure_openai.api_key': 'RAG_AZURE_OPENAI_API_KEY',
'rag.azure_openai.api_version': 'RAG_AZURE_OPENAI_API_VERSION',
'rag.azure_openai.base_url': 'RAG_AZURE_OPENAI_BASE_URL',
'rag.bypass_embedding_and_retrieval': 'BYPASS_EMBEDDING_AND_RETRIEVAL',
'rag.chunk_min_size_target': 'CHUNK_MIN_SIZE_TARGET',
'rag.chunk_overlap': 'CHUNK_OVERLAP',
'rag.chunk_size': 'CHUNK_SIZE',
'rag.datalab_marker_additional_config': 'DATALAB_MARKER_ADDITIONAL_CONFIG',
'rag.datalab_marker_api_base_url': 'DATALAB_MARKER_API_BASE_URL',
'rag.datalab_marker_api_key': 'DATALAB_MARKER_API_KEY',
'rag.datalab_marker_disable_image_extraction': 'DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION',
'rag.datalab_marker_force_ocr': 'DATALAB_MARKER_FORCE_OCR',
'rag.datalab_marker_format_lines': 'DATALAB_MARKER_FORMAT_LINES',
'rag.datalab_marker_output_format': 'DATALAB_MARKER_OUTPUT_FORMAT',
'rag.datalab_marker_paginate': 'DATALAB_MARKER_PAGINATE',
'rag.datalab_marker_skip_cache': 'DATALAB_MARKER_SKIP_CACHE',
'rag.datalab_marker_strip_existing_ocr': 'DATALAB_MARKER_STRIP_EXISTING_OCR',
'rag.docling_api_key': 'DOCLING_API_KEY',
'rag.docling_params': 'DOCLING_PARAMS',
'rag.docling_server_url': 'DOCLING_SERVER_URL',
'rag.document_intelligence_endpoint': 'DOCUMENT_INTELLIGENCE_ENDPOINT',
'rag.document_intelligence_key': 'DOCUMENT_INTELLIGENCE_KEY',
'rag.document_intelligence_model': 'DOCUMENT_INTELLIGENCE_MODEL',
'rag.embedding_batch_size': 'RAG_EMBEDDING_BATCH_SIZE',
'rag.embedding_concurrent_requests': 'RAG_EMBEDDING_CONCURRENT_REQUESTS',
'rag.embedding_engine': 'RAG_EMBEDDING_ENGINE',
'rag.embedding_model': 'RAG_EMBEDDING_MODEL',
'rag.enable_async_embedding': 'ENABLE_ASYNC_EMBEDDING',
'rag.enable_hybrid_search': 'ENABLE_RAG_HYBRID_SEARCH',
'rag.enable_hybrid_search_enriched_texts': 'ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS',
'rag.enable_markdown_header_text_splitter': 'ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER',
'rag.external_document_loader_api_key': 'EXTERNAL_DOCUMENT_LOADER_API_KEY',
'rag.external_document_loader_url': 'EXTERNAL_DOCUMENT_LOADER_URL',
'rag.external_reranker_api_key': 'RAG_EXTERNAL_RERANKER_API_KEY',
'rag.external_reranker_timeout': 'RAG_EXTERNAL_RERANKER_TIMEOUT',
'rag.external_reranker_url': 'RAG_EXTERNAL_RERANKER_URL',
'rag.file.allowed_extensions': 'RAG_ALLOWED_FILE_EXTENSIONS',
'rag.file.max_count': 'RAG_FILE_MAX_COUNT',
'rag.file.max_size': 'RAG_FILE_MAX_SIZE',
'rag.full_context': 'RAG_FULL_CONTEXT',
'rag.hybrid_bm25_weight': 'RAG_HYBRID_BM25_WEIGHT',
'rag.mineru_api_key': 'MINERU_API_KEY',
'rag.mineru_api_mode': 'MINERU_API_MODE',
'rag.mineru_api_timeout': 'MINERU_API_TIMEOUT',
'rag.mineru_api_url': 'MINERU_API_URL',
'rag.mineru_file_extensions': 'MINERU_FILE_EXTENSIONS',
'rag.mineru_params': 'MINERU_PARAMS',
'rag.mistral_ocr_api_key': 'MISTRAL_OCR_API_KEY',
'rag.ollama.key': 'RAG_OLLAMA_API_KEY',
'rag.ollama.url': 'RAG_OLLAMA_BASE_URL',
'rag.openai_api_base_url': 'RAG_OPENAI_API_BASE_URL',
'rag.openai_api_key': 'RAG_OPENAI_API_KEY',
'rag.paddleocr_vl_base_url': 'PADDLEOCR_VL_BASE_URL',
'rag.paddleocr_vl_token': 'PADDLEOCR_VL_TOKEN',
'rag.pdf_extract_images': 'PDF_EXTRACT_IMAGES',
'rag.pdf_loader_mode': 'PDF_LOADER_MODE',
'rag.relevance_threshold': 'RAG_RELEVANCE_THRESHOLD',
'rag.reranking_batch_size': 'RAG_RERANKING_BATCH_SIZE',
'rag.reranking_engine': 'RAG_RERANKING_ENGINE',
'rag.reranking_model': 'RAG_RERANKING_MODEL',
'rag.template': 'RAG_TEMPLATE',
'rag.text_splitter': 'RAG_TEXT_SPLITTER',
'rag.tika_server_url': 'TIKA_SERVER_URL',
'rag.tiktoken_encoding_name': 'TIKTOKEN_ENCODING_NAME',
'rag.top_k': 'RAG_TOP_K',
'rag.top_k_reranker': 'RAG_TOP_K_RERANKER',
# RAG — Web
"rag.web.fetch.max_content_length": "WEB_FETCH_MAX_CONTENT_LENGTH",
"rag.web.loader.concurrent_requests": "WEB_LOADER_CONCURRENT_REQUESTS",
"rag.web.loader.engine": "WEB_LOADER_ENGINE",
"rag.web.loader.external_web_loader_api_key": "EXTERNAL_WEB_LOADER_API_KEY",
"rag.web.loader.external_web_loader_url": "EXTERNAL_WEB_LOADER_URL",
"rag.web.loader.firecrawl_api_key": "FIRECRAWL_API_KEY",
"rag.web.loader.firecrawl_api_url": "FIRECRAWL_API_BASE_URL",
"rag.web.loader.firecrawl_timeout": "FIRECRAWL_TIMEOUT",
"rag.web.loader.playwright_timeout": "PLAYWRIGHT_TIMEOUT",
"rag.web.loader.playwright_ws_url": "PLAYWRIGHT_WS_URL",
"rag.web.loader.ssl_verification": "ENABLE_WEB_LOADER_SSL_VERIFICATION",
"rag.web.loader.timeout": "WEB_LOADER_TIMEOUT",
"rag.web.search.azure_ai_search_api_key": "AZURE_AI_SEARCH_API_KEY",
"rag.web.search.azure_ai_search_endpoint": "AZURE_AI_SEARCH_ENDPOINT",
"rag.web.search.azure_ai_search_index_name": "AZURE_AI_SEARCH_INDEX_NAME",
"rag.web.search.bing_search_v7_endpoint": "BING_SEARCH_V7_ENDPOINT",
"rag.web.search.bing_search_v7_subscription_key": "BING_SEARCH_V7_SUBSCRIPTION_KEY",
"rag.web.search.bocha_search_api_key": "BOCHA_SEARCH_API_KEY",
"rag.web.search.brave_search_api_key": "BRAVE_SEARCH_API_KEY",
"rag.web.search.brave_search_context_tokens": "BRAVE_SEARCH_CONTEXT_TOKENS",
"rag.web.search.bypass_embedding_and_retrieval": "BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL",
"rag.web.search.bypass_web_loader": "BYPASS_WEB_SEARCH_WEB_LOADER",
"rag.web.search.concurrent_requests": "WEB_SEARCH_CONCURRENT_REQUESTS",
"rag.web.search.ddgs_backend": "DDGS_BACKEND",
"rag.web.search.domain.filter_list": "WEB_SEARCH_DOMAIN_FILTER_LIST",
"rag.web.search.enable": "ENABLE_WEB_SEARCH",
"rag.web.search.engine": "WEB_SEARCH_ENGINE",
"rag.web.search.exa_api_key": "EXA_API_KEY",
"rag.web.search.external_web_search_api_key": "EXTERNAL_WEB_SEARCH_API_KEY",
"rag.web.search.external_web_search_url": "EXTERNAL_WEB_SEARCH_URL",
"rag.web.search.google_pse_api_key": "GOOGLE_PSE_API_KEY",
"rag.web.search.google_pse_engine_id": "GOOGLE_PSE_ENGINE_ID",
"rag.web.search.jina_api_base_url": "JINA_API_BASE_URL",
"rag.web.search.jina_api_key": "JINA_API_KEY",
"rag.web.search.kagi_search_api_key": "KAGI_SEARCH_API_KEY",
"rag.web.search.linkup_api_key": "LINKUP_API_KEY",
"rag.web.search.linkup_search_params": "LINKUP_SEARCH_PARAMS",
"rag.web.search.mojeek_search_api_key": "MOJEEK_SEARCH_API_KEY",
"rag.web.search.ollama_cloud_api_key": "OLLAMA_CLOUD_WEB_SEARCH_API_KEY",
"rag.web.search.perplexity_api_key": "PERPLEXITY_API_KEY",
"rag.web.search.perplexity_model": "PERPLEXITY_MODEL",
"rag.web.search.perplexity_search_api_url": "PERPLEXITY_SEARCH_API_URL",
"rag.web.search.perplexity_search_context_usage": "PERPLEXITY_SEARCH_CONTEXT_USAGE",
"rag.web.search.result_count": "WEB_SEARCH_RESULT_COUNT",
"rag.web.search.searchapi_api_key": "SEARCHAPI_API_KEY",
"rag.web.search.searchapi_engine": "SEARCHAPI_ENGINE",
"rag.web.search.searxng_language": "SEARXNG_LANGUAGE",
"rag.web.search.searxng_query_url": "SEARXNG_QUERY_URL",
"rag.web.search.serpapi_api_key": "SERPAPI_API_KEY",
"rag.web.search.serpapi_engine": "SERPAPI_ENGINE",
"rag.web.search.serper_api_key": "SERPER_API_KEY",
"rag.web.search.serply_api_key": "SERPLY_API_KEY",
"rag.web.search.serpstack_api_key": "SERPSTACK_API_KEY",
"rag.web.search.serpstack_https": "SERPSTACK_HTTPS",
"rag.web.search.sougou_api_sid": "SOUGOU_API_SID",
"rag.web.search.sougou_api_sk": "SOUGOU_API_SK",
"rag.web.search.tavily_api_key": "TAVILY_API_KEY",
"rag.web.search.tavily_extract_depth": "TAVILY_EXTRACT_DEPTH",
"rag.web.search.trust_env": "WEB_SEARCH_TRUST_ENV",
"rag.web.search.yacy_password": "YACY_PASSWORD",
"rag.web.search.yacy_query_url": "YACY_QUERY_URL",
"rag.web.search.yacy_username": "YACY_USERNAME",
"rag.web.search.yandex_web_search_api_key": "YANDEX_WEB_SEARCH_API_KEY",
"rag.web.search.yandex_web_search_config": "YANDEX_WEB_SEARCH_CONFIG",
"rag.web.search.yandex_web_search_url": "YANDEX_WEB_SEARCH_URL",
"rag.web.search.youcom_api_key": "YOUCOM_API_KEY",
"rag.youtube_loader_language": "YOUTUBE_LOADER_LANGUAGE",
"rag.youtube_loader_proxy_url": "YOUTUBE_LOADER_PROXY_URL",
'rag.web.fetch.max_content_length': 'WEB_FETCH_MAX_CONTENT_LENGTH',
'rag.web.loader.concurrent_requests': 'WEB_LOADER_CONCURRENT_REQUESTS',
'rag.web.loader.engine': 'WEB_LOADER_ENGINE',
'rag.web.loader.external_web_loader_api_key': 'EXTERNAL_WEB_LOADER_API_KEY',
'rag.web.loader.external_web_loader_url': 'EXTERNAL_WEB_LOADER_URL',
'rag.web.loader.firecrawl_api_key': 'FIRECRAWL_API_KEY',
'rag.web.loader.firecrawl_api_url': 'FIRECRAWL_API_BASE_URL',
'rag.web.loader.firecrawl_timeout': 'FIRECRAWL_TIMEOUT',
'rag.web.loader.playwright_timeout': 'PLAYWRIGHT_TIMEOUT',
'rag.web.loader.playwright_ws_url': 'PLAYWRIGHT_WS_URL',
'rag.web.loader.ssl_verification': 'ENABLE_WEB_LOADER_SSL_VERIFICATION',
'rag.web.loader.timeout': 'WEB_LOADER_TIMEOUT',
'rag.web.search.azure_ai_search_api_key': 'AZURE_AI_SEARCH_API_KEY',
'rag.web.search.azure_ai_search_endpoint': 'AZURE_AI_SEARCH_ENDPOINT',
'rag.web.search.azure_ai_search_index_name': 'AZURE_AI_SEARCH_INDEX_NAME',
'rag.web.search.bing_search_v7_endpoint': 'BING_SEARCH_V7_ENDPOINT',
'rag.web.search.bing_search_v7_subscription_key': 'BING_SEARCH_V7_SUBSCRIPTION_KEY',
'rag.web.search.bocha_search_api_key': 'BOCHA_SEARCH_API_KEY',
'rag.web.search.brave_search_api_key': 'BRAVE_SEARCH_API_KEY',
'rag.web.search.brave_search_context_tokens': 'BRAVE_SEARCH_CONTEXT_TOKENS',
'rag.web.search.bypass_embedding_and_retrieval': 'BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL',
'rag.web.search.bypass_web_loader': 'BYPASS_WEB_SEARCH_WEB_LOADER',
'rag.web.search.concurrent_requests': 'WEB_SEARCH_CONCURRENT_REQUESTS',
'rag.web.search.ddgs_backend': 'DDGS_BACKEND',
'rag.web.search.domain.filter_list': 'WEB_SEARCH_DOMAIN_FILTER_LIST',
'rag.web.search.enable': 'ENABLE_WEB_SEARCH',
'rag.web.search.engine': 'WEB_SEARCH_ENGINE',
'rag.web.search.exa_api_key': 'EXA_API_KEY',
'rag.web.search.external_web_search_api_key': 'EXTERNAL_WEB_SEARCH_API_KEY',
'rag.web.search.external_web_search_url': 'EXTERNAL_WEB_SEARCH_URL',
'rag.web.search.google_pse_api_key': 'GOOGLE_PSE_API_KEY',
'rag.web.search.google_pse_engine_id': 'GOOGLE_PSE_ENGINE_ID',
'rag.web.search.jina_api_base_url': 'JINA_API_BASE_URL',
'rag.web.search.jina_api_key': 'JINA_API_KEY',
'rag.web.search.kagi_search_api_key': 'KAGI_SEARCH_API_KEY',
'rag.web.search.linkup_api_key': 'LINKUP_API_KEY',
'rag.web.search.linkup_search_params': 'LINKUP_SEARCH_PARAMS',
'rag.web.search.mojeek_search_api_key': 'MOJEEK_SEARCH_API_KEY',
'rag.web.search.ollama_cloud_api_key': 'OLLAMA_CLOUD_WEB_SEARCH_API_KEY',
'rag.web.search.perplexity_api_key': 'PERPLEXITY_API_KEY',
'rag.web.search.perplexity_model': 'PERPLEXITY_MODEL',
'rag.web.search.perplexity_search_api_url': 'PERPLEXITY_SEARCH_API_URL',
'rag.web.search.perplexity_search_context_usage': 'PERPLEXITY_SEARCH_CONTEXT_USAGE',
'rag.web.search.result_count': 'WEB_SEARCH_RESULT_COUNT',
'rag.web.search.searchapi_api_key': 'SEARCHAPI_API_KEY',
'rag.web.search.searchapi_engine': 'SEARCHAPI_ENGINE',
'rag.web.search.searxng_language': 'SEARXNG_LANGUAGE',
'rag.web.search.searxng_query_url': 'SEARXNG_QUERY_URL',
'rag.web.search.serpapi_api_key': 'SERPAPI_API_KEY',
'rag.web.search.serpapi_engine': 'SERPAPI_ENGINE',
'rag.web.search.serper_api_key': 'SERPER_API_KEY',
'rag.web.search.serply_api_key': 'SERPLY_API_KEY',
'rag.web.search.serpstack_api_key': 'SERPSTACK_API_KEY',
'rag.web.search.serpstack_https': 'SERPSTACK_HTTPS',
'rag.web.search.sougou_api_sid': 'SOUGOU_API_SID',
'rag.web.search.sougou_api_sk': 'SOUGOU_API_SK',
'rag.web.search.tavily_api_key': 'TAVILY_API_KEY',
'rag.web.search.tavily_extract_depth': 'TAVILY_EXTRACT_DEPTH',
'rag.web.search.trust_env': 'WEB_SEARCH_TRUST_ENV',
'rag.web.search.yacy_password': 'YACY_PASSWORD',
'rag.web.search.yacy_query_url': 'YACY_QUERY_URL',
'rag.web.search.yacy_username': 'YACY_USERNAME',
'rag.web.search.yandex_web_search_api_key': 'YANDEX_WEB_SEARCH_API_KEY',
'rag.web.search.yandex_web_search_config': 'YANDEX_WEB_SEARCH_CONFIG',
'rag.web.search.yandex_web_search_url': 'YANDEX_WEB_SEARCH_URL',
'rag.web.search.youcom_api_key': 'YOUCOM_API_KEY',
'rag.youtube_loader_language': 'YOUTUBE_LOADER_LANGUAGE',
'rag.youtube_loader_proxy_url': 'YOUTUBE_LOADER_PROXY_URL',
# Tasks
"task.autocomplete.enable": "ENABLE_AUTOCOMPLETE_GENERATION",
"task.autocomplete.input_max_length": "AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH",
"task.autocomplete.prompt_template": "AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE",
"task.follow_up.enable": "ENABLE_FOLLOW_UP_GENERATION",
"task.follow_up.prompt_template": "FOLLOW_UP_GENERATION_PROMPT_TEMPLATE",
"task.image.prompt_template": "IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE",
"task.model.default": "TASK_MODEL",
"task.model.external": "TASK_MODEL_EXTERNAL",
"task.query.prompt_template": "QUERY_GENERATION_PROMPT_TEMPLATE",
"task.query.retrieval.enable": "ENABLE_RETRIEVAL_QUERY_GENERATION",
"task.query.search.enable": "ENABLE_SEARCH_QUERY_GENERATION",
"task.tags.enable": "ENABLE_TAGS_GENERATION",
"task.tags.prompt_template": "TAGS_GENERATION_PROMPT_TEMPLATE",
"task.title.enable": "ENABLE_TITLE_GENERATION",
"task.title.prompt_template": "TITLE_GENERATION_PROMPT_TEMPLATE",
"task.tools.prompt_template": "TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE",
"task.voice.prompt.enable": "ENABLE_VOICE_MODE_PROMPT",
"task.voice.prompt_template": "VOICE_MODE_PROMPT_TEMPLATE",
'task.autocomplete.enable': 'ENABLE_AUTOCOMPLETE_GENERATION',
'task.autocomplete.input_max_length': 'AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH',
'task.autocomplete.prompt_template': 'AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE',
'task.follow_up.enable': 'ENABLE_FOLLOW_UP_GENERATION',
'task.follow_up.prompt_template': 'FOLLOW_UP_GENERATION_PROMPT_TEMPLATE',
'task.image.prompt_template': 'IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE',
'task.model.default': 'TASK_MODEL',
'task.model.external': 'TASK_MODEL_EXTERNAL',
'task.query.prompt_template': 'QUERY_GENERATION_PROMPT_TEMPLATE',
'task.query.retrieval.enable': 'ENABLE_RETRIEVAL_QUERY_GENERATION',
'task.query.search.enable': 'ENABLE_SEARCH_QUERY_GENERATION',
'task.tags.enable': 'ENABLE_TAGS_GENERATION',
'task.tags.prompt_template': 'TAGS_GENERATION_PROMPT_TEMPLATE',
'task.title.enable': 'ENABLE_TITLE_GENERATION',
'task.title.prompt_template': 'TITLE_GENERATION_PROMPT_TEMPLATE',
'task.tools.prompt_template': 'TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE',
'task.voice.prompt.enable': 'ENABLE_VOICE_MODE_PROMPT',
'task.voice.prompt_template': 'VOICE_MODE_PROMPT_TEMPLATE',
# Misc
"terminal_server.connections": "TERMINAL_SERVER_CONNECTIONS",
"tool_server.connections": "TOOL_SERVER_CONNECTIONS",
"ui.banners": "WEBUI_BANNERS",
"ui.default_group_id": "DEFAULT_GROUP_ID",
"ui.default_locale": "DEFAULT_LOCALE",
"ui.default_models": "DEFAULT_MODELS",
"ui.default_pinned_models": "DEFAULT_PINNED_MODELS",
"ui.default_user_role": "DEFAULT_USER_ROLE",
"ui.enable_community_sharing": "ENABLE_COMMUNITY_SHARING",
"ui.enable_login_form": "ENABLE_LOGIN_FORM",
"ui.enable_message_rating": "ENABLE_MESSAGE_RATING",
"ui.enable_password_change_form": "ENABLE_PASSWORD_CHANGE_FORM",
"ui.enable_signup": "ENABLE_SIGNUP",
"ui.enable_user_webhooks": "ENABLE_USER_WEBHOOKS",
"ui.model_order_list": "MODEL_ORDER_LIST",
"ui.pending_user_overlay_content": "PENDING_USER_OVERLAY_CONTENT",
"ui.pending_user_overlay_title": "PENDING_USER_OVERLAY_TITLE",
"ui.prompt_suggestions": "DEFAULT_PROMPT_SUGGESTIONS",
"ui.watermark": "RESPONSE_WATERMARK",
"user.permissions": "USER_PERMISSIONS",
"users.enable_status": "ENABLE_USER_STATUS",
"webhook_url": "WEBHOOK_URL",
"webui.url": "WEBUI_URL",
'terminal_server.connections': 'TERMINAL_SERVER_CONNECTIONS',
'tool_server.connections': 'TOOL_SERVER_CONNECTIONS',
'ui.banners': 'WEBUI_BANNERS',
'ui.default_group_id': 'DEFAULT_GROUP_ID',
'ui.default_locale': 'DEFAULT_LOCALE',
'ui.default_models': 'DEFAULT_MODELS',
'ui.default_pinned_models': 'DEFAULT_PINNED_MODELS',
'ui.default_user_role': 'DEFAULT_USER_ROLE',
'ui.enable_community_sharing': 'ENABLE_COMMUNITY_SHARING',
'ui.enable_login_form': 'ENABLE_LOGIN_FORM',
'ui.enable_message_rating': 'ENABLE_MESSAGE_RATING',
'ui.enable_password_change_form': 'ENABLE_PASSWORD_CHANGE_FORM',
'ui.enable_signup': 'ENABLE_SIGNUP',
'ui.enable_user_webhooks': 'ENABLE_USER_WEBHOOKS',
'ui.model_order_list': 'MODEL_ORDER_LIST',
'ui.pending_user_overlay_content': 'PENDING_USER_OVERLAY_CONTENT',
'ui.pending_user_overlay_title': 'PENDING_USER_OVERLAY_TITLE',
'ui.prompt_suggestions': 'DEFAULT_PROMPT_SUGGESTIONS',
'ui.watermark': 'RESPONSE_WATERMARK',
'user.permissions': 'USER_PERMISSIONS',
'users.enable_status': 'ENABLE_USER_STATUS',
'webhook_url': 'WEBHOOK_URL',
'webui.url': 'WEBUI_URL',
}
STORAGE_KEY_REWRITES = {
"oauth.refresh_token_include_scope": "oauth.refresh_token.include_scope",
"rag.openai_api_base_url": "rag.openai.api_base_url",
"rag.openai_api_key": "rag.openai.api_key",
"rag.ollama.url": "rag.ollama.base_url",
"rag.ollama.key": "rag.ollama.api_key",
"oauth.oidc.avatar_claim": "oauth.picture_claim",
"oauth.oidc.client_id": "oauth.client_id",
"oauth.oidc.client_secret": "oauth.client_secret",
"oauth.oidc.code_challenge_method": "oauth.code_challenge_method",
"oauth.oidc.email_claim": "oauth.email_claim",
"oauth.oidc.end_session_endpoint": "oauth.end_session_endpoint",
"oauth.oidc.group_claim": "oauth.group_claim",
"oauth.oidc.oauth_timeout": "oauth.timeout",
"oauth.oidc.provider_name": "oauth.provider_name",
"oauth.oidc.provider_url": "oauth.provider_url",
"oauth.oidc.redirect_uri": "oauth.redirect_uri",
"oauth.oidc.scopes": "oauth.scopes",
"oauth.oidc.sub_claim": "oauth.sub_claim",
"oauth.oidc.token_endpoint_auth_method": "oauth.token_endpoint_auth_method",
"oauth.oidc.username_claim": "oauth.username_claim",
'oauth.refresh_token_include_scope': 'oauth.refresh_token.include_scope',
'rag.openai_api_base_url': 'rag.openai.api_base_url',
'rag.openai_api_key': 'rag.openai.api_key',
'rag.ollama.url': 'rag.ollama.base_url',
'rag.ollama.key': 'rag.ollama.api_key',
'oauth.oidc.avatar_claim': 'oauth.picture_claim',
'oauth.oidc.client_id': 'oauth.client_id',
'oauth.oidc.client_secret': 'oauth.client_secret',
'oauth.oidc.code_challenge_method': 'oauth.code_challenge_method',
'oauth.oidc.email_claim': 'oauth.email_claim',
'oauth.oidc.end_session_endpoint': 'oauth.end_session_endpoint',
'oauth.oidc.group_claim': 'oauth.group_claim',
'oauth.oidc.oauth_timeout': 'oauth.timeout',
'oauth.oidc.provider_name': 'oauth.provider_name',
'oauth.oidc.provider_url': 'oauth.provider_url',
'oauth.oidc.redirect_uri': 'oauth.redirect_uri',
'oauth.oidc.scopes': 'oauth.scopes',
'oauth.oidc.sub_claim': 'oauth.sub_claim',
'oauth.oidc.token_endpoint_auth_method': 'oauth.token_endpoint_auth_method',
'oauth.oidc.username_claim': 'oauth.username_claim',
}
LEGACY_KEY_TO_STORAGE_KEY = {
legacy_key: STORAGE_KEY_REWRITES.get(blob_path, blob_path)
for blob_path, legacy_key in BLOB_PATH_TO_KEY.items()
legacy_key: STORAGE_KEY_REWRITES.get(blob_path, blob_path) for blob_path, legacy_key in BLOB_PATH_TO_KEY.items()
}
@ -466,7 +464,9 @@ def upgrade() -> None:
conn = op.get_bind()
inspector = sa.inspect(conn)
table_names = set(inspector.get_table_names())
config_columns = {column['name'] for column in inspector.get_columns('config')} if 'config' in table_names else set()
config_columns = (
{column['name'] for column in inspector.get_columns('config')} if 'config' in table_names else set()
)
has_old_config = {'id', 'data'}.issubset(config_columns)
has_new_config = {'key', 'value'}.issubset(config_columns)
@ -481,9 +481,7 @@ def upgrade() -> None:
blob_data = {}
if has_old_config:
try:
result = conn.execute(
sa.select(old_config.c.data).order_by(old_config.c.id.desc()).limit(1)
)
result = conn.execute(sa.select(old_config.c.data).order_by(old_config.c.id.desc()).limit(1))
row = result.fetchone()
if row and row[0]:
raw = row[0]
@ -538,10 +536,7 @@ def upgrade() -> None:
now = int(time.time())
op.bulk_insert(
new_config,
[
{'key': k, 'value': v, 'updated_at': now}
for k, v in rows.items()
],
[{'key': k, 'value': v, 'updated_at': now} for k, v in rows.items()],
)
@ -557,7 +552,9 @@ def downgrade() -> None:
op.rename_table('config_old', 'config')
return
config_columns = {column['name'] for column in inspector.get_columns('config')} if 'config' in table_names else set()
config_columns = (
{column['name'] for column in inspector.get_columns('config')} if 'config' in table_names else set()
)
has_per_key_config = {'key', 'value'}.issubset(config_columns)
blob_data = {}

View file

@ -222,11 +222,7 @@ class FeedbackTable:
) -> FeedbackListResponse:
async with get_async_db_context(db) as db:
stmt = select(Feedback, User).join(User, Feedback.user_id == User.id)
count_stmt = (
select(func.count(Feedback.id))
.select_from(Feedback)
.join(User, Feedback.user_id == User.id)
)
count_stmt = select(func.count(Feedback.id)).select_from(Feedback).join(User, Feedback.user_id == User.id)
if filter:
# Apply model_id filter (exact match)

View file

@ -144,9 +144,7 @@ class FolderTable:
except Exception:
return None
async def get_folder_by_id(
self, id: str, db: Optional[AsyncSession] = None
) -> Optional[FolderModel]:
async def get_folder_by_id(self, id: str, db: Optional[AsyncSession] = None) -> Optional[FolderModel]:
"""Fetch folder by ID only (no user_id filter). Used for shared access."""
try:
async with get_async_db_context(db) as db:
@ -159,8 +157,7 @@ class FolderTable:
return None
async def get_shared_folder_ids_for_user(
self, user_id: str, user_group_ids: set[str],
db: Optional[AsyncSession] = None
self, user_id: str, user_group_ids: set[str], db: Optional[AsyncSession] = None
) -> dict[str, str]:
"""
Returns {folder_id: highest_permission} for all folders shared with user.
@ -175,8 +172,7 @@ class FolderTable:
]
if user_group_ids:
conditions.append(
and_(AccessGrant.principal_type == 'group',
AccessGrant.principal_id.in_(user_group_ids))
and_(AccessGrant.principal_type == 'group', AccessGrant.principal_id.in_(user_group_ids))
)
result = await db.execute(
select(AccessGrant).filter(

View file

@ -164,11 +164,7 @@ class FunctionsTable:
func_data['updated_at'] = int(time.time())
if func.id in existing_ids:
await db.execute(
update(Function)
.filter_by(id=func.id)
.values(**func_data)
)
await db.execute(update(Function).filter_by(id=func.id).values(**func_data))
else:
new_func = Function(**func_data)
db.add(new_func)

View file

@ -210,7 +210,9 @@ class MemoriesTable:
)
db.add(memory)
await db.flush()
results.append({'action': action, 'status': 'created', 'memory': MemoryModel.model_validate(memory)})
results.append(
{'action': action, 'status': 'created', 'memory': MemoryModel.model_validate(memory)}
)
elif action == 'replace':
memory_id = operation.get('id')
@ -228,7 +230,9 @@ class MemoriesTable:
memory.meta = {**(memory.meta or {}), **operation.get('meta')}
memory.updated_at = now
await db.flush()
results.append({'action': action, 'status': 'updated', 'memory': MemoryModel.model_validate(memory)})
results.append(
{'action': action, 'status': 'updated', 'memory': MemoryModel.model_validate(memory)}
)
elif action == 'move':
memory_id = operation.get('id')
@ -241,7 +245,9 @@ class MemoriesTable:
memory.meta = {**(memory.meta or {}), **operation.get('meta')}
memory.updated_at = now
await db.flush()
results.append({'action': action, 'status': 'updated', 'memory': MemoryModel.model_validate(memory)})
results.append(
{'action': action, 'status': 'updated', 'memory': MemoryModel.model_validate(memory)}
)
elif action == 'remove':
memory_id = operation.get('id')

View file

@ -540,10 +540,7 @@ class MessageTable:
)
grouped[mid][reaction.name]['count'] += 1
return {
mid: [Reactions(**r) for r in reactions.values()]
for mid, reactions in grouped.items()
}
return {mid: [Reactions(**r) for r in reactions.values()] for mid, reactions in grouped.items()}
async def get_thread_reply_counts_by_message_ids(
self, ids: list[str], db: Optional[AsyncSession] = None

View file

@ -244,9 +244,7 @@ class ToolsTable:
try:
async with get_async_db_context(db) as db:
await db.execute(
update(Tool)
.filter_by(id=id)
.values(valves=encrypt_valves(valves), updated_at=int(time.time()))
update(Tool).filter_by(id=id).values(valves=encrypt_valves(valves), updated_at=int(time.time()))
)
await db.commit()
return await self.get_tool_by_id(id, db=db)

View file

@ -192,7 +192,7 @@ async def _retrieve_milvus(connection, auth_config, knowledge, query, count, emb
}
normalized = []
for hit in (response[0] if response else []):
for hit in response[0] if response else []:
item = dict(hit)
entity = item.get('entity') or {}
result = {

View file

@ -280,10 +280,7 @@ class MilvusClient(VectorDBBase):
for item in items:
text = item['text'] or ''
if len(text) > MILVUS_TEXT_MAX_LENGTH:
log.warning(
f'Milvus: truncating text id={item["id"]} '
f'{len(text)}->{MILVUS_TEXT_MAX_LENGTH} chars'
)
log.warning(f'Milvus: truncating text id={item["id"]} {len(text)}->{MILVUS_TEXT_MAX_LENGTH} chars')
text = text[:MILVUS_TEXT_MAX_LENGTH]
data.append(
{
@ -299,10 +296,7 @@ class MilvusClient(VectorDBBase):
data=data,
)
except MilvusException as e:
log.error(
f'Milvus insert failed for {self.collection_prefix}_{collection_name} '
f'({len(items)} items): {e}'
)
log.error(f'Milvus insert failed for {self.collection_prefix}_{collection_name} ({len(items)} items): {e}')
raise
def upsert(self, collection_name: str, items: list[VectorItem]):
@ -324,10 +318,7 @@ class MilvusClient(VectorDBBase):
for item in items:
text = item['text'] or ''
if len(text) > MILVUS_TEXT_MAX_LENGTH:
log.warning(
f'Milvus: truncating text id={item["id"]} '
f'{len(text)}->{MILVUS_TEXT_MAX_LENGTH} chars'
)
log.warning(f'Milvus: truncating text id={item["id"]} {len(text)}->{MILVUS_TEXT_MAX_LENGTH} chars')
text = text[:MILVUS_TEXT_MAX_LENGTH]
data.append(
{
@ -343,10 +334,7 @@ class MilvusClient(VectorDBBase):
data=data,
)
except MilvusException as e:
log.error(
f'Milvus upsert failed for {self.collection_prefix}_{collection_name} '
f'({len(items)} items): {e}'
)
log.error(f'Milvus upsert failed for {self.collection_prefix}_{collection_name} ({len(items)} items): {e}')
raise
def delete(

View file

@ -292,8 +292,7 @@ async def update_audio_config(request: Request, form_data: AudioConfigUpdateForm
if form_data.stt.ENGINE == '':
request.app.state.faster_whisper_model = await asyncio.to_thread(
set_faster_whisper_model,
form_data.stt.WHISPER_MODEL, WHISPER_MODEL_AUTO_UPDATE
set_faster_whisper_model, form_data.stt.WHISPER_MODEL, WHISPER_MODEL_AUTO_UPDATE
)
else:
request.app.state.faster_whisper_model = None
@ -560,9 +559,7 @@ async def speech(request: Request, user=Depends(get_verified_user)):
detail=ERROR_MESSAGES.NOT_FOUND,
)
if user.role != 'admin' and not await has_permission(
user.id, 'chat.tts', await Config.get('user.permissions')
):
if user.role != 'admin' and not await has_permission(user.id, 'chat.tts', await Config.get('user.permissions')):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@ -1152,9 +1149,7 @@ async def transcription(
language: Optional[str] = Form(None),
user=Depends(get_verified_user),
):
if user.role != 'admin' and not await has_permission(
user.id, 'chat.stt', await Config.get('user.permissions')
):
if user.role != 'admin' and not await has_permission(user.id, 'chat.stt', await Config.get('user.permissions')):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,

View file

@ -186,7 +186,8 @@ async def create_session_response(
request,
EVENTS.AUTH_LOGIN,
actor=user,
subject_id=user.id, subject_type='user',
subject_id=user.id,
subject_type='user',
source=source,
data={'auth_method': source},
)
@ -386,7 +387,8 @@ async def update_password(
request,
EVENTS.AUTH_PASSWORD_CHANGED,
actor=user,
subject_id=user.id, subject_type='user',
subject_id=user.id,
subject_type='user',
)
return success
else:
@ -1372,9 +1374,7 @@ async def update_oauth_config(request: Request, form_data: OAuthConfigForm, user
async def _check_api_key_permission(request: Request, user, db: AsyncSession):
if not await Config.get('auth.enable_api_keys') or (
user.role != 'admin'
and not await has_permission(
user.id, 'features.api_keys', await Config.get('user.permissions'), db=db
)
and not await has_permission(user.id, 'features.api_keys', await Config.get('user.permissions'), db=db)
):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
@ -1397,7 +1397,8 @@ async def generate_api_key(
request,
EVENTS.AUTH_API_KEY_CREATED,
actor=user,
subject_id=user.id, subject_type='user',
subject_id=user.id,
subject_type='user',
)
return {
'api_key': api_key,
@ -1418,16 +1419,15 @@ async def delete_api_key(
request,
EVENTS.AUTH_API_KEY_DELETED,
actor=user,
subject_id=user.id, subject_type='user',
subject_id=user.id,
subject_type='user',
)
return success
# get api key
@router.get('/api_key', response_model=ApiKey)
async def get_api_key(
request: Request, user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session)
):
async def get_api_key(request: Request, user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session)):
await _check_api_key_permission(request, user, db)
api_key = await Users.get_user_api_key_by_id(user.id, db=db)
if api_key:

View file

@ -266,7 +266,8 @@ async def toggle_automation_by_id(
request,
EVENTS.AUTOMATION_ENABLED if toggled.is_active else EVENTS.AUTOMATION_DISABLED,
actor=user,
subject_id=toggled.id, subject_type='automation',
subject_id=toggled.id,
subject_type='automation',
data={'name': toggled.name},
)
return response

View file

@ -42,9 +42,7 @@ async def check_calendar_permission(request: Request, user):
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
if user.role != 'admin' and not await has_permission(
user.id, 'features.calendar', config.get('user.permissions')
):
if user.role != 'admin' and not await has_permission(user.id, 'features.calendar', config.get('user.permissions')):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.UNAUTHORIZED,

View file

@ -575,9 +575,7 @@ async def delete_all_user_chats(
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
if user.role == 'user' and not await has_permission(
user.id, 'chat.delete', await Config.get('user.permissions')
):
if user.role == 'user' and not await has_permission(user.id, 'chat.delete', await Config.get('user.permissions')):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@ -589,7 +587,8 @@ async def delete_all_user_chats(
request,
EVENTS.CHAT_DELETED_ALL,
actor=user,
subject_id=user.id, subject_type='user',
subject_id=user.id,
subject_type='user',
)
return result
@ -1001,7 +1000,8 @@ async def unshare_all_chats(
request,
EVENTS.CHAT_UNSHARED,
actor=user,
subject_id=user.id, subject_type='user',
subject_id=user.id,
subject_type='user',
data={'count': len(chat_ids), 'chat_ids': chat_ids},
)
return result
@ -1215,6 +1215,12 @@ async def update_chat_by_id(
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
updated_chat = {**chat.chat, **form_data.chat}
if 'history' in form_data.chat:
updated_chat['history'] = Chats.merge_history(
chat.chat.get('history'),
form_data.chat.get('history'),
form_data.deleted_message_ids,
)
# Re-derive content from output for assistant messages so that frontend
# edits to output items are reflected in content. Only when output
@ -1222,7 +1228,7 @@ async def update_chat_by_id(
# (e.g. a `replace` event or an outlet filter footer) would be reverted.
existing_messages = (chat.chat.get('history') or {}).get('messages') or {}
for msg_id, msg in updated_chat.get('history', {}).get('messages', {}).items():
if msg.get('role') == 'assistant' and msg.get('output'):
if isinstance(msg, dict) and msg.get('role') == 'assistant' and msg.get('output'):
if msg.get('output') != existing_messages.get(msg_id, {}).get('output'):
msg['content'] = serialize_output(msg['output'])
@ -1233,7 +1239,7 @@ async def update_chat_by_id(
# history with potential edits, deletions, or new branches.
messages = (updated_chat.get('history') or {}).get('messages') or {}
if messages:
await Chats.reconcile_messages_by_chat_id(id, user.id, messages)
await Chats.reconcile_messages_by_chat_id(id, user.id, messages, form_data.deleted_message_ids)
await publish_event(
request,
@ -1470,7 +1476,8 @@ async def pin_chat_by_id(
request,
EVENTS.CHAT_PINNED if chat.pinned else EVENTS.CHAT_UNPINNED,
actor=user,
subject_id=id, subject_type='chat',
subject_id=id,
subject_type='chat',
)
return chat
else:
@ -1643,7 +1650,8 @@ async def archive_chat_by_id(
request,
EVENTS.CHAT_ARCHIVED if chat.archived else EVENTS.CHAT_UNARCHIVED,
actor=user,
subject_id=id, subject_type='chat',
subject_id=id,
subject_type='chat',
)
return ChatResponse(**chat.model_dump())
else:
@ -1660,9 +1668,7 @@ async def share_chat_by_id(
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not await has_permission(
user.id, 'chat.share', await Config.get('user.permissions')
):
if user.role != 'admin' and not await has_permission(user.id, 'chat.share', await Config.get('user.permissions')):
raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED)
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)

View file

@ -144,7 +144,8 @@ async def set_connections_config(
request,
EVENTS.CONFIG_CONNECTIONS_UPDATED,
actor=user,
subject_id='connections', subject_type='config',
subject_id='connections',
subject_type='config',
data=values,
)
return values
@ -262,9 +263,7 @@ async def set_tool_servers_config(
if auth_type in ('oauth_2.1', 'oauth_2.1_static') and server_id:
try:
oauth_client_info = resolve_oauth_client_info(connection)
oauth_client_info = await recover_static_oauth_client_metadata(
connection, oauth_client_info
)
oauth_client_info = await recover_static_oauth_client_metadata(connection, oauth_client_info)
oauth_client_info = apply_connection_oauth_options(connection, oauth_client_info)
request.app.state.oauth_client_manager.add_client(
f'{server_type}:{server_id}',
@ -278,7 +277,8 @@ async def set_tool_servers_config(
request,
EVENTS.CONFIG_TOOL_SERVERS_UPDATED,
actor=user,
subject_id='tool_server.connections', subject_type='config',
subject_id='tool_server.connections',
subject_type='config',
data={'count': len(connections), 'types': [connection.get('type', 'openapi') for connection in connections]},
)
return {'TOOL_SERVER_CONNECTIONS': connections}
@ -330,7 +330,8 @@ async def set_terminal_servers_config(
request,
EVENTS.CONFIG_TERMINAL_SERVERS_UPDATED,
actor=user,
subject_id='terminal_server.connections', subject_type='config',
subject_id='terminal_server.connections',
subject_type='config',
data={'count': len(connections)},
)
return {'TERMINAL_SERVER_CONNECTIONS': connections}
@ -699,7 +700,8 @@ async def set_code_execution_config(
request,
EVENTS.CONFIG_CODE_EXECUTION_UPDATED,
actor=user,
subject_id='code_execution', subject_type='config',
subject_id='code_execution',
subject_type='config',
data={
'code_execution_enabled': values.get('ENABLE_CODE_EXECUTION'),
'code_execution_engine': values.get('CODE_EXECUTION_ENGINE'),
@ -741,7 +743,8 @@ async def set_models_config(request: Request, form_data: ModelsConfigForm, user=
request,
EVENTS.CONFIG_MODELS_UPDATED,
actor=user,
subject_id='models', subject_type='config',
subject_id='models',
subject_type='config',
data={
'default_models': values.get('DEFAULT_MODELS'),
'default_pinned_models': values.get('DEFAULT_PINNED_MODELS'),
@ -773,7 +776,8 @@ async def set_default_suggestions(
request,
EVENTS.CONFIG_SUGGESTIONS_UPDATED,
actor=user,
subject_id='ui.prompt_suggestions', subject_type='config',
subject_id='ui.prompt_suggestions',
subject_type='config',
data={'count': len(suggestions or [])},
)
return suggestions
@ -801,7 +805,8 @@ async def set_banners(
request,
EVENTS.CONFIG_BANNERS_UPDATED,
actor=user,
subject_id='ui.banners', subject_type='config',
subject_id='ui.banners',
subject_type='config',
data={'count': len(banners or [])},
)
return banners

View file

@ -373,7 +373,8 @@ async def delete_feedbacks(
request,
EVENTS.FEEDBACK_DELETED_ALL,
actor=user,
subject_id=user.id, subject_type='user',
subject_id=user.id,
subject_type='user',
)
return success

View file

@ -270,9 +270,7 @@ async def upload_file(
result_meta = result.meta or {}
result_content_type = (
result_meta.get('content_type')
if isinstance(result_meta, dict)
else getattr(result_meta, 'content_type', None)
result_meta.get('content_type') if isinstance(result_meta, dict) else getattr(result_meta, 'content_type', None)
)
await publish_event(
request,
@ -532,7 +530,9 @@ async def count_files(
@router.delete('/all')
async def delete_all_files(request: Request, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
async def delete_all_files(
request: Request, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)
):
result = await Files.delete_all_files(db=db)
if result:
try:

View file

@ -182,9 +182,7 @@ async def get_shared_folders(
groups = await Groups.get_groups_by_member_id(user.id, db=db)
group_ids = {g.id for g in groups}
folder_perms = await Folders.get_shared_folder_ids_for_user(
user.id, group_ids, db=db
)
folder_perms = await Folders.get_shared_folder_ids_for_user(user.id, group_ids, db=db)
# Filter out folders owned by the user
results = []
@ -199,28 +197,30 @@ async def get_shared_folders(
owner = await Users.get_user_by_id(folder.user_id, db=db)
owner_cache[folder.user_id] = owner.name if owner else 'Unknown'
results.append({
**folder.model_dump(),
'owner_name': owner_cache[folder.user_id],
'permission': permission,
})
results.append(
{
**folder.model_dump(),
'owner_name': owner_cache[folder.user_id],
'permission': permission,
}
)
# Also include child folders of shared folders (inheritance)
shared_root_ids = {r['id'] for r in results}
for root_id in list(shared_root_ids):
root_folder = await Folders.get_folder_by_id(root_id, db=db)
if root_folder:
children = await Folders.get_children_folders_by_id_and_user_id(
root_id, root_folder.user_id, db=db
)
children = await Folders.get_children_folders_by_id_and_user_id(root_id, root_folder.user_id, db=db)
if children:
for child in children:
if child.id not in {r['id'] for r in results}:
results.append({
**child.model_dump(),
'owner_name': owner_cache.get(child.user_id, 'Unknown'),
'permission': folder_perms.get(root_id, 'read'),
})
results.append(
{
**child.model_dump(),
'owner_name': owner_cache.get(child.user_id, 'Unknown'),
'permission': folder_perms.get(root_id, 'read'),
}
)
return results
@ -231,7 +231,9 @@ async def get_shared_folders(
@router.get('/{id}', response_model=None)
async def get_folder_by_id(request: Request, id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
async def get_folder_by_id(
request: Request, id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
await check_folders_permission(request, user, db=db)
folder = await Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
if folder:
@ -443,7 +445,8 @@ async def update_folder_access_by_id(
form_data.access_grants = await filter_allowed_access_grants(
await Config.get('user.permissions'),
user.id, user.role,
user.id,
user.role,
form_data.access_grants,
None,
db=db,
@ -509,10 +512,7 @@ async def get_shared_folder_chats(
chat['owner_name'] = owner_cache[uid]
return {
'chats': [
{**chat, 'readonly': chat['user_id'] != user.id}
for chat in chats
],
'chats': [{**chat, 'readonly': chat['user_id'] != user.id} for chat in chats],
'folder_permission': 'write' if has_write else 'read',
}

View file

@ -291,7 +291,8 @@ async def toggle_function_by_id(
request,
EVENTS.FUNCTION_ENABLED if function.is_active else EVENTS.FUNCTION_DISABLED,
actor=user,
subject_id=function.id, subject_type='function',
subject_id=function.id,
subject_type='function',
data={'type': function.type, 'name': function.name},
)
return function

View file

@ -201,25 +201,12 @@ async def set_image_model(request: Request, model: str):
async def get_image_model(request):
image_config = await get_image_config()
if image_config.IMAGE_GENERATION_ENGINE == 'openai':
return (
image_config.IMAGE_GENERATION_MODEL
if image_config.IMAGE_GENERATION_MODEL
else 'dall-e-2'
)
return image_config.IMAGE_GENERATION_MODEL if image_config.IMAGE_GENERATION_MODEL else 'dall-e-2'
elif image_config.IMAGE_GENERATION_ENGINE == 'gemini':
return (
image_config.IMAGE_GENERATION_MODEL
if image_config.IMAGE_GENERATION_MODEL
else 'imagen-3.0-generate-002'
)
return image_config.IMAGE_GENERATION_MODEL if image_config.IMAGE_GENERATION_MODEL else 'imagen-3.0-generate-002'
elif image_config.IMAGE_GENERATION_ENGINE == 'comfyui':
return (
image_config.IMAGE_GENERATION_MODEL if image_config.IMAGE_GENERATION_MODEL else ''
)
elif (
image_config.IMAGE_GENERATION_ENGINE == 'automatic1111'
or image_config.IMAGE_GENERATION_ENGINE == ''
):
return image_config.IMAGE_GENERATION_MODEL if image_config.IMAGE_GENERATION_MODEL else ''
elif image_config.IMAGE_GENERATION_ENGINE == 'automatic1111' or image_config.IMAGE_GENERATION_ENGINE == '':
try:
session = await get_session()
async with session.get(
@ -433,10 +420,7 @@ async def get_models(request: Request, user=Depends(get_verified_user)):
info['CheckpointLoaderSimple']['input']['required']['ckpt_name'][0],
)
)
elif (
image_config.IMAGE_GENERATION_ENGINE == 'automatic1111'
or image_config.IMAGE_GENERATION_ENGINE == ''
):
elif image_config.IMAGE_GENERATION_ENGINE == 'automatic1111' or image_config.IMAGE_GENERATION_ENGINE == '':
session = await get_session()
async with session.get(
url=f'{image_config.AUTOMATIC1111_BASE_URL}/sdapi/v1/sd-models',
@ -592,7 +576,8 @@ async def generate_images(request: Request, form_data: CreateImageForm, user=Dep
request,
EVENTS.IMAGE_GENERATED,
actor=user,
subject_id=None, subject_type='image',
subject_id=None,
subject_type='image',
data={
'model': form_data.model,
'size': form_data.size,
@ -658,11 +643,7 @@ async def image_generations(
)
else {'response_format': 'b64_json'}
),
**(
{}
if not image_config.IMAGES_OPENAI_API_PARAMS
else image_config.IMAGES_OPENAI_API_PARAMS
),
**({} if not image_config.IMAGES_OPENAI_API_PARAMS else image_config.IMAGES_OPENAI_API_PARAMS),
}
session = await get_session()
@ -803,10 +784,7 @@ async def image_generations(
)
images.append({'url': url})
return images
elif (
image_config.IMAGE_GENERATION_ENGINE == 'automatic1111'
or image_config.IMAGE_GENERATION_ENGINE == ''
):
elif image_config.IMAGE_GENERATION_ENGINE == 'automatic1111' or image_config.IMAGE_GENERATION_ENGINE == '':
if form_data.model:
await set_image_model(request, form_data.model)
@ -892,7 +870,8 @@ async def edit_images(request: Request, form_data: EditImageForm, user=Depends(g
request,
EVENTS.IMAGE_EDITED,
actor=user,
subject_id=None, subject_type='image',
subject_id=None,
subject_type='image',
data={
'model': form_data.model,
'size': form_data.size,

View file

@ -570,7 +570,9 @@ async def _set_external_connections(connections: list[dict]) -> None:
await Config.upsert({EXTERNAL_KNOWLEDGE_CONNECTIONS_CONFIG_KEY: connections})
def _external_connection_dict(form_data: ExternalKnowledgeConnectionForm, user_id: str, id: Optional[str] = None) -> dict:
def _external_connection_dict(
form_data: ExternalKnowledgeConnectionForm, user_id: str, id: Optional[str] = None
) -> dict:
provider, config = _validate_external_connection_form(form_data)
now = int(time.time())
return {
@ -1703,9 +1705,7 @@ async def delete_knowledge_by_id(
connection_id = (knowledge.meta or {}).get('external', {}).get('connection_id')
if connection_id:
connections = [
connection
for connection in await _get_external_connections()
if connection.get('id') != connection_id
connection for connection in await _get_external_connections() if connection.get('id') != connection_id
]
await _set_external_connections(connections)
else:

View file

@ -39,9 +39,7 @@ async def check_memories_permission(user):
detail=ERROR_MESSAGES.NOT_FOUND,
)
if user.role != 'admin' and not await has_permission(
user.id, 'features.memories', config.get('user.permissions')
):
if user.role != 'admin' and not await has_permission(user.id, 'features.memories', config.get('user.permissions')):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@ -431,7 +429,8 @@ async def reset_memory_from_vector_db(
request,
EVENTS.MEMORY_RESET,
actor=user,
subject_id=user.id, subject_type='user',
subject_id=user.id,
subject_type='user',
data={'count': len(memories)},
)
return True
@ -461,7 +460,8 @@ async def delete_memory_by_user_id(
request,
EVENTS.MEMORY_DELETED,
actor=user,
subject_id=user.id, subject_type='user',
subject_id=user.id,
subject_type='user',
)
return True

View file

@ -654,7 +654,8 @@ async def toggle_model_by_id(
request,
EVENTS.MODEL_ENABLED if model.is_active else EVENTS.MODEL_DISABLED,
actor=user,
subject_id=model.id, subject_type='model',
subject_id=model.id,
subject_type='model',
data={'name': model.name},
)
return model
@ -867,7 +868,9 @@ async def delete_model_by_id(
@router.delete('/delete/all', response_model=bool)
async def delete_all_models(request: Request, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
async def delete_all_models(
request: Request, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)
):
result = await Models.delete_all_models(db=db)
if result:
await publish_event(request, EVENTS.MODEL_DELETED, actor=user, subject_type='model')

View file

@ -479,7 +479,8 @@ async def pin_note_by_id(
request,
EVENTS.NOTE_PINNED if note.is_pinned else EVENTS.NOTE_UNPINNED,
actor=user,
subject_id=note.id, subject_type='note',
subject_id=note.id,
subject_type='note',
)
return note

View file

@ -356,9 +356,7 @@ async def update_config(request: Request, form_data: OpenAIConfigForm, user=Depe
@router.post('/audio/speech')
async def speech(request: Request, user=Depends(get_verified_user)):
if user.role != 'admin' and not await has_permission(
user.id, 'chat.tts', await Config.get('user.permissions')
):
if user.role != 'admin' and not await has_permission(user.id, 'chat.tts', await Config.get('user.permissions')):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,

View file

@ -532,7 +532,8 @@ async def toggle_prompt_active(
request,
EVENTS.PROMPT_ENABLED if result.is_active else EVENTS.PROMPT_DISABLED,
actor=user,
subject_id=result.id, subject_type='prompt',
subject_id=result.id,
subject_type='prompt',
data={'name': result.name, 'command': result.command},
)
return result

View file

@ -196,9 +196,7 @@ def get_rf(
)
except Exception as e:
log.error(f'ExternalReranking: {e}')
raise Exception(
ERROR_MESSAGES.DEFAULT(e, 'Error loading reranking model')
)
raise Exception(ERROR_MESSAGES.DEFAULT(e, 'Error loading reranking model'))
else:
import sentence_transformers
import torch
@ -516,9 +514,7 @@ async def unload_embedding_model(request: Request):
@router.post('/embedding/update')
async def update_embedding_config(request: Request, form_data: EmbeddingModelUpdateForm, user=Depends(get_admin_user)):
config = await get_retrieval_config()
log.info(
f'Updating embedding model: {config.RAG_EMBEDDING_MODEL} to {form_data.RAG_EMBEDDING_MODEL}'
)
log.info(f'Updating embedding model: {config.RAG_EMBEDDING_MODEL} to {form_data.RAG_EMBEDDING_MODEL}')
await unload_embedding_model(request)
try:
config.RAG_EMBEDDING_ENGINE = form_data.RAG_EMBEDDING_ENGINE
@ -574,9 +570,7 @@ async def update_embedding_config(request: Request, form_data: EmbeddingModelUpd
),
config.RAG_EMBEDDING_BATCH_SIZE,
azure_api_version=(
config.RAG_AZURE_OPENAI_API_VERSION
if config.RAG_EMBEDDING_ENGINE == 'azure_openai'
else None
config.RAG_AZURE_OPENAI_API_VERSION if config.RAG_EMBEDDING_ENGINE == 'azure_openai' else None
),
enable_async=config.ENABLE_ASYNC_EMBEDDING,
concurrent_requests=config.RAG_EMBEDDING_CONCURRENT_REQUESTS,
@ -933,9 +927,7 @@ class ConfigForm(BaseModel):
async def update_rag_config(request: Request, form_data: ConfigForm, user=Depends(get_admin_user)):
# RAG settings
config = await get_retrieval_config()
config.RAG_TEMPLATE = (
form_data.RAG_TEMPLATE if form_data.RAG_TEMPLATE is not None else config.RAG_TEMPLATE
)
config.RAG_TEMPLATE = form_data.RAG_TEMPLATE if form_data.RAG_TEMPLATE is not None else config.RAG_TEMPLATE
config.TOP_K = form_data.TOP_K if form_data.TOP_K is not None else config.TOP_K
config.BYPASS_EMBEDDING_AND_RETRIEVAL = (
form_data.BYPASS_EMBEDDING_AND_RETRIEVAL
@ -943,9 +935,7 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
else config.BYPASS_EMBEDDING_AND_RETRIEVAL
)
config.RAG_FULL_CONTEXT = (
form_data.RAG_FULL_CONTEXT
if form_data.RAG_FULL_CONTEXT is not None
else config.RAG_FULL_CONTEXT
form_data.RAG_FULL_CONTEXT if form_data.RAG_FULL_CONTEXT is not None else config.RAG_FULL_CONTEXT
)
# Hybrid search settings
@ -960,18 +950,12 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
else config.ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS
)
config.TOP_K_RERANKER = (
form_data.TOP_K_RERANKER if form_data.TOP_K_RERANKER is not None else config.TOP_K_RERANKER
)
config.TOP_K_RERANKER = form_data.TOP_K_RERANKER if form_data.TOP_K_RERANKER is not None else config.TOP_K_RERANKER
config.RELEVANCE_THRESHOLD = (
form_data.RELEVANCE_THRESHOLD
if form_data.RELEVANCE_THRESHOLD is not None
else config.RELEVANCE_THRESHOLD
form_data.RELEVANCE_THRESHOLD if form_data.RELEVANCE_THRESHOLD is not None else config.RELEVANCE_THRESHOLD
)
config.HYBRID_BM25_WEIGHT = (
form_data.HYBRID_BM25_WEIGHT
if form_data.HYBRID_BM25_WEIGHT is not None
else config.HYBRID_BM25_WEIGHT
form_data.HYBRID_BM25_WEIGHT if form_data.HYBRID_BM25_WEIGHT is not None else config.HYBRID_BM25_WEIGHT
)
# Content extraction settings
@ -981,9 +965,7 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
else config.CONTENT_EXTRACTION_ENGINE
)
config.PDF_EXTRACT_IMAGES = (
form_data.PDF_EXTRACT_IMAGES
if form_data.PDF_EXTRACT_IMAGES is not None
else config.PDF_EXTRACT_IMAGES
form_data.PDF_EXTRACT_IMAGES if form_data.PDF_EXTRACT_IMAGES is not None else config.PDF_EXTRACT_IMAGES
)
config.PDF_LOADER_MODE = (
form_data.PDF_LOADER_MODE if form_data.PDF_LOADER_MODE is not None else config.PDF_LOADER_MODE
@ -1062,16 +1044,12 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
form_data.TIKA_SERVER_URL if form_data.TIKA_SERVER_URL is not None else config.TIKA_SERVER_URL
)
config.DOCLING_SERVER_URL = (
form_data.DOCLING_SERVER_URL
if form_data.DOCLING_SERVER_URL is not None
else config.DOCLING_SERVER_URL
form_data.DOCLING_SERVER_URL if form_data.DOCLING_SERVER_URL is not None else config.DOCLING_SERVER_URL
)
config.DOCLING_API_KEY = (
form_data.DOCLING_API_KEY if form_data.DOCLING_API_KEY is not None else config.DOCLING_API_KEY
)
config.DOCLING_PARAMS = (
form_data.DOCLING_PARAMS if form_data.DOCLING_PARAMS is not None else config.DOCLING_PARAMS
)
config.DOCLING_PARAMS = form_data.DOCLING_PARAMS if form_data.DOCLING_PARAMS is not None else config.DOCLING_PARAMS
config.DOCUMENT_INTELLIGENCE_ENDPOINT = (
form_data.DOCUMENT_INTELLIGENCE_ENDPOINT
if form_data.DOCUMENT_INTELLIGENCE_ENDPOINT is not None
@ -1094,39 +1072,25 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
else config.MISTRAL_OCR_API_BASE_URL
)
config.MISTRAL_OCR_API_KEY = (
form_data.MISTRAL_OCR_API_KEY
if form_data.MISTRAL_OCR_API_KEY is not None
else config.MISTRAL_OCR_API_KEY
form_data.MISTRAL_OCR_API_KEY if form_data.MISTRAL_OCR_API_KEY is not None else config.MISTRAL_OCR_API_KEY
)
config.PADDLEOCR_VL_BASE_URL = (
form_data.PADDLEOCR_VL_BASE_URL
if form_data.PADDLEOCR_VL_BASE_URL is not None
else config.PADDLEOCR_VL_BASE_URL
form_data.PADDLEOCR_VL_BASE_URL if form_data.PADDLEOCR_VL_BASE_URL is not None else config.PADDLEOCR_VL_BASE_URL
)
config.PADDLEOCR_VL_TOKEN = (
form_data.PADDLEOCR_VL_TOKEN
if form_data.PADDLEOCR_VL_TOKEN is not None
else config.PADDLEOCR_VL_TOKEN
form_data.PADDLEOCR_VL_TOKEN if form_data.PADDLEOCR_VL_TOKEN is not None else config.PADDLEOCR_VL_TOKEN
)
# MinerU settings
config.MINERU_API_MODE = (
form_data.MINERU_API_MODE if form_data.MINERU_API_MODE is not None else config.MINERU_API_MODE
)
config.MINERU_API_URL = (
form_data.MINERU_API_URL if form_data.MINERU_API_URL is not None else config.MINERU_API_URL
)
config.MINERU_API_KEY = (
form_data.MINERU_API_KEY if form_data.MINERU_API_KEY is not None else config.MINERU_API_KEY
)
config.MINERU_API_URL = form_data.MINERU_API_URL if form_data.MINERU_API_URL is not None else config.MINERU_API_URL
config.MINERU_API_KEY = form_data.MINERU_API_KEY if form_data.MINERU_API_KEY is not None else config.MINERU_API_KEY
config.MINERU_API_TIMEOUT = (
form_data.MINERU_API_TIMEOUT
if form_data.MINERU_API_TIMEOUT is not None
else config.MINERU_API_TIMEOUT
)
config.MINERU_PARAMS = (
form_data.MINERU_PARAMS if form_data.MINERU_PARAMS is not None else config.MINERU_PARAMS
form_data.MINERU_API_TIMEOUT if form_data.MINERU_API_TIMEOUT is not None else config.MINERU_API_TIMEOUT
)
config.MINERU_PARAMS = form_data.MINERU_PARAMS if form_data.MINERU_PARAMS is not None else config.MINERU_PARAMS
config.MINERU_FILE_EXTENSIONS = (
form_data.MINERU_FILE_EXTENSIONS
if form_data.MINERU_FILE_EXTENSIONS is not None
@ -1147,9 +1111,7 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
if torch.cuda.is_available():
torch.cuda.empty_cache()
config.RAG_RERANKING_ENGINE = (
form_data.RAG_RERANKING_ENGINE
if form_data.RAG_RERANKING_ENGINE is not None
else config.RAG_RERANKING_ENGINE
form_data.RAG_RERANKING_ENGINE if form_data.RAG_RERANKING_ENGINE is not None else config.RAG_RERANKING_ENGINE
)
config.RAG_EXTERNAL_RERANKER_URL = (
@ -1176,21 +1138,14 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
else config.RAG_RERANKING_BATCH_SIZE
)
log.info(
f'Updating reranking model: {config.RAG_RERANKING_MODEL} to {form_data.RAG_RERANKING_MODEL}'
)
log.info(f'Updating reranking model: {config.RAG_RERANKING_MODEL} to {form_data.RAG_RERANKING_MODEL}')
try:
config.RAG_RERANKING_MODEL = (
form_data.RAG_RERANKING_MODEL
if form_data.RAG_RERANKING_MODEL is not None
else config.RAG_RERANKING_MODEL
form_data.RAG_RERANKING_MODEL if form_data.RAG_RERANKING_MODEL is not None else config.RAG_RERANKING_MODEL
)
try:
if (
config.ENABLE_RAG_HYBRID_SEARCH
and not config.BYPASS_EMBEDDING_AND_RETRIEVAL
):
if config.ENABLE_RAG_HYBRID_SEARCH and not config.BYPASS_EMBEDDING_AND_RETRIEVAL:
request.app.state.rf = get_rf(
config.RAG_RERANKING_ENGINE,
config.RAG_RERANKING_MODEL,
@ -1216,25 +1171,17 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
)
# Chunking settings
config.TEXT_SPLITTER = (
form_data.TEXT_SPLITTER if form_data.TEXT_SPLITTER is not None else config.TEXT_SPLITTER
)
config.TEXT_SPLITTER = form_data.TEXT_SPLITTER if form_data.TEXT_SPLITTER is not None else config.TEXT_SPLITTER
config.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER = (
form_data.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER
if form_data.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER is not None
else config.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER
)
config.CHUNK_SIZE = (
form_data.CHUNK_SIZE if form_data.CHUNK_SIZE is not None else config.CHUNK_SIZE
)
config.CHUNK_SIZE = form_data.CHUNK_SIZE if form_data.CHUNK_SIZE is not None else config.CHUNK_SIZE
config.CHUNK_MIN_SIZE_TARGET = (
form_data.CHUNK_MIN_SIZE_TARGET
if form_data.CHUNK_MIN_SIZE_TARGET is not None
else config.CHUNK_MIN_SIZE_TARGET
)
config.CHUNK_OVERLAP = (
form_data.CHUNK_OVERLAP if form_data.CHUNK_OVERLAP is not None else config.CHUNK_OVERLAP
form_data.CHUNK_MIN_SIZE_TARGET if form_data.CHUNK_MIN_SIZE_TARGET is not None else config.CHUNK_MIN_SIZE_TARGET
)
config.CHUNK_OVERLAP = form_data.CHUNK_OVERLAP if form_data.CHUNK_OVERLAP is not None else config.CHUNK_OVERLAP
config.RAG_TOKENIZER_MODEL = (
form_data.RAG_TOKENIZER_MODEL.strip()
if form_data.RAG_TOKENIZER_MODEL is not None
@ -1287,9 +1234,7 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
config.WEB_FETCH_MAX_CONTENT_LENGTH = form_data.web.WEB_FETCH_MAX_CONTENT_LENGTH
config.WEB_LOADER_CONCURRENT_REQUESTS = form_data.web.WEB_LOADER_CONCURRENT_REQUESTS
config.WEB_SEARCH_DOMAIN_FILTER_LIST = form_data.web.WEB_SEARCH_DOMAIN_FILTER_LIST
config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = (
form_data.web.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL
)
config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = form_data.web.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL
config.BYPASS_WEB_SEARCH_WEB_LOADER = form_data.web.BYPASS_WEB_SEARCH_WEB_LOADER
config.OLLAMA_CLOUD_WEB_SEARCH_API_KEY = form_data.web.OLLAMA_CLOUD_WEB_SEARCH_API_KEY
config.SEARXNG_QUERY_URL = form_data.web.SEARXNG_QUERY_URL
@ -1802,9 +1747,7 @@ def save_docs_to_vector_db(
),
config.RAG_EMBEDDING_BATCH_SIZE,
azure_api_version=(
config.RAG_AZURE_OPENAI_API_VERSION
if config.RAG_EMBEDDING_ENGINE == 'azure_openai'
else None
config.RAG_AZURE_OPENAI_API_VERSION if config.RAG_EMBEDDING_ENGINE == 'azure_openai' else None
),
enable_async=config.ENABLE_ASYNC_EMBEDDING,
concurrent_requests=config.RAG_EMBEDDING_CONCURRENT_REQUESTS,
@ -1997,7 +1940,8 @@ async def process_file(
request,
EVENTS.RETRIEVAL_CONTENT_PROCESSED,
actor=user,
subject_id=file.id, subject_type='file',
subject_id=file.id,
subject_type='file',
data={'collection_name': None, 'filename': file.filename},
)
return {
@ -2056,7 +2000,8 @@ async def process_file(
request,
EVENTS.RETRIEVAL_CONTENT_PROCESSED,
actor=user,
subject_id=file.id, subject_type='file',
subject_id=file.id,
subject_type='file',
data={'collection_name': collection_name, 'filename': file.filename},
)
return {
@ -2131,7 +2076,8 @@ async def process_text(
request,
EVENTS.RETRIEVAL_CONTENT_PROCESSED,
actor=user,
subject_id=collection_name, subject_type='retrieval.collection',
subject_id=collection_name,
subject_type='retrieval.collection',
data={'name': form_data.name, 'content_preview': text_content[:300]},
)
return {
@ -2451,11 +2397,7 @@ async def search_web(request: Request, engine: str, query: str, user=None) -> li
config.WEB_SEARCH_DOMAIN_FILTER_LIST,
)
elif engine == 'azure':
if (
config.AZURE_AI_SEARCH_API_KEY
and config.AZURE_AI_SEARCH_ENDPOINT
and config.AZURE_AI_SEARCH_INDEX_NAME
):
if config.AZURE_AI_SEARCH_API_KEY and config.AZURE_AI_SEARCH_ENDPOINT and config.AZURE_AI_SEARCH_INDEX_NAME:
return await asyncio.to_thread(
search_azure,
config.AZURE_AI_SEARCH_API_KEY,
@ -2570,9 +2512,7 @@ async def process_web_search(request: Request, form_data: SearchForm, user=Depen
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
if user.role != 'admin' and not await has_permission(
user.id, 'features.web_search', config.USER_PERMISSIONS
):
if user.role != 'admin' and not await has_permission(user.id, 'features.web_search', config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@ -2980,7 +2920,8 @@ async def reset_upload_dir(request: Request, user=Depends(get_admin_user)) -> bo
request,
EVENTS.RETRIEVAL_UPLOADS_RESET,
actor=user,
subject_id='all', subject_type='file.uploads',
subject_id='all',
subject_type='file.uploads',
)
return True
@ -3117,7 +3058,8 @@ async def process_files_batch(
request,
EVENTS.RETRIEVAL_CONTENT_PROCESSED,
actor=user,
subject_id=collection_name, subject_type='retrieval.collection',
subject_id=collection_name,
subject_type='retrieval.collection',
data={
'count': len([item for item in file_results if item.status == 'completed']),
'errors': len(file_errors),

View file

@ -159,12 +159,8 @@ async def create_new_skill(
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not (
await has_permission(
user.id, 'workspace.skills', await Config.get('user.permissions'), db=db
)
or await has_permission(
user.id, 'workspace.skills_import', await Config.get('user.permissions'), db=db
)
await has_permission(user.id, 'workspace.skills', await Config.get('user.permissions'), db=db)
or await has_permission(user.id, 'workspace.skills_import', await Config.get('user.permissions'), db=db)
):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,

View file

@ -76,8 +76,7 @@ async def get_tools(
for tool in await Tools.get_tools(defer_content=True, db=db):
tool_module = tools_cache.get(tool.id)
has_user_valves = (
hasattr(tool_module, 'UserValves') if tool_module
else (tool.meta.has_user_valves if tool.meta else False)
hasattr(tool_module, 'UserValves') if tool_module else (tool.meta.has_user_valves if tool.meta else False)
)
tools.append(
ToolUserResponse(

View file

@ -280,7 +280,8 @@ async def update_default_user_permissions(request: Request, form_data: UserPermi
request,
EVENTS.USER_PERMISSIONS_UPDATED,
actor=user,
subject_id='user.permissions', subject_type='config',
subject_id='user.permissions',
subject_type='config',
)
return user_permissions
@ -691,7 +692,8 @@ async def update_user_by_id(
request,
EVENTS.AUTH_PASSWORD_CHANGED,
actor=session_user,
subject_id=user_id, subject_type='user',
subject_id=user_id,
subject_type='user',
source='admin',
)
return updated_user

View file

@ -54,10 +54,7 @@ async def compact_messages_for_request(
messages, previous_summary = _apply_latest_summary_checkpoint(messages)
token_threshold = _resolve_token_threshold(config['token_threshold'], metadata)
if (
not _exceeds_token_threshold(messages, system_prompt, previous_summary, token_threshold)
or len(messages) <= 3
):
if not _exceeds_token_threshold(messages, system_prompt, previous_summary, token_threshold) or len(messages) <= 3:
return messages, previous_summary, False
boundary = _find_compaction_boundary(messages)

View file

@ -355,9 +355,9 @@ async def add_memory_context(request, form_data: dict, user, model: dict | None
seen_ids.add(memory_id)
content = str(doc)
if metadata.get('path') and content.startswith(f"{metadata.get('path')}\n"):
if metadata.get('path') and content.startswith(f'{metadata.get("path")}\n'):
content = content[len(metadata.get('path')) + 1 :]
label = f"{metadata.get('path')}: {content}" if metadata.get('path') else content
label = f'{metadata.get("path")}: {content}' if metadata.get('path') else content
sections[Memories.normalize_memory_type(metadata.get('type'))].append(label)
parts = []

View file

@ -1476,6 +1476,7 @@ async def chat_completion_tools_handler(
return body, {'sources': sources}
async def chat_web_search_handler(request: Request, form_data: dict, extra_params: dict, user):
event_emitter = extra_params['__event_emitter__']
await event_emitter(
@ -3636,9 +3637,7 @@ async def non_streaming_chat_response_handler(response, ctx):
'end_tag': '</think>',
'attributes': {'type': 'reasoning_content'},
'content': (
[{'type': 'output_text', 'text': reasoning_content}]
if reasoning_content
else []
[{'type': 'output_text', 'text': reasoning_content}] if reasoning_content else []
),
'summary': None,
}
@ -4148,19 +4147,19 @@ async def streaming_chat_response_handler(response, ctx):
# error events so frontend and DB paths still receive them.
try:
raw_obj = json.loads(data)
raw_error = raw_obj.get("error") if isinstance(raw_obj, dict) else None
raw_error = raw_obj.get('error') if isinstance(raw_obj, dict) else None
if raw_error:
try:
Chats.upsert_message_to_chat_by_id_and_message_id(
metadata["chat_id"],
metadata["message_id"],
metadata['chat_id'],
metadata['message_id'],
{
"error": {"content": raw_error},
'error': {'content': raw_error},
},
)
except Exception:
pass
await event_emitter({ "type": "chat:completion", "data": { "error": raw_error } })
await event_emitter({'type': 'chat:completion', 'data': {'error': raw_error}})
except Exception:
pass
continue
@ -4390,9 +4389,9 @@ async def streaming_chat_response_handler(response, ctx):
current_response_tool_call['function']['name'] = delta_name
if delta_arguments:
current_response_tool_call['function'][
'arguments'
] += delta_arguments
current_response_tool_call['function']['arguments'] += (
delta_arguments
)
# Emit pending tool calls in real-time
if response_tool_calls:

View file

@ -254,12 +254,7 @@ def convert_output_to_messages(
def flush_pending():
nonlocal pending_content, pending_tool_calls, pending_reasoning, pending_reasoning_details
if (
not pending_content
and not pending_tool_calls
and not pending_reasoning
and not pending_reasoning_details
):
if not pending_content and not pending_tool_calls and not pending_reasoning and not pending_reasoning_details:
return
message = {

View file

@ -54,16 +54,8 @@ async def fetch_openai_models(request: Request, user: UserModel = None):
async def get_all_base_models(request: Request, user: UserModel = None):
config = await Config.get_many('openai.enable', 'ollama.enable')
openai_task = (
fetch_openai_models(request, user)
if config.get('openai.enable')
else asyncio.sleep(0, result=[])
)
ollama_task = (
fetch_ollama_models(request, user)
if config.get('ollama.enable')
else asyncio.sleep(0, result=[])
)
openai_task = fetch_openai_models(request, user) if config.get('openai.enable') else asyncio.sleep(0, result=[])
ollama_task = fetch_ollama_models(request, user) if config.get('ollama.enable') else asyncio.sleep(0, result=[])
function_task = get_function_models(request)
openai_models, ollama_models, function_models = await asyncio.gather(openai_task, ollama_task, function_task)

View file

@ -178,10 +178,7 @@ def _default_value(value):
async def get_oauth_runtime_config() -> SimpleNamespace:
keys = [key for key, _default in OAUTH_RUNTIME_CONFIG.values()]
stored = await Config.get_many(*keys)
values = {
name: stored.get(key, _default_value(default))
for name, (key, default) in OAUTH_RUNTIME_CONFIG.items()
}
values = {name: stored.get(key, _default_value(default)) for name, (key, default) in OAUTH_RUNTIME_CONFIG.items()}
return SimpleNamespace(**values)
@ -715,10 +712,7 @@ def apply_connection_oauth_options(connection: dict, oauth_client_info: dict) ->
def scope_has_resource_indicator(scope: str | None) -> bool:
if not scope:
return False
return any(
scope_value.startswith(('https://', 'http://', 'api://'))
for scope_value in scope.split()
)
return any(scope_value.startswith(('https://', 'http://', 'api://')) for scope_value in scope.split())
def should_send_oauth_resource(client_info: OAuthClientInformationFull | None) -> bool:
@ -852,9 +846,7 @@ class OAuthClientManager:
try:
oauth_client_info = resolve_oauth_client_info(connection)
oauth_client_info = await recover_static_oauth_client_metadata(
connection, oauth_client_info
)
oauth_client_info = await recover_static_oauth_client_metadata(connection, oauth_client_info)
oauth_client_info = apply_connection_oauth_options(connection, oauth_client_info)
return self.add_client(expected_client_id, OAuthClientInformationFull(**oauth_client_info))['client']
except Exception as e:

View file

@ -217,9 +217,7 @@ async def load_tool_module_by_id(tool_id, content=None):
# Install required packages found within the frontmatter.
# Runs `pip install` via subprocess, which can take a long time;
# offload to a thread so it doesn't block the event loop.
await asyncio.to_thread(
install_frontmatter_requirements, frontmatter.get('requirements', '')
)
await asyncio.to_thread(install_frontmatter_requirements, frontmatter.get('requirements', ''))
module_name = f'tool_{tool_id}'
module = types.ModuleType(module_name)
@ -264,9 +262,7 @@ async def load_function_module_by_id(function_id: str, content: str | None = Non
else:
frontmatter = extract_frontmatter(content)
# `pip install` via subprocess can block for a long time; offload it.
await asyncio.to_thread(
install_frontmatter_requirements, frontmatter.get('requirements', '')
)
await asyncio.to_thread(install_frontmatter_requirements, frontmatter.get('requirements', ''))
module_name = f'function_{function_id}'
module = types.ModuleType(module_name)
@ -468,8 +464,6 @@ async def install_tool_and_function_dependencies():
all_dependencies += f'{dependencies}, '
# `pip install` via subprocess can block for a long time; offload it.
await asyncio.to_thread(
install_frontmatter_requirements, all_dependencies.strip(', ')
)
await asyncio.to_thread(install_frontmatter_requirements, all_dependencies.strip(', '))
except Exception as e:
log.error(f'Error installing requirements: {e}')

View file

@ -618,19 +618,11 @@ async def get_builtin_tools(
builtin_functions.append(execute_code)
# Notes tools - search, view, create, and update user's notes
if (
is_builtin_tool_enabled('notes')
and config.get('notes.enable')
and await has_user_permission('notes')
):
if is_builtin_tool_enabled('notes') and config.get('notes.enable') and await has_user_permission('notes'):
builtin_functions.extend([search_notes, view_note, write_note, replace_note_content])
# Channels tools - search channels and messages
if (
is_builtin_tool_enabled('channels')
and config.get('channels.enable')
and await has_user_permission('channels')
):
if is_builtin_tool_enabled('channels') and config.get('channels.enable') and await has_user_permission('channels'):
builtin_functions.extend(
[
search_channels,
@ -659,11 +651,7 @@ async def get_builtin_tools(
)
# Calendar tools - search/create/update/delete events
if (
is_builtin_tool_enabled('calendar')
and config.get('calendar.enable')
and await has_user_permission('calendar')
):
if is_builtin_tool_enabled('calendar') and config.get('calendar.enable') and await has_user_permission('calendar'):
builtin_functions.extend(
[search_calendar_events, create_calendar_event, update_calendar_event, delete_calendar_event]
)

View file

@ -27,9 +27,7 @@ def _event_text(message: str, description: str | None = None, event_data: dict |
return '\n'.join(lines)
async def post_webhook(
name: str, url: str, message: str, event_data: dict, description: str | None = None
) -> bool:
async def post_webhook(name: str, url: str, message: str, event_data: dict, description: str | None = None) -> bool:
try:
log.debug(f'post_webhook: {url}, {message}, {event_data}')
# Block private-IP / loopback / cloud-metadata targets — the URL is

View file

@ -1156,7 +1156,12 @@ export const getChatAccessGrants = async (token: string, id: string) => {
return res;
};
export const updateChatById = async (token: string, id: string, chat: object) => {
export const updateChatById = async (
token: string,
id: string,
chat: object,
deletedMessageIds: string[] = []
) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}`, {
@ -1167,7 +1172,8 @@ export const updateChatById = async (token: string, id: string, chat: object) =>
...(token && { authorization: `Bearer ${token}` })
},
body: JSON.stringify({
chat: chat
chat: chat,
...(deletedMessageIds.length && { deleted_message_ids: deletedMessageIds })
})
})
.then(async (res) => {

View file

@ -512,7 +512,7 @@
}
};
const showMessage = async (message, scroll = true) => {
const showMessage = async (message, scroll = true, save = true) => {
const _chatId = JSON.parse(JSON.stringify($chatId));
let _messageId = JSON.parse(JSON.stringify(message.id));
@ -545,7 +545,9 @@
await tick();
await tick();
saveChatHandler(_chatId, history);
if (save) {
saveChatHandler(_chatId, history);
}
};
const updateLastReadAt = (id) => {

View file

@ -35,6 +35,7 @@
export let atSelectedModel;
let messages = [];
let deletedMessageIds = new Set<string>();
export let setInputText: Function = () => {};
@ -168,10 +169,16 @@
if (!$temporaryChatEnabled) {
history = history;
await tick();
const res = await updateChatById(localStorage.token, chatId, {
history: history,
messages: messages
});
const res = await updateChatById(
localStorage.token,
chatId,
{
history: history,
messages: messages
},
Array.from(deletedMessageIds)
);
deletedMessageIds.clear();
// Refresh local message content from backend (e.g. re-derived via serialize_output)
if (res?.chat?.history?.messages) {
@ -461,9 +468,11 @@
// Delete the message and its children
[messageId, ...childMessageIds].forEach((id) => {
delete history.messages[id];
deletedMessageIds.add(id);
});
showMessage({ id: parentMessageId }, false);
showMessage({ id: parentMessageId }, false, false);
await updateChat();
};
const triggerScroll = () => {